use super::*;
use std::fs;
use tempfile::TempDir;

// ========================================================================
// Test helpers -- reduce repeated struct construction across tests
// ========================================================================

/// Build a [`SymbolReference`] with sensible defaults, overriding only the
/// fields that vary between tests.
fn make_symbol_ref(symbol: &str, kind: SymbolKind, file: &str, line: usize) -> SymbolReference {
    SymbolReference {
        symbol: symbol.to_string(),
        kind,
        file: PathBuf::from(file),
        line,
        context: format!("{symbol} context"),
    }
}

/// Build a [`FileDependency`] from string slices.
fn make_dependency(from: &str, to: &str, kind: DependencyKind) -> FileDependency {
    FileDependency {
        from: PathBuf::from(from),
        to: PathBuf::from(to),
        kind,
    }
}

/// Build a [`DeadCode`] entry from the fields that actually vary.
fn make_dead_code(symbol: &str, kind: SymbolKind, file: &str, line: usize) -> DeadCode {
    DeadCode {
        symbol: symbol.to_string(),
        kind,
        file: PathBuf::from(file),
        line,
        reason: "No references found".to_string(),
    }
}

/// Helper: assert a value round-trips through serde_json.
fn assert_json_roundtrip<T: Serialize + serde::de::DeserializeOwned>(value: &T) -> T {
    let json = serde_json::to_string(value).unwrap();
    serde_json::from_str(&json).unwrap()
}

#[test]
fn test_analyzer_creation() {
    let analyzer = ParfAnalyzer::new();
    assert_eq!(analyzer.file_cache.len(), 0);
}

#[test]
fn test_function_name_extraction() {
    assert_eq!(
        ParfAnalyzer::extract_function_name("fn main() {"),
        Some("main".to_string())
    );
    assert_eq!(
        ParfAnalyzer::extract_function_name("pub fn test_function() -> Result<()> {"),
        Some("test_function".to_string())
    );
}

#[test]
fn test_type_name_extraction() {
    assert_eq!(
        ParfAnalyzer::extract_type_name("struct MyStruct {"),
        Some("MyStruct".to_string())
    );
    assert_eq!(
        ParfAnalyzer::extract_type_name("pub enum Status {"),
        Some("Status".to_string())
    );
}

#[test]
fn test_python_function_name_extraction() {
    assert_eq!(
        ParfAnalyzer::extract_python_function_name("def my_function():"),
        Some("my_function".to_string())
    );
    assert_eq!(
        ParfAnalyzer::extract_python_function_name("    def helper(arg1, arg2):"),
        Some("helper".to_string())
    );
}

#[test]
fn test_python_class_name_extraction() {
    assert_eq!(
        ParfAnalyzer::extract_python_class_name("class MyClass:"),
        Some("MyClass".to_string())
    );
    assert_eq!(
        ParfAnalyzer::extract_python_class_name("class MyClass(BaseClass):"),
        Some("MyClass".to_string())
    );
}

#[test]
fn test_index_codebase() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("test.rs");
    fs::write(&test_file, "fn hello() {}\nfn world() {}")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    assert_eq!(analyzer.file_cache.len(), 1);
    assert!(analyzer.symbol_definitions.contains_key("hello"));
    assert!(analyzer.symbol_definitions.contains_key("world"));

    Ok(())
}

#[test]
fn test_pattern_detection() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("test.rs");
    fs::write(&test_file, "// TODO: fix this\nlet x = y.unwrap();")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let patterns = analyzer.detect_patterns();
    assert!(patterns
        .iter()
        .any(|p| matches!(p, CodePattern::TechDebt { .. })));
    assert!(patterns
        .iter()
        .any(|p| matches!(p, CodePattern::ErrorHandling { .. })));

    Ok(())
}

// ============================================================================
// SYMBOL KIND TESTS
// ============================================================================

#[test]
fn test_symbol_kind_all_variants() {
    let variants = [
        SymbolKind::Function,
        SymbolKind::Class,
        SymbolKind::Variable,
        SymbolKind::Constant,
        SymbolKind::Module,
        SymbolKind::Import,
    ];
    assert_eq!(variants.len(), 6);
}

