// Index accessor tests (stats, get_by_name/file, corpus, project_root),
// generic callee/test_chunk filtering, call graph filtering,
// cross-project call graph, and PTX role classification/diagnostics

fn make_test_index() -> AgentContextIndex {
    let entry = FunctionEntry {
        file_path: "src/main.rs".to_string(),
        function_name: "main".to_string(),
        signature: "fn main()".to_string(),
        doc_comment: None,
        source: "fn main() { }".to_string(),
        start_line: 1,
        end_line: 1,
        language: "Rust".to_string(),
        quality: QualityMetrics {
            tdg_score: 1.0,
            tdg_grade: "A".to_string(),
            complexity: 3,
            cognitive_complexity: 2,
            big_o: "O(1)".to_string(),
            satd_count: 0,
            loc: 5,
            commit_count: 0,
            churn_score: 0.0,
            contract_level: None,
            contract_equation: None,
        },
        checksum: "test".to_string(),
        definition_type: DefinitionType::default(),
        commit_count: 0,
        churn_score: 0.0,
        clone_count: 0,
        pattern_diversity: 0.0,
        fault_annotations: Vec::new(), linked_definition: None,
    };
    let mut name_index = HashMap::new();
    name_index.insert("main".to_string(), vec![0usize]);
    let mut file_index = HashMap::new();
    file_index.insert("src/main.rs".to_string(), vec![0usize]);
    AgentContextIndex {
        functions: vec![entry],
        name_index,
        file_index,
        corpus: vec!["fn main".to_string()],
        corpus_lower: vec!["fn main".to_string()],
        name_frequency: HashMap::new(),
        calls: HashMap::new(),
        called_by: HashMap::new(),
        graph_metrics: vec![GraphMetrics {
            pagerank: 0.5,
            centrality: 0.3,
            in_degree: 2,
            out_degree: 1,
        }],
        project_root: PathBuf::from("/tmp/test"),
        manifest: IndexManifest {
            version: "1.3.0".to_string(),
            built_at: "test".to_string(),
            project_root: "/tmp/test".to_string(),
            function_count: 1,
            file_count: 1,
            languages: vec!["Rust".to_string()],
            avg_tdg_score: 1.0,
            file_checksums: HashMap::new(),
            last_incremental_changes: 0,
        },
        db_path: None,
        coverage_off_files: HashSet::new(),
    }
}

#[test]
fn test_stats_with_data() {
    let index = make_test_index();
    let stats = index.stats();
    assert!(stats.total_functions > 0);
    assert!(!stats.by_language.is_empty());
    assert!(!stats.by_grade.is_empty());
    assert!(stats.avg_complexity >= 0.0);
    assert!(stats.index_size_bytes > 0);
}

#[test]
fn test_stats_empty_index() {
    let index = AgentContextIndex {
        functions: vec![],
        name_index: HashMap::new(),
        file_index: HashMap::new(),
        corpus: vec![],
        corpus_lower: vec![],
        name_frequency: HashMap::new(),
        calls: HashMap::new(),
        called_by: HashMap::new(),
        graph_metrics: vec![],
        project_root: PathBuf::from("/tmp"),
        manifest: IndexManifest {
            version: "1.3.0".to_string(),
            built_at: "test".to_string(),
            project_root: "/tmp".to_string(),
            function_count: 0,
            file_count: 0,
            languages: vec![],
            avg_tdg_score: 0.0,
            file_checksums: HashMap::new(),
            last_incremental_changes: 0,
        },
        db_path: None,
        coverage_off_files: HashSet::new(),
    };
    let stats = index.stats();
    assert_eq!(stats.total_functions, 0);
    assert!((stats.avg_complexity - 0.0).abs() < 0.001);
}

#[test]
fn test_get_by_name() {
    let index = make_test_index();
    let funcs = index.get_by_name("main");
    // "main" exists in test fixture
    // "main" may or may not exist depending on test fixture
    let _ = funcs;
    let missing = index.get_by_name("nonexistent_function_xyz");
    assert!(missing.is_empty());
}

#[test]
fn test_get_by_file() {
    let index = make_test_index();
    let missing = index.get_by_file("nonexistent/path.rs");
    assert!(missing.is_empty());
}

#[test]
fn test_corpus_accessor() {
    let index = make_test_index();
    let corpus = index.corpus();
    assert_eq!(corpus.len(), index.all_functions().len());
}

