use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use crate::fetch::rsync::{RsyncFetchError, RsyncFetchResult, RsyncFetcher};
use crate::replay::archive::{ReplayArchiveIndex, canonical_rsync_module};
use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
use crate::storage::{RepositoryViewState, RocksStore};

#[derive(Clone, Debug)]
pub struct PayloadDeltaReplayRsyncFetcher {
    base_index: Arc<ReplayArchiveIndex>,
    delta_index: Arc<ReplayDeltaArchiveIndex>,
}

impl PayloadDeltaReplayRsyncFetcher {
    pub fn new(
        base_index: Arc<ReplayArchiveIndex>,
        delta_index: Arc<ReplayDeltaArchiveIndex>,
    ) -> Self {
        Self {
            base_index,
            delta_index,
        }
    }

    pub fn base_index(&self) -> &ReplayArchiveIndex {
        self.base_index.as_ref()
    }

    pub fn delta_index(&self) -> &ReplayDeltaArchiveIndex {
        self.delta_index.as_ref()
    }
}

pub struct PayloadDeltaReplayCurrentStoreRsyncFetcher<'a> {
    store: &'a RocksStore,
    delta_index: Arc<ReplayDeltaArchiveIndex>,
}

impl<'a> PayloadDeltaReplayCurrentStoreRsyncFetcher<'a> {
    pub fn new(store: &'a RocksStore, delta_index: Arc<ReplayDeltaArchiveIndex>) -> Self {
        Self { store, delta_index }
    }

    pub fn delta_index(&self) -> &ReplayDeltaArchiveIndex {
        self.delta_index.as_ref()
    }
}

impl RsyncFetcher for PayloadDeltaReplayRsyncFetcher {
    fn fetch_objects(&self, rsync_base_uri: &str) -> RsyncFetchResult<Vec<(String, Vec<u8>)>> {
        let module_uri = canonical_rsync_module(rsync_base_uri)
            .map_err(|e| RsyncFetchError::Fetch(e.to_string()))?;
        let normalized_base = if rsync_base_uri.ends_with('/') {
            rsync_base_uri.to_string()
        } else {
            format!("{rsync_base_uri}/")
        };

        let mut merged: BTreeMap<String, Vec<u8>> = BTreeMap::new();
        let mut saw_base = false;
        let overlay_only = self
            .delta_index
            .rsync_module(&module_uri)
            .map(|module| module.overlay_only)
            .unwrap_or(false);
        if !overlay_only {
            if let Ok(base_module) = self
                .base_index
                .resolve_rsync_module_for_base_uri(rsync_base_uri)
            {
                let base_tree_root = module_tree_root(&module_uri, &base_module.tree_dir)
                    .map_err(RsyncFetchError::Fetch)?;
                if base_tree_root.is_dir() {
                    let mut base_objects = Vec::new();
                    walk_dir_collect(
                        &base_tree_root,
                        &base_tree_root,
                        &module_uri,
                        &mut base_objects,
                    )
                    .map_err(RsyncFetchError::Fetch)?;
                    for (uri, bytes) in base_objects {
                        merged.insert(uri, bytes);
                    }
                    saw_base = true;
                }
            }
        }

        let mut saw_overlay = false;
        if let Some(delta_module) = self.delta_index.rsync_module(&module_uri) {
            for (uri, path) in &delta_module.overlay_files {
                let bytes = fs::read(path).map_err(|e| {
                    RsyncFetchError::Fetch(format!(
                        "read delta rsync overlay failed: {}: {e}",
                        path.display()
                    ))
                })?;
                merged.insert(uri.clone(), bytes);
                saw_overlay = true;
            }
        }

        if !saw_base && !saw_overlay {
            return Err(RsyncFetchError::Fetch(format!(
                "delta replay base rsync module not found and no delta overlay exists for module: {module_uri}"
            )));
        }

        let filtered: Vec<(String, Vec<u8>)> = merged
            .into_iter()
            .filter(|(uri, _)| uri.starts_with(&normalized_base))
            .collect();
        if filtered.is_empty() {
            return Err(RsyncFetchError::Fetch(format!(
                "delta replay rsync subtree not found: {normalized_base}"
            )));
        }
        Ok(filtered)
    }
}