#[test]
fn test_symbol_kind_equality() {
    assert_eq!(SymbolKind::Function, SymbolKind::Function);
    assert_ne!(SymbolKind::Function, SymbolKind::Class);
}

#[test]
fn test_symbol_kind_serialization() {
    let kind = SymbolKind::Function;
    let deserialized = assert_json_roundtrip(&kind);
    assert_eq!(kind, deserialized);
}

// ============================================================================
// SYMBOL REFERENCE TESTS
// ============================================================================

#[test]
fn test_symbol_reference_construction() {
    let sym_ref = make_symbol_ref("test_func", SymbolKind::Function, "test.rs", 42);

    assert_eq!(sym_ref.symbol, "test_func");
    assert_eq!(sym_ref.kind, SymbolKind::Function);
    assert_eq!(sym_ref.line, 42);
}

#[test]
fn test_symbol_reference_serialization() {
    let sym_ref = make_symbol_ref("my_var", SymbolKind::Variable, "module.rs", 10);
    let deserialized = assert_json_roundtrip(&sym_ref);

    assert_eq!(sym_ref.symbol, deserialized.symbol);
    assert_eq!(sym_ref.kind, deserialized.kind);
    assert_eq!(sym_ref.line, deserialized.line);
}

// ============================================================================
// CODE PATTERN TESTS
// ============================================================================

#[test]
fn test_code_pattern_tech_debt() {
    let pattern = CodePattern::TechDebt {
        message: "// TODO: refactor".to_string(),
        file: PathBuf::from("main.rs"),
        line: 100,
    };

    match pattern {
        CodePattern::TechDebt { message, .. } => assert!(message.contains("TODO")),
        _ => panic!("Wrong variant"),
    }
}

#[test]
fn test_code_pattern_deprecated_api() {
    let pattern = CodePattern::DeprecatedApi {
        api: "old_function".to_string(),
        file: PathBuf::from("api.rs"),
        line: 50,
    };

    match pattern {
        CodePattern::DeprecatedApi { api, .. } => assert_eq!(api, "old_function"),
        _ => panic!("Wrong variant"),
    }
}

#[test]
fn test_code_pattern_error_handling() {
    let pattern = CodePattern::ErrorHandling {
        pattern: "unwrap()".to_string(),
        file: PathBuf::from("lib.rs"),
        line: 25,
    };

    match pattern {
        CodePattern::ErrorHandling { pattern, .. } => assert!(pattern.contains("unwrap")),
        _ => panic!("Wrong variant"),
    }
}

#[test]
fn test_code_pattern_resource_management() {
    let pattern = CodePattern::ResourceManagement {
        resource_type: "file".to_string(),
        file: PathBuf::from("io.rs"),
        line: 15,
    };

    match pattern {
        CodePattern::ResourceManagement { resource_type, .. } => {
            assert_eq!(resource_type, "file")
        }
        _ => panic!("Wrong variant"),
    }
}

#[test]
fn test_code_pattern_duplicate_code() {
    let pattern = CodePattern::DuplicateCode {
        pattern: "for i in 0..10".to_string(),
        occurrences: vec![(PathBuf::from("a.rs"), 10), (PathBuf::from("b.rs"), 20)],
    };

    match pattern {
        CodePattern::DuplicateCode { occurrences, .. } => assert_eq!(occurrences.len(), 2),
        _ => panic!("Wrong variant"),
    }
}

#[test]
fn test_code_pattern_serialization() {
    let pattern = CodePattern::TechDebt {
        message: "FIXME".to_string(),
        file: PathBuf::from("test.rs"),
        line: 1,
    };

    let deserialized = assert_json_roundtrip(&pattern);

    match deserialized {
        CodePattern::TechDebt { message, .. } => assert_eq!(message, "FIXME"),
        _ => panic!("Wrong variant"),
    }
}

// ============================================================================
// FILE DEPENDENCY TESTS
// ============================================================================

#[test]
fn test_file_dependency_construction() {
    let dep = make_dependency("main.rs", "module.rs", DependencyKind::Import);

    assert_eq!(dep.from, PathBuf::from("main.rs"));
    assert_eq!(dep.to, PathBuf::from("module.rs"));
    assert_eq!(dep.kind, DependencyKind::Import);
}