#[test]
fn test_project_root_accessor() {
    let index = make_test_index();
    let root = index.project_root();
    assert!(!root.as_os_str().is_empty());
}

#[test]
fn test_is_generic_callee() {
    // Common method names should be excluded
    assert!(is_generic_callee("new"));
    assert!(is_generic_callee("from"));
    assert!(is_generic_callee("clone"));
    assert!(is_generic_callee("default"));
    assert!(is_generic_callee("unwrap"));
    assert!(is_generic_callee("push"));
    assert!(is_generic_callee("iter"));
    assert!(is_generic_callee("collect"));
    assert!(is_generic_callee("serialize"));
    assert!(is_generic_callee("build"));
    assert!(is_generic_callee("parse"));
    assert!(is_generic_callee("test"));
    // Domain-specific names should NOT be excluded
    assert!(!is_generic_callee("handle_error"));
    assert!(!is_generic_callee("process_request"));
    assert!(!is_generic_callee("calculate_tdg"));
    assert!(!is_generic_callee("dispatch_event"));
}

#[test]
fn test_is_test_chunk() {
    // Test files
    assert!(is_test_chunk("foo", "src/tests/mod.rs"));
    assert!(is_test_chunk("foo", "src/handler_test.rs"));
    assert!(is_test_chunk("foo", "src/handler_tests.rs"));
    // Test function names
    assert!(is_test_chunk("test_handle_error", "src/handler.rs"));
    assert!(is_test_chunk("test_parse", "src/lib.rs"));
    // C++ test files
    assert!(is_test_chunk("foo", "src/handler_test.cpp"));
    assert!(is_test_chunk("foo", "src/handler_test.cc"));
    assert!(is_test_chunk("bar", "src/handler_unittest.cpp"));
    assert!(is_test_chunk("baz", "tests/unit/test_attention.cpp"));
    // C++ gtest macros
    assert!(is_test_chunk("TEST_F", "src/handler.cpp"));
    assert!(is_test_chunk("TEST_P", "src/handler.cpp"));
    // JS/TS test files
    assert!(is_test_chunk("foo", "src/handler.test.ts"));
    assert!(is_test_chunk("foo", "src/handler.spec.js"));
    // Python/Go test files
    assert!(is_test_chunk("foo", "src/handler_test.py"));
    assert!(is_test_chunk("foo", "src/handler_test.go"));
    // Non-test code
    assert!(!is_test_chunk("handle_error", "src/handler.rs"));
    assert!(!is_test_chunk("build", "src/lib.rs"));
    assert!(!is_test_chunk("testing_utils", "src/utils.rs")); // "testing_" != "test_"
}

#[test]
fn test_call_graph_excludes_generic_names() {
    // Create functions where "new" appears in many sources
    let functions = vec![
        FunctionEntry {
            file_path: "a.rs".to_string(),
            function_name: "new".to_string(),
            signature: "fn new()".to_string(),
            doc_comment: None,
            source: "fn new() { }".to_string(),
            start_line: 1,
            end_line: 1,
            language: "Rust".to_string(),
            quality: QualityMetrics::default(),
            checksum: "a".to_string(),
            definition_type: DefinitionType::default(),
            commit_count: 0,
            churn_score: 0.0,
            clone_count: 0,
            pattern_diversity: 0.0,
            fault_annotations: Vec::new(), linked_definition: None,
        },
        FunctionEntry {
            file_path: "b.rs".to_string(),
            function_name: "process".to_string(),
            signature: "fn process()".to_string(),
            doc_comment: None,
            // Source mentions "new" but it's a generic callee — should be excluded
            source: "fn process() { let x = Foo::new(); }".to_string(),
            start_line: 1,
            end_line: 1,
            language: "Rust".to_string(),
            quality: QualityMetrics::default(),
            checksum: "b".to_string(),
            definition_type: DefinitionType::default(),
            commit_count: 0,
            churn_score: 0.0,
            clone_count: 0,
            pattern_diversity: 0.0,
            fault_annotations: Vec::new(), linked_definition: None,
        },
        FunctionEntry {
            file_path: "c.rs".to_string(),
            function_name: "dispatch_event".to_string(),
            signature: "fn dispatch_event()".to_string(),
            doc_comment: None,
            source: "fn dispatch_event() { process(); }".to_string(),
            start_line: 1,
            end_line: 1,
            language: "Rust".to_string(),
            quality: QualityMetrics::default(),
            checksum: "c".to_string(),
            definition_type: DefinitionType::default(),
            commit_count: 0,
            churn_score: 0.0,
            clone_count: 0,
            pattern_diversity: 0.0,
            fault_annotations: Vec::new(), linked_definition: None,
        },
    ];

    let indices = build_indices(&functions);
    let (calls, called_by) = build_call_graph(&functions, &indices.name_index);

    // "process" calling "new" should be EXCLUDED (generic callee)
    let process_calls = calls.get(&1).cloned().unwrap_or_default();
    assert!(
        !process_calls.contains(&0),
        "generic callee 'new' should be excluded from call graph"
    );

    // "dispatch_event" calling "process" should be INCLUDED (domain-specific)
    let dispatch_calls = calls.get(&2).cloned().unwrap_or_default();
    assert!(
        dispatch_calls.contains(&1),
        "domain-specific callee 'process' should be in call graph"
    );

    // "new" should have no callers (all filtered)
    assert!(
        !called_by.contains_key(&0),
        "'new' should have no callers in call graph"
    );
}