impl RsyncFetcher for PayloadDeltaReplayCurrentStoreRsyncFetcher<'_> {
    fn fetch_objects(&self, rsync_base_uri: &str) -> RsyncFetchResult<Vec<(String, Vec<u8>)>> {
        let module_uri = canonical_rsync_module(rsync_base_uri)
            .map_err(|e| RsyncFetchError::Fetch(e.to_string()))?;
        let normalized_base = if rsync_base_uri.ends_with('/') {
            rsync_base_uri.to_string()
        } else {
            format!("{rsync_base_uri}/")
        };

        let mut merged: BTreeMap<String, Vec<u8>> = BTreeMap::new();
        let mut saw_base = false;
        let overlay_only = self
            .delta_index
            .rsync_module(&module_uri)
            .map(|module| module.overlay_only)
            .unwrap_or(false);

        if !overlay_only {
            let entries = self
                .store
                .list_repository_view_entries_with_prefix(&module_uri)
                .map_err(|e| RsyncFetchError::Fetch(format!("list repository view failed: {e}")))?;
            for entry in entries {
                if entry.state != RepositoryViewState::Present {
                    continue;
                }
                let bytes = self
                    .store
                    .load_current_object_bytes_by_uri(&entry.rsync_uri)
                    .map_err(|e| {
                        RsyncFetchError::Fetch(format!(
                            "load current object failed for {}: {e}",
                            entry.rsync_uri
                        ))
                    })?
                    .ok_or_else(|| {
                        RsyncFetchError::Fetch(format!(
                            "current object missing for {}",
                            entry.rsync_uri
                        ))
                    })?;
                merged.insert(entry.rsync_uri, bytes);
                saw_base = true;
            }
        }

        let mut saw_overlay = false;
        if let Some(delta_module) = self.delta_index.rsync_module(&module_uri) {
            for (uri, path) in &delta_module.overlay_files {
                let bytes = fs::read(path).map_err(|e| {
                    RsyncFetchError::Fetch(format!(
                        "read delta rsync overlay failed: {}: {e}",
                        path.display()
                    ))
                })?;
                merged.insert(uri.clone(), bytes);
                saw_overlay = true;
            }
        }

        if !saw_base && !saw_overlay {
            return Err(RsyncFetchError::Fetch(format!(
                "delta replay current-store base not found and no delta overlay exists for module: {module_uri}"
            )));
        }

        let filtered: Vec<(String, Vec<u8>)> = merged
            .into_iter()
            .filter(|(uri, _)| uri.starts_with(&normalized_base))
            .collect();
        if filtered.is_empty() {
            return Err(RsyncFetchError::Fetch(format!(
                "delta replay rsync subtree not found: {normalized_base}"
            )));
        }
        Ok(filtered)
    }
}

fn module_tree_root(module_uri: &str, tree_dir: &Path) -> Result<PathBuf, String> {
    let rest = module_uri
        .strip_prefix("rsync://")
        .ok_or_else(|| format!("invalid rsync module URI: {module_uri}"))?;
    let mut parts = rest.trim_end_matches('/').split('/');
    let authority = parts
        .next()
        .ok_or_else(|| format!("invalid rsync module URI: {module_uri}"))?;
    let module = parts
        .next()
        .ok_or_else(|| format!("invalid rsync module URI: {module_uri}"))?;
    Ok(tree_dir.join(authority).join(module))
}