#[test]
fn test_dependency_kind_all_variants() {
    let kinds = [
        DependencyKind::Import,
        DependencyKind::Include,
        DependencyKind::Require,
        DependencyKind::ModuleUse,
    ];
    assert_eq!(kinds.len(), 4);
}

#[test]
fn test_dependency_kind_equality() {
    assert_eq!(DependencyKind::Import, DependencyKind::Import);
    assert_ne!(DependencyKind::Import, DependencyKind::Include);
}

#[test]
fn test_file_dependency_serialization() {
    let dep = make_dependency("a.rs", "b.rs", DependencyKind::ModuleUse);
    let deserialized = assert_json_roundtrip(&dep);

    assert_eq!(dep.from, deserialized.from);
    assert_eq!(dep.kind, deserialized.kind);
}

// ============================================================================
// DEAD CODE TESTS
// ============================================================================

#[test]
fn test_dead_code_construction() {
    let dead = make_dead_code("unused_func", SymbolKind::Function, "old.rs", 99);

    assert_eq!(dead.symbol, "unused_func");
    assert_eq!(dead.kind, SymbolKind::Function);
    assert_eq!(dead.line, 99);
}

#[test]
fn test_dead_code_serialization() {
    let dead = make_dead_code("dead", SymbolKind::Class, "lib.rs", 1);
    let deserialized = assert_json_roundtrip(&dead);

    assert_eq!(dead.symbol, deserialized.symbol);
    assert_eq!(dead.kind, deserialized.kind);
}

// ============================================================================
// PARF ANALYZER TESTS
// ============================================================================

#[test]
fn test_default_analyzer() {
    let analyzer = ParfAnalyzer::default();
    assert_eq!(analyzer.file_cache.len(), 0);
    assert_eq!(analyzer.symbol_definitions.len(), 0);
}

#[test]
fn test_find_references() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("test.rs");
    fs::write(
        &test_file,
        "fn main() {\n    println!(\"hello\");\n    hello();\n}\nfn hello() {}",
    )?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let refs = analyzer.find_references("hello", SymbolKind::Function);
    assert!(!refs.is_empty());
    assert!(refs.iter().any(|r| r.context.contains("hello")));

    Ok(())
}

#[test]
fn test_find_references_not_found() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("test.rs");
    fs::write(&test_file, "fn main() {}")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let refs = analyzer.find_references("nonexistent", SymbolKind::Function);
    assert!(refs.is_empty());

    Ok(())
}

#[test]
fn test_detect_patterns_fixme() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("code.rs");
    fs::write(&test_file, "// FIXME: broken code\nfn test() {}")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let patterns = analyzer.detect_patterns();
    assert!(patterns
        .iter()
        .any(|p| matches!(p, CodePattern::TechDebt { .. })));

    Ok(())
}

#[test]
fn test_detect_patterns_deprecated() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("api.rs");
    fs::write(&test_file, "#[deprecated]\nfn old_api() {}")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let patterns = analyzer.detect_patterns();
    assert!(patterns
        .iter()
        .any(|p| matches!(p, CodePattern::DeprecatedApi { .. })));

    Ok(())
}

#[test]
fn test_detect_patterns_file_handling() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("io.rs");
    fs::write(&test_file, "let f = File::open(\"test.txt\");")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let patterns = analyzer.detect_patterns();
    assert!(patterns
        .iter()
        .any(|p| matches!(p, CodePattern::ResourceManagement { .. })));

    Ok(())
}

#[test]
fn test_analyze_dependencies_rust() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("lib.rs");
    fs::write(&test_file, "use std::fs;\nuse serde::Serialize;")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let deps = analyzer.analyze_dependencies();
    assert!(!deps.is_empty());
    assert!(deps.iter().any(|d| d.kind == DependencyKind::ModuleUse));

    Ok(())
}