// ── Feature 1: Cross-Project Call Graph Tests ──────────────────────────────

#[test]
fn test_merge_fast_preserves_graph_metrics_length() {
    let temp_dir = tempfile::TempDir::new().unwrap();

    let proj_a = temp_dir.path().join("a");
    std::fs::create_dir_all(proj_a.join("src")).unwrap();
    std::fs::write(proj_a.join("src/lib.rs"), "fn alpha() {}\nfn beta() {}\n").unwrap();
    let mut index_a = AgentContextIndex::build(&proj_a).unwrap();

    let proj_b = temp_dir.path().join("b");
    std::fs::create_dir_all(proj_b.join("src")).unwrap();
    std::fs::write(proj_b.join("src/lib.rs"), "fn gamma() {}\n").unwrap();
    let index_b = AgentContextIndex::build(&proj_b).unwrap();

    let total = index_a.functions.len() + index_b.functions.len();
    index_a.merge_fast(index_b);

    // graph_metrics length must equal total functions after merge
    assert_eq!(index_a.graph_metrics.len(), total);
    assert_eq!(index_a.functions.len(), total);
}

#[test]
fn test_rebuild_cross_project_graph_creates_cross_edges() {
    let temp_dir = tempfile::TempDir::new().unwrap();

    // Project A: defines `shared_util`, calls nothing
    let proj_a = temp_dir.path().join("a");
    std::fs::create_dir_all(proj_a.join("src")).unwrap();
    std::fs::write(
        proj_a.join("src/lib.rs"),
        "pub fn shared_util() -> i32 { 42 }\n",
    )
    .unwrap();
    let mut index_a = AgentContextIndex::build(&proj_a).unwrap();

    // Project B: calls `shared_util`
    let proj_b = temp_dir.path().join("b");
    std::fs::create_dir_all(proj_b.join("src")).unwrap();
    std::fs::write(
        proj_b.join("src/lib.rs"),
        "fn consumer() -> i32 { shared_util() }\n",
    )
    .unwrap();

    // Load B with prefix to simulate workspace
    let index_b = AgentContextIndex::build(&proj_b).unwrap();
    index_a.merge_fast(index_b);

    // Before rebuild, call graph only has per-project edges
    // After rebuild, cross-project edges should exist
    index_a.rebuild_cross_project_graph();

    // Check that graph_metrics were recomputed (length matches)
    assert_eq!(index_a.graph_metrics.len(), index_a.functions.len());

    // Find consumer function and verify it has callees
    let consumer_idx = index_a
        .functions
        .iter()
        .position(|f| f.function_name == "consumer");
    assert!(consumer_idx.is_some(), "consumer function should exist");
    let ci = consumer_idx.unwrap();
    let callees = index_a.get_calls(ci);
    assert!(
        callees.contains(&"shared_util"),
        "consumer should call shared_util after rebuild"
    );
}

// ── Feature 3: Cross-Project Callers Count Test ────────────────────────────

