//! Tests for staged-only scanning (--staged flag).

use std::fs;
use std::process::Command;

use tempfile::TempDir;

use crate::walker::{find_repo_root, get_staged_files, walk_staged_files};
use crate::{run_scan, AphoriaConfig, FileSource, ScanArgs, ScanMode};

/// Helper to initialize a git repository in a temp directory.
fn init_git_repo(dir: &std::path::Path) {
    Command::new("git").args(["init"]).current_dir(dir).output().expect("git init should succeed");

    // Configure git user for commits
    Command::new("git")
        .args(["config", "user.email", "test@example.com"])
        .current_dir(dir)
        .output()
        .expect("git config email");
    Command::new("git")
        .args(["config", "user.name", "Test User"])
        .current_dir(dir)
        .output()
        .expect("git config name");
}

/// Helper to stage a file in git.
fn stage_file(repo: &std::path::Path, file: &str) {
    Command::new("git")
        .args(["add", file])
        .current_dir(repo)
        .output()
        .expect("git add should succeed");
}

#[test]
fn test_staged_flag_requires_git_repo() {
    // Create a temp directory without git
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Create a source file
    fs::write(root.join("main.rs"), "fn main() {}").expect("write file");

    let config = AphoriaConfig::default();

    // walk_staged_files should fail with NotGitRepo
    let result = walk_staged_files(root, &config);
    assert!(result.is_err());

    let err = result.unwrap_err();
    assert!(err.to_string().contains("Not a git repository"));
}

#[test]
fn test_staged_flag_with_no_staged_files() {
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Initialize git repo
    init_git_repo(root);

    // Create files but don't stage them
    fs::write(root.join("main.rs"), "fn main() {}").expect("write file");
    fs::write(root.join("lib.rs"), "pub fn foo() {}").expect("write file");

    let config = AphoriaConfig::default();

    // walk_staged_files should return empty, not error
    let files = walk_staged_files(root, &config).expect("should succeed");
    assert!(files.is_empty(), "No staged files should mean empty result");
}

#[test]
fn test_staged_flag_scans_only_staged_files() {
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Initialize git repo
    init_git_repo(root);

    // Create multiple source files
    fs::write(root.join("main.rs"), "fn main() {}").expect("write main.rs");
    fs::write(root.join("lib.rs"), "pub fn foo() {}").expect("write lib.rs");
    fs::write(root.join("utils.rs"), "pub fn bar() {}").expect("write utils.rs");

    // Stage only one file
    stage_file(root, "main.rs");

    let config = AphoriaConfig::default();

    let files = walk_staged_files(root, &config).expect("should succeed");

    // Only main.rs should be included
    assert_eq!(files.len(), 1);
    assert_eq!(files[0].relative_path, "main.rs");
}

#[test]
fn test_staged_respects_scan_root() {
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Initialize git repo
    init_git_repo(root);

    // Create files in different directories
    let src_dir = root.join("src");
    let lib_dir = root.join("lib");
    fs::create_dir_all(&src_dir).expect("create src");
    fs::create_dir_all(&lib_dir).expect("create lib");

    fs::write(src_dir.join("main.rs"), "fn main() {}").expect("write src/main.rs");
    fs::write(lib_dir.join("lib.rs"), "pub fn foo() {}").expect("write lib/lib.rs");

    // Stage both files
    stage_file(root, "src/main.rs");
    stage_file(root, "lib/lib.rs");

    let config = AphoriaConfig::default();

    // Scan only the src directory
    let files = walk_staged_files(&src_dir, &config).expect("should succeed");

    // Only src/main.rs should be included
    assert_eq!(files.len(), 1);
    assert_eq!(files[0].relative_path, "main.rs");
}

#[test]
fn test_staged_applies_filters() {
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Initialize git repo
    init_git_repo(root);

    // Create both regular and test files
    fs::write(root.join("main.rs"), "fn main() {}").expect("write main.rs");
    fs::write(root.join("main_test.rs"), "#[test] fn test_main() {}").expect("write test");

    // Stage both
    stage_file(root, "main.rs");
    stage_file(root, "main_test.rs");

    // Config with test exclusion
    let mut config = AphoriaConfig::default();
    config.scan.include_tests = false;

    let files = walk_staged_files(root, &config).expect("should succeed");

    // Only main.rs should be included (test file excluded)
    assert_eq!(files.len(), 1);
    assert_eq!(files[0].relative_path, "main.rs");
}