#[test]
fn test_analyze_dependencies_python() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("main.py");
    fs::write(
        &test_file,
        "import numpy as np\nfrom sklearn import linear_model",
    )?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let deps = analyzer.analyze_dependencies();
    assert!(!deps.is_empty());
    assert!(deps.iter().any(|d| d.kind == DependencyKind::Import));

    Ok(())
}

#[test]
fn test_find_dead_code_with_unused_function() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("dead.rs");
    fs::write(
        &test_file,
        "fn unused_func() {}\nfn main() {\n    // nothing\n}",
    )?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let dead = analyzer.find_dead_code();
    assert!(dead.is_empty() || dead.iter().any(|d| d.symbol == "unused_func"));

    Ok(())
}

#[test]
fn test_find_dead_code_skips_tests() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("tests.rs");
    fs::write(&test_file, "#[test]\nfn test_something() {}")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let dead = analyzer.find_dead_code();
    assert!(!dead.iter().any(|d| d.symbol == "test_something"));

    Ok(())
}

#[test]
fn test_find_dead_code_skips_main() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("main.rs");
    fs::write(&test_file, "fn main() {}")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let dead = analyzer.find_dead_code();
    assert!(!dead.iter().any(|d| d.symbol == "main"));

    Ok(())
}

#[test]
fn test_generate_report() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("test.rs");
    fs::write(&test_file, "fn test() {}\n// TODO: fix")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let report = analyzer.generate_report();
    assert!(report.contains("PARF Analysis Report"));
    assert!(report.contains("Files analyzed:"));
    assert!(report.contains("Symbols defined:"));
    assert!(report.contains("Patterns detected:"));

    Ok(())
}

#[test]
fn test_generate_report_with_dead_code() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("code.rs");
    fs::write(&test_file, "fn unused() {}\nfn main() {}")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    let report = analyzer.generate_report();
    assert!(report.contains("Potentially dead code:"));

    Ok(())
}

#[test]
fn test_index_python_file() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_file = temp_dir.path().join("test.py");
    fs::write(
        &test_file,
        "def my_function():\n    pass\n\nclass MyClass:\n    pass",
    )?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    assert!(analyzer.symbol_definitions.contains_key("my_function"));
    assert!(analyzer.symbol_definitions.contains_key("MyClass"));

    Ok(())
}

#[test]
fn test_index_multiple_files() -> Result<()> {
    let temp_dir = TempDir::new()?;
    fs::write(temp_dir.path().join("a.rs"), "fn func_a() {}")?;
    fs::write(temp_dir.path().join("b.rs"), "fn func_b() {}")?;
    fs::write(temp_dir.path().join("c.py"), "def func_c(): pass")?;

    let mut analyzer = ParfAnalyzer::new();
    analyzer.index_codebase(temp_dir.path())?;

    assert_eq!(analyzer.file_cache.len(), 3);
    assert!(analyzer.symbol_definitions.contains_key("func_a"));
    assert!(analyzer.symbol_definitions.contains_key("func_b"));
    assert!(analyzer.symbol_definitions.contains_key("func_c"));

    Ok(())
}

#[test]
fn test_extract_function_name_edge_cases() {
    assert_eq!(
        ParfAnalyzer::extract_function_name("pub async fn test() {"),
        Some("test".to_string())
    );
    assert_eq!(
        ParfAnalyzer::extract_function_name("no function here"),
        None
    );
    assert_eq!(
        ParfAnalyzer::extract_function_name("fn ()"),
        Some("".to_string())
    );
}

#[test]
fn test_extract_type_name_with_generics() {
    assert_eq!(
        ParfAnalyzer::extract_type_name("struct Vec<T> {"),
        Some("Vec".to_string())
    );
    assert_eq!(
        ParfAnalyzer::extract_type_name("enum Option<T>"),
        Some("Option".to_string())
    );
}

#[test]
fn test_extract_python_function_name_with_args() {
    assert_eq!(
        ParfAnalyzer::extract_python_function_name("def process(data, config):"),
        Some("process".to_string())
    );
}

#[test]
fn test_extract_python_class_name_with_inheritance() {
    assert_eq!(
        ParfAnalyzer::extract_python_class_name("class Child(Parent, Mixin):"),
        Some("Child".to_string())
    );
}