fn walk_dir_collect(
    root: &Path,
    current: &Path,
    rsync_base_uri: &str,
    out: &mut Vec<(String, Vec<u8>)>,
) -> Result<(), String> {
    let rd = fs::read_dir(current).map_err(|e| e.to_string())?;
    for entry in rd {
        let entry = entry.map_err(|e| e.to_string())?;
        let path = entry.path();
        let meta = entry.metadata().map_err(|e| e.to_string())?;
        if meta.is_dir() {
            walk_dir_collect(root, &path, rsync_base_uri, out)?;
            continue;
        }
        if !meta.is_file() {
            continue;
        }
        let rel = path
            .strip_prefix(root)
            .map_err(|e| e.to_string())?
            .to_string_lossy()
            .replace('\\', "/");
        let base = if rsync_base_uri.ends_with('/') {
            rsync_base_uri.to_string()
        } else {
            format!("{rsync_base_uri}/")
        };
        let uri = format!("{base}{rel}");
        let bytes = fs::read(&path).map_err(|e| e.to_string())?;
        out.push((uri, bytes));
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::replay::archive::ReplayArchiveIndex;
    use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
    use crate::storage::{RawByHashEntry, RepositoryViewEntry, RepositoryViewState, RocksStore};

    fn build_base_and_delta_rsync_fixture()
    -> (tempfile::TempDir, PathBuf, PathBuf, PathBuf, PathBuf) {
        let temp = tempfile::tempdir().expect("tempdir");
        let base_archive = temp.path().join("payload-archive");
        let base_capture_root = base_archive.join("v1/captures/base-cap");
        std::fs::create_dir_all(&base_capture_root).expect("mkdir base capture");
        std::fs::write(
            base_capture_root.join("capture.json"),
            r#"{"version":1,"captureId":"base-cap","createdAt":"2026-03-16T00:00:00Z","notes":""}"#,
        )
        .expect("write base capture meta");
        let module_uri = "rsync://rsync.example.test/repo/";
        let module_hash = crate::replay::archive::sha256_hex(module_uri.as_bytes());
        let base_bucket = base_capture_root.join("rsync/modules").join(&module_hash);
        let base_tree = base_bucket.join("tree/rsync.example.test/repo");
        std::fs::create_dir_all(base_tree.join("sub")).expect("mkdir base tree");
        std::fs::write(base_bucket.join("meta.json"), format!(r#"{{"version":1,"module":"{module_uri}","createdAt":"2026-03-16T00:00:00Z","lastSeenAt":"2026-03-16T00:00:01Z"}}"#)).expect("write base meta");
        std::fs::write(base_tree.join("a.roa"), b"base-a").expect("write base a");
        std::fs::write(base_tree.join("sub").join("b.cer"), b"base-b").expect("write base b");
        let base_locks = temp.path().join("base-locks.json");
        std::fs::write(&base_locks, format!(r#"{{"version":1,"capture":"base-cap","rrdp":{{}},"rsync":{{"{module_uri}":{{"transport":"rsync"}}}}}}"#)).expect("write base locks");

        let delta_archive = temp.path().join("payload-delta-archive");
        let delta_capture_root = delta_archive.join("v1/captures/delta-cap");
        std::fs::create_dir_all(&delta_capture_root).expect("mkdir delta capture");
        std::fs::write(delta_capture_root.join("capture.json"), r#"{"version":1,"captureId":"delta-cap","createdAt":"2026-03-16T00:00:00Z","notes":""}"#).expect("write delta capture meta");
        std::fs::write(delta_capture_root.join("base.json"), r#"{"version":1,"baseCapture":"base-cap","baseLocksSha256":"deadbeef","createdAt":"2026-03-16T00:00:00Z"}"#).expect("write delta base meta");
        let delta_bucket = delta_capture_root.join("rsync/modules").join(&module_hash);
        let delta_tree = delta_bucket.join("tree/rsync.example.test/repo");
        std::fs::create_dir_all(delta_tree.join("sub")).expect("mkdir delta tree");
        std::fs::write(delta_bucket.join("meta.json"), format!(r#"{{"version":1,"module":"{module_uri}","createdAt":"2026-03-16T00:00:00Z","lastSeenAt":"2026-03-16T00:00:01Z"}}"#)).expect("write delta meta");
        std::fs::write(delta_bucket.join("files.json"), format!(r#"{{"version":1,"module":"{module_uri}","fileCount":1,"files":["{module_uri}sub/b.cer"]}}"#)).expect("write files json");
        std::fs::write(delta_tree.join("sub").join("b.cer"), b"delta-b")
            .expect("write delta overlay");
        let delta_locks = temp.path().join("locks-delta.json");
        std::fs::write(&delta_locks, format!(r#"{{"version":1,"capture":"delta-cap","baseCapture":"base-cap","baseLocksSha256":"deadbeef","rrdp":{{}},"rsync":{{"{module_uri}":{{"file_count":1,"overlay_only":false}}}}}}"#)).expect("write delta locks");
        (temp, base_archive, base_locks, delta_archive, delta_locks)
    }

    #[test]
    fn delta_rsync_current_store_fetcher_merges_current_store_and_overlay() {
        let (_temp, _base_archive, _base_locks, delta_archive, delta_locks) =
            build_base_and_delta_rsync_fixture();
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        store
            .put_raw_by_hash_entry(&RawByHashEntry {
                sha256_hex: crate::replay::archive::sha256_hex(b"base-a"),
                bytes: b"base-a".to_vec(),
                origin_uris: vec!["rsync://rsync.example.test/repo/a.roa".to_string()],
                object_type: Some("roa".to_string()),
                encoding: None,
            })
            .expect("put raw by hash");
        store
            .put_repository_view_entry(&RepositoryViewEntry {
                rsync_uri: "rsync://rsync.example.test/repo/a.roa".to_string(),
                current_hash: Some(crate::replay::archive::sha256_hex(b"base-a")),
                repository_source: Some("rsync".to_string()),
                object_type: Some("roa".to_string()),
                state: RepositoryViewState::Present,
            })
            .expect("put repository view");

        let delta = Arc::new(
            ReplayDeltaArchiveIndex::load(&delta_archive, &delta_locks).expect("load delta index"),
        );
        let fetcher = PayloadDeltaReplayCurrentStoreRsyncFetcher::new(&store, delta);
        let mut objects = fetcher
            .fetch_objects("rsync://rsync.example.test/repo/")
            .expect("fetch current-store objects");
        objects.sort_by(|a, b| a.0.cmp(&b.0));
        assert_eq!(objects.len(), 2);
        assert_eq!(objects[0].0, "rsync://rsync.example.test/repo/a.roa");
        assert_eq!(objects[0].1, b"base-a");
        assert_eq!(objects[1].0, "rsync://rsync.example.test/repo/sub/b.cer");
        assert_eq!(objects[1].1, b"delta-b");
    }

    #[test]
    fn delta_rsync_fetcher_uses_base_only_when_delta_has_no_module_entry() {
        let (_temp, base_archive, base_locks, delta_archive, delta_locks) =
            build_base_and_delta_rsync_fixture();
        std::fs::write(
            &delta_locks,
            r#"{"version":1,"capture":"delta-cap","baseCapture":"base-cap","baseLocksSha256":"deadbeef","rrdp":{},"rsync":{}}"#,
        )
        .expect("rewrite delta locks no rsync");
        let base = Arc::new(
            ReplayArchiveIndex::load(&base_archive, &base_locks).expect("load base index"),
        );
        let delta = Arc::new(
            ReplayDeltaArchiveIndex::load(&delta_archive, &delta_locks).expect("load delta index"),
        );
        let fetcher = PayloadDeltaReplayRsyncFetcher::new(base, delta);
        let mut objects = fetcher
            .fetch_objects("rsync://rsync.example.test/repo/")
            .expect("fetch base only objects");
        objects.sort_by(|a, b| a.0.cmp(&b.0));
        assert_eq!(objects.len(), 2);
        assert_eq!(objects[0].1, b"base-a");
        assert_eq!(objects[1].1, b"base-b");
    }

    #[test]
    fn delta_rsync_fetcher_reports_missing_base_subtree_and_exposes_indexes() {
        let (_temp, base_archive, base_locks, delta_archive, delta_locks) =
            build_base_and_delta_rsync_fixture();
        let base = Arc::new(
            ReplayArchiveIndex::load(&base_archive, &base_locks).expect("load base index"),
        );
        let delta = Arc::new(
            ReplayDeltaArchiveIndex::load(&delta_archive, &delta_locks).expect("load delta index"),
        );
        let fetcher = PayloadDeltaReplayRsyncFetcher::new(base.clone(), delta.clone());
        assert_eq!(fetcher.base_index().rsync_modules.len(), 1);
        assert_eq!(fetcher.delta_index().rsync_modules.len(), 1);
        let err = fetcher
            .fetch_objects("rsync://rsync.example.test/repo/missing/")
            .unwrap_err();
        match err {
            RsyncFetchError::Fetch(msg) => {
                assert!(
                    msg.contains("delta replay rsync subtree not found"),
                    "{msg}"
                )
            }
        }
    }

    #[test]
    fn delta_rsync_fetcher_can_use_overlay_without_base_module() {
        let (_temp, base_archive, base_locks, delta_archive, delta_locks) =
            build_base_and_delta_rsync_fixture();
        let module_hash =
            crate::replay::archive::sha256_hex("rsync://rsync.example.test/repo/".as_bytes());
        std::fs::remove_dir_all(
            base_archive
                .join("v1/captures/base-cap/rsync/modules")
                .join(&module_hash),
        )
        .expect("remove base module dir");
        let base = Arc::new(
            ReplayArchiveIndex::load_allow_missing_rsync_modules(&base_archive, &base_locks)
                .expect("load lenient base index"),
        );
        let delta = Arc::new(
            ReplayDeltaArchiveIndex::load(&delta_archive, &delta_locks).expect("load delta index"),
        );
        let fetcher = PayloadDeltaReplayRsyncFetcher::new(base, delta);
        let objects = fetcher
            .fetch_objects("rsync://rsync.example.test/repo/sub/")
            .expect("fetch subtree from overlay only");
        assert_eq!(objects.len(), 1);
        assert_eq!(objects[0].0, "rsync://rsync.example.test/repo/sub/b.cer");
        assert_eq!(objects[0].1, b"delta-b");
    }

    #[test]
    fn delta_rsync_fetcher_merges_base_and_overlay() {
        let (_temp, base_archive, base_locks, delta_archive, delta_locks) =
            build_base_and_delta_rsync_fixture();
        let base = Arc::new(
            ReplayArchiveIndex::load(&base_archive, &base_locks).expect("load base index"),
        );
        let delta = Arc::new(
            ReplayDeltaArchiveIndex::load(&delta_archive, &delta_locks).expect("load delta index"),
        );
        let fetcher = PayloadDeltaReplayRsyncFetcher::new(base, delta);
        let mut objects = fetcher
            .fetch_objects("rsync://rsync.example.test/repo/")
            .expect("fetch merged objects");
        objects.sort_by(|a, b| a.0.cmp(&b.0));
        assert_eq!(objects.len(), 2);
        assert_eq!(objects[0].0, "rsync://rsync.example.test/repo/a.roa");
        assert_eq!(objects[0].1, b"base-a");
        assert_eq!(objects[1].0, "rsync://rsync.example.test/repo/sub/b.cer");
        assert_eq!(objects[1].1, b"delta-b");
    }

    #[test]
    fn delta_rsync_fetcher_reads_subtree_and_rejects_missing_base_module() {
        let (_temp, base_archive, base_locks, delta_archive, delta_locks) =
            build_base_and_delta_rsync_fixture();
        let base = Arc::new(
            ReplayArchiveIndex::load(&base_archive, &base_locks).expect("load base index"),
        );
        let delta = Arc::new(
            ReplayDeltaArchiveIndex::load(&delta_archive, &delta_locks).expect("load delta index"),
        );
        let fetcher = PayloadDeltaReplayRsyncFetcher::new(base, delta);
        let objects = fetcher
            .fetch_objects("rsync://rsync.example.test/repo/sub/")
            .expect("fetch subtree");
        assert_eq!(objects.len(), 1);
        assert_eq!(objects[0].0, "rsync://rsync.example.test/repo/sub/b.cer");
        assert_eq!(objects[0].1, b"delta-b");

        let err = fetcher
            .fetch_objects("rsync://missing.example/repo/")
            .unwrap_err();
        match err {
            RsyncFetchError::Fetch(msg) => assert!(
                msg.contains("delta replay base rsync module not found")
                    || msg.contains("no replay lock found for rsync module"),
                "{msg}"
            ),
        }
    }
}