#[test]
fn test_staged_excludes_deleted_files() {
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Initialize git repo
    init_git_repo(root);

    // Create and commit a file first
    fs::write(root.join("to_delete.rs"), "fn delete_me() {}").expect("write file");
    stage_file(root, "to_delete.rs");
    Command::new("git")
        .args(["commit", "-m", "initial"])
        .current_dir(root)
        .output()
        .expect("git commit");

    // Create a new file
    fs::write(root.join("main.rs"), "fn main() {}").expect("write main.rs");
    stage_file(root, "main.rs");

    // Delete the file and stage the deletion
    fs::remove_file(root.join("to_delete.rs")).expect("delete file");
    Command::new("git")
        .args(["add", "to_delete.rs"])
        .current_dir(root)
        .output()
        .expect("stage deletion");

    let config = AphoriaConfig::default();
    let files = walk_staged_files(root, &config).expect("should succeed");

    // Only main.rs should be included (deleted file excluded by diff-filter)
    assert_eq!(files.len(), 1);
    assert_eq!(files[0].relative_path, "main.rs");
}

#[tokio::test]
async fn test_staged_with_persist_and_sync() {
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Initialize git repo
    init_git_repo(root);

    // Create a file with a TLS issue
    fs::write(
        root.join("client.rs"),
        r#"
        let client = reqwest::Client::builder()
            .danger_accept_invalid_certs(true)
            .build()?;
        "#,
    )
    .expect("write client.rs");

    // Stage it
    stage_file(root, "client.rs");

    // Create Cargo.toml
    fs::write(
        root.join("Cargo.toml"),
        r#"
        [package]
        name = "test-staged"
        version = "0.1.0"
        "#,
    )
    .expect("write Cargo.toml");
    stage_file(root, "Cargo.toml");

    let mut config = AphoriaConfig::default();
    config.episteme.data_dir = root.join(".aphoria").join("db");

    let args = ScanArgs {
        path: root.to_path_buf(),
        format: "table".to_string(),
        exit_code_enabled: false,
        mode: ScanMode::Ephemeral,
        debug: false,
        sync: false,
        file_source: FileSource::Staged,
        benchmark: false,
        show_claims: false,
    };

    let result = run_scan(args, &config).await.expect("scan should succeed");

    // Should only scan the 2 staged files
    assert_eq!(result.files_scanned, 2);
}

#[test]
fn test_find_repo_root_from_nested_subdir() {
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Initialize git repo at root
    init_git_repo(root);

    // Create deeply nested directory
    let nested = root.join("src").join("lib").join("utils");
    fs::create_dir_all(&nested).expect("create nested dirs");

    // find_repo_root should find the repo from nested dir
    let found = find_repo_root(&nested).expect("find repo root");
    assert_eq!(found.canonicalize().unwrap(), root.canonicalize().unwrap());
}

#[test]
fn test_get_staged_files_returns_correct_paths() {
    let temp_dir = TempDir::new().expect("create temp dir");
    let root = temp_dir.path();

    // Initialize git repo
    init_git_repo(root);

    // Create files
    fs::write(root.join("main.rs"), "fn main() {}").expect("write main.rs");
    let src = root.join("src");
    fs::create_dir(&src).expect("create src");
    fs::write(src.join("lib.rs"), "pub fn foo() {}").expect("write src/lib.rs");

    // Stage both
    stage_file(root, "main.rs");
    stage_file(root, "src/lib.rs");

    let files = get_staged_files(root).expect("get staged files");

    assert_eq!(files.len(), 2);

    // Paths should be absolute
    let main_path = root.join("main.rs");
    let lib_path = src.join("lib.rs");

    assert!(files.contains(&main_path));
    assert!(files.contains(&lib_path));
}
