use anyhow::Result;

use astmap_core::diff::{diff_against_base, diff_working_tree, DiffResult};
use astmap_core::port::VcsProvider;
use astmap_core::{DiffStore, LanguageRegistry};

pub fn diff(
    db: &dyn DiffStore,
    vcs: &dyn VcsProvider,
    lang: &dyn LanguageRegistry,
    base: Option<&str>,
    depth: i64,
) -> Result<DiffResult> {
    let ext = lang.supported_extensions();
    let result = if let Some(base_ref) = base {
        diff_against_base(vcs, base_ref, db, depth, ext)?
    } else {
        diff_working_tree(vcs, db, depth, ext)?
    };
    Ok(result)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::infra::discovery::IgnoreWalkDiscovery;
    use crate::infra::git::Git2VcsProvider;
    use astmap_languages::DefaultLanguageRegistry;
    use std::fs;
    use std::process::Command;

    fn setup_git_project() -> tempfile::TempDir {
        let dir = tempfile::tempdir().unwrap();
        for (args, _) in [
            (vec!["init"], ""),
            (vec!["config", "user.email", "test@test.com"], ""),
            (vec!["config", "user.name", "Test"], ""),
        ] {
            Command::new("git")
                .args(&args)
                .current_dir(dir.path())
                .output()
                .unwrap();
        }

        let src = dir.path().join("src");
        fs::create_dir_all(&src).unwrap();
        fs::write(src.join("lib.rs"), "pub fn hello() {}\n").unwrap();

        Command::new("git")
            .args(["add", "."])
            .current_dir(dir.path())
            .output()
            .unwrap();
        Command::new("git")
            .args(["commit", "-m", "initial"])
            .current_dir(dir.path())
            .output()
            .unwrap();

        // Scan
        let db = crate::infra::sqlite::open(dir.path()).unwrap();
        astmap_core::scanner::scan(
            dir.path(),
            db.as_ref(),
            true,
            &DefaultLanguageRegistry,
            &IgnoreWalkDiscovery,
        )
        .unwrap();
        dir
    }

    #[test]
    fn working_tree_no_changes() {
        let dir = setup_git_project();
        let db = crate::infra::sqlite::open(dir.path()).unwrap();
        let vcs = Git2VcsProvider::open(dir.path()).unwrap();
        let lang = DefaultLanguageRegistry;

        let result = diff(db.as_ref(), &vcs, &lang, None, 3).unwrap();
        assert!(result.changed_files.is_empty());
    }

    #[test]
    fn working_tree_with_changes() {
        let dir = setup_git_project();
        fs::write(
            dir.path().join("src/lib.rs"),
            "pub fn hello() {}\npub fn added() {}\n",
        )
        .unwrap();

        let db = crate::infra::sqlite::open(dir.path()).unwrap();
        let vcs = Git2VcsProvider::open(dir.path()).unwrap();
        let lang = DefaultLanguageRegistry;

        let result = diff(db.as_ref(), &vcs, &lang, None, 3).unwrap();
        assert!(!result.changed_files.is_empty());
    }

    #[test]
    fn against_base_ref() {
        let dir = setup_git_project();
        fs::write(
            dir.path().join("src/lib.rs"),
            "pub fn hello() {}\npub fn world() {}\n",
        )
        .unwrap();
        Command::new("git")
            .args(["add", "."])
            .current_dir(dir.path())
            .output()
            .unwrap();
        Command::new("git")
            .args(["commit", "-m", "add world"])
            .current_dir(dir.path())
            .output()
            .unwrap();

        // Re-scan
        let db = crate::infra::sqlite::open(dir.path()).unwrap();
        astmap_core::scanner::scan(
            dir.path(),
            db.as_ref(),
            true,
            &DefaultLanguageRegistry,
            &IgnoreWalkDiscovery,
        )
        .unwrap();

        let vcs = Git2VcsProvider::open(dir.path()).unwrap();
        let lang = DefaultLanguageRegistry;

        let result = diff(db.as_ref(), &vcs, &lang, Some("HEAD~1"), 3).unwrap();
        assert!(!result.changed_files.is_empty());
    }
}