#[test]
fn test_count_cross_project_callers() {
    let temp_dir = tempfile::TempDir::new().unwrap();

    let proj_a = temp_dir.path().join("a");
    std::fs::create_dir_all(proj_a.join("src")).unwrap();
    std::fs::write(
        proj_a.join("src/lib.rs"),
        "pub fn shared_util() -> i32 { 42 }\n",
    )
    .unwrap();
    let mut index = AgentContextIndex::build(&proj_a).unwrap();

    // Prefix project A paths
    for func in &mut index.functions {
        func.file_path = format!("proj_a/{}", func.file_path);
    }

    // Add project B functions that call shared_util
    let proj_b = temp_dir.path().join("b");
    std::fs::create_dir_all(proj_b.join("src")).unwrap();
    std::fs::write(
        proj_b.join("src/lib.rs"),
        "fn caller_b() -> i32 { shared_util() }\n",
    )
    .unwrap();
    let mut index_b = AgentContextIndex::build(&proj_b).unwrap();
    for func in &mut index_b.functions {
        func.file_path = format!("proj_b/{}", func.file_path);
    }

    index.merge_fast(index_b);
    index.rebuild_cross_project_graph();

    // Find shared_util
    let shared_idx = index
        .functions
        .iter()
        .position(|f| f.function_name == "shared_util")
        .unwrap();
    let xp_callers = index.count_cross_project_callers(shared_idx);
    // caller_b from proj_b calls shared_util in proj_a — that's a cross-project caller
    assert!(
        xp_callers > 0,
        "shared_util should have cross-project callers, got {}",
        xp_callers
    );
}

#[test]
fn test_count_cross_project_callers_out_of_bounds() {
    let temp_dir = tempfile::TempDir::new().unwrap();
    let proj = temp_dir.path().join("p");
    std::fs::create_dir_all(proj.join("src")).unwrap();
    std::fs::write(proj.join("src/lib.rs"), "fn foo() {}\n").unwrap();
    let index = AgentContextIndex::build(&proj).unwrap();
    // Out-of-bounds should return 0, not panic
    assert_eq!(index.count_cross_project_callers(999), 0);
}

// ── Feature 2/5: PTX Role Classification Tests ─────────────────────────────

#[test]
fn test_classify_ptx_role_emitter() {
    use crate::services::agent_context::query::ptx_flow::{classify_ptx_role, PtxRole};
    assert_eq!(
        classify_ptx_role("asm!(\"nop\")", "src/kernel.rs"),
        Some(PtxRole::Emitter)
    );
    assert_eq!(
        classify_ptx_role("fn foo() {}", "kernel.cu"),
        Some(PtxRole::Emitter)
    );
    assert_eq!(
        classify_ptx_role(".version 7.0\n.target sm_86", "kernel.ptx"),
        Some(PtxRole::Emitter)
    );
}

#[test]
fn test_classify_ptx_role_loader() {
    use crate::services::agent_context::query::ptx_flow::{classify_ptx_role, PtxRole};
    assert_eq!(
        classify_ptx_role("cuModuleLoad(&module)", "src/gpu.rs"),
        Some(PtxRole::Loader)
    );
    assert_eq!(
        classify_ptx_role("load_ptx(data)", "src/compute.rs"),
        Some(PtxRole::Loader)
    );
}

#[test]
fn test_classify_ptx_role_analyzer() {
    use crate::services::agent_context::query::ptx_flow::{classify_ptx_role, PtxRole};
    assert_eq!(
        classify_ptx_role("let rp = register_pressure(ptx)", "src/diag.rs"),
        Some(PtxRole::Analyzer)
    );
    assert_eq!(
        classify_ptx_role("detect_ptx_barrier(src)", "src/comply.rs"),
        Some(PtxRole::Analyzer)
    );
}

#[test]
fn test_classify_ptx_role_none() {
    use crate::services::agent_context::query::ptx_flow::classify_ptx_role;
    assert_eq!(
        classify_ptx_role("fn add(a: i32, b: i32) -> i32 { a + b }", "src/math.rs"),
        None
    );
}

// ── Feature 5: PTX Diagnostics Counter Tests ───────────────────────────────

#[test]
fn test_ptx_diagnostics_register_and_branch_counting() {
    use crate::services::agent_context::query::ptx_diagnostics::run_ptx_diagnostics;

    let temp_dir = tempfile::TempDir::new().unwrap();
    let proj = temp_dir.path().join("p");
    std::fs::create_dir_all(proj.join("src")).unwrap();
    std::fs::write(
        proj.join("src/kernel.rs"),
        r#"fn detect_ptx_barrier_test() {
    // This function references PTX analysis keywords
    let barriers = barrier_divergence_check();
    let shared = shared_memory_size();
}
"#,
    )
    .unwrap();

    let index = AgentContextIndex::build(&proj).unwrap();
    let result = run_ptx_diagnostics(&index);
    // The function references ptx keywords so should be found
    // (may or may not have diagnostics depending on content)
    let _ = &result;
}
