//! End-to-end CLI integration test.
//!
//! Creates a real git repo, commits base+head files, runs `diffsift classify`
//! as a subprocess, and asserts on the JSON output.

use std::process::Command;

fn run_git(dir: &std::path::Path, args: &[&str]) {
    let status = Command::new("git")
        .args(args)
        .current_dir(dir)
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()
        .expect("run git");
    assert!(status.success(), "git {:?} failed", args);
}

fn git_output(dir: &std::path::Path, args: &[&str]) -> String {
    let output = Command::new("git")
        .args(args)
        .current_dir(dir)
        .output()
        .expect("run git");
    String::from_utf8(output.stdout).unwrap().trim().to_string()
}

fn setup_repo(
    base_files: &[(&str, &str)],
    head_files: &[(&str, &str)],
) -> (tempfile::TempDir, String, String) {
    let dir = tempfile::tempdir().expect("create tempdir");
    let repo = dir.path();
    run_git(repo, &["init"]);
    run_git(repo, &["config", "user.name", "test"]);
    run_git(repo, &["config", "user.email", "test@test.com"]);

    for (path, content) in base_files {
        let full = repo.join(path);
        if let Some(p) = full.parent() {
            std::fs::create_dir_all(p).ok();
        }
        std::fs::write(&full, content).unwrap();
    }
    run_git(repo, &["add", "-A"]);
    run_git(repo, &["commit", "-m", "base"]);
    let base = git_output(repo, &["rev-parse", "HEAD"]);

    for (path, content) in head_files {
        let full = repo.join(path);
        if let Some(p) = full.parent() {
            std::fs::create_dir_all(p).ok();
        }
        std::fs::write(&full, content).unwrap();
    }
    run_git(repo, &["add", "-A"]);
    run_git(repo, &["commit", "-m", "head"]);
    let head = git_output(repo, &["rev-parse", "HEAD"]);

    (dir, base, head)
}

/// Find the diffsift binary in target/debug or target/release.
fn diffsift_bin() -> std::path::PathBuf {
    let candidates = [
        std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .unwrap()
            .parent()
            .unwrap()
            .join("target/debug/diffsift"),
        std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .unwrap()
            .parent()
            .unwrap()
            .join("target/release/diffsift"),
    ];
    for c in &candidates {
        if c.exists() {
            return c.clone();
        }
    }
    panic!(
        "diffsift binary not found — run `cargo build` first. Checked: {:?}",
        candidates
    );
}

#[test]
fn test_classify_json_output() {
    let base_ts = r#"export function isOldEnough(age: number): boolean {
    return age >= 18;
}
"#;
    let head_ts = r#"export function isOldEnough(age: number): boolean {
    // Bug: off-by-one, 18-year-olds now excluded
    return age > 18;
}
"#;

    let (dir, base_sha, head_sha) =
        setup_repo(&[("src/auth.ts", base_ts)], &[("src/auth.ts", head_ts)]);

    let output = Command::new(diffsift_bin())
        .args([
            "classify",
            "--repo",
            dir.path().to_str().unwrap(),
            "--base",
            &base_sha,
            "--head",
            &head_sha,
            "--format",
            "json",
        ])
        .output()
        .expect("run diffsift classify");

    assert!(
        output.status.success(),
        "diffsift classify failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8(output.stdout).unwrap();
    let json: serde_json::Value = serde_json::from_str(&stdout)
        .unwrap_or_else(|e| panic!("invalid JSON output: {e}\nstdout: {stdout}"));

    // Should have classified files
    let files = json["files"].as_array().expect("files should be an array");
    assert!(!files.is_empty(), "should have at least one file");

    // Find the auth.ts file
    let auth_file = files
        .iter()
        .find(|f| f["path"].as_str().unwrap_or("").contains("auth.ts"))
        .expect("should have auth.ts in output");

    // Should have hunks
    let hunks = auth_file["hunks"]
        .as_array()
        .expect("hunks should be array");
    assert!(!hunks.is_empty(), "should have at least one hunk");
}

#[test]
fn test_classify_summary_json_output() {
    let base_ts = "export function greet(name: string): string {\n    return `Hello ${name}`;\n}\n";
    let head_ts = "export function greet(name: string): string {\n    return `Hi ${name}`;\n}\n";

    let (dir, base_sha, head_sha) =
        setup_repo(&[("src/greet.ts", base_ts)], &[("src/greet.ts", head_ts)]);

    let output = Command::new(diffsift_bin())
        .args([
            "classify",
            "--repo",
            dir.path().to_str().unwrap(),
            "--base",
            &base_sha,
            "--head",
            &head_sha,
            "--format",
            "summary-json",
        ])
        .output()
        .expect("run diffsift classify");

    assert!(
        output.status.success(),
        "summary-json failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8(output.stdout).unwrap();
    let json: serde_json::Value = serde_json::from_str(&stdout)
        .unwrap_or_else(|e| panic!("invalid summary-json: {e}\n{stdout}"));

    assert!(json["stats"].is_object(), "should have stats");
    assert!(json["focus"].is_array(), "should have focus array");
}

#[test]
fn test_classify_sarif_output() {
    let base_ts = "export function check(x: number): boolean {\n    return x >= 10;\n}\n";
    let head_ts =
        "export function check(x: number): boolean {\n    // bug\n    return x > 10;\n}\n";

    let (dir, base_sha, head_sha) =
        setup_repo(&[("src/check.ts", base_ts)], &[("src/check.ts", head_ts)]);

    let output = Command::new(diffsift_bin())
        .args([
            "classify",
            "--repo",
            dir.path().to_str().unwrap(),
            "--base",
            &base_sha,
            "--head",
            &head_sha,
            "--format",
            "sarif",
        ])
        .output()
        .expect("run diffsift classify");

    assert!(
        output.status.success(),
        "sarif failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8(output.stdout).unwrap();
    let json: serde_json::Value =
        serde_json::from_str(&stdout).unwrap_or_else(|e| panic!("invalid SARIF: {e}\n{stdout}"));

    assert_eq!(
        json["$schema"].as_str(),
        Some("https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json")
    );
    assert_eq!(json["version"].as_str(), Some("2.1.0"));
    let runs = json["runs"].as_array().expect("runs array");
    assert!(!runs.is_empty());
}

#[test]
fn test_init_creates_config() {
    let dir = tempfile::tempdir().expect("create tempdir");
    run_git(dir.path(), &["init"]);
    run_git(dir.path(), &["config", "user.name", "test"]);
    run_git(dir.path(), &["config", "user.email", "test@test.com"]);

    let output = Command::new(diffsift_bin())
        .args(["init", "--path", dir.path().to_str().unwrap()])
        .output()
        .expect("run diffsift init");

    assert!(
        output.status.success(),
        "init failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let config_path = dir.path().join(".diffsift").join("config.toml");
    assert!(
        config_path.exists(),
        ".diffsift/config.toml should be created"
    );

    let content = std::fs::read_to_string(&config_path).expect("read config");
    assert!(content.contains("[llm]"), "should have [llm] section");
}
