//! Coverage tests for similarity handler
//! Extracted for file health compliance (CB-040)

use super::super::*;
use crate::cli::{DuplicateOutputFormat, DuplicateType};
use crate::services::similarity::{
    CloneType, ComprehensiveReport, EntropyBlock, EntropyReport, Location, Metrics, Priority,
    RefactoringHint, SimilarBlock,
};
use std::path::PathBuf;
use tempfile::TempDir;

fn create_test_location(file: &str, start: usize, end: usize) -> Location {
    Location {
        file: PathBuf::from(file),
        start_line: start,
        end_line: end,
        start_column: None,
        end_column: None,
    }
}

fn create_test_similar_block(id: &str, locations: Vec<Location>) -> SimilarBlock {
    SimilarBlock {
        id: id.to_string(),
        locations,
        similarity: 0.95,
        clone_type: CloneType::Type1,
        lines: 10,
        tokens: 50,
        content_preview: "fn example() {\n    let x = 1;\n}".to_string(),
    }
}

fn create_test_entropy_block(file: &str, entropy: f64) -> EntropyBlock {
    EntropyBlock {
        location: create_test_location(file, 1, 10),
        entropy,
        category: if entropy > 4.0 {
            "Complex".to_string()
        } else {
            "Repetitive".to_string()
        },
        suggestion: "Consider refactoring".to_string(),
    }
}

fn create_test_refactoring_hint(pattern: &str, priority: Priority) -> RefactoringHint {
    RefactoringHint {
        locations: vec![
            create_test_location("test1.rs", 1, 10),
            create_test_location("test2.rs", 5, 15),
        ],
        pattern: pattern.to_string(),
        suggestion: "Extract to shared function".to_string(),
        priority,
    }
}

fn create_test_metrics(dup_pct: f64, avg_entropy: f64, total_clones: usize) -> Metrics {
    Metrics {
        duplication_percentage: dup_pct,
        average_entropy: avg_entropy,
        total_clones,
    }
}

fn create_empty_report() -> ComprehensiveReport {
    ComprehensiveReport {
        exact_duplicates: vec![],
        structural_similarities: vec![],
        semantic_similarities: vec![],
        entropy_analysis: None,
        refactoring_opportunities: vec![],
        metrics: create_test_metrics(0.0, 0.0, 0),
    }
}

fn create_populated_report() -> ComprehensiveReport {
    let locations1 = vec![
        create_test_location("src/file1.rs", 10, 20),
        create_test_location("src/file2.rs", 30, 40),
    ];
    let locations2 = vec![
        create_test_location("src/file3.rs", 50, 60),
        create_test_location("src/file4.rs", 70, 80),
    ];

    ComprehensiveReport {
        exact_duplicates: vec![
            create_test_similar_block("block1", locations1.clone()),
            create_test_similar_block("block2", locations2.clone()),
        ],
        structural_similarities: vec![SimilarBlock {
            id: "struct1".to_string(),
            locations: locations1.clone(),
            similarity: 0.85,
            clone_type: CloneType::Type2,
            lines: 15,
            tokens: 75,
            content_preview: "struct Example {}".to_string(),
        }],
        semantic_similarities: vec![SimilarBlock {
            id: "sem1".to_string(),
            locations: locations2.clone(),
            similarity: 0.78,
            clone_type: CloneType::Type4,
            lines: 20,
            tokens: 100,
            content_preview: "impl Trait for Example {}".to_string(),
        }],
        entropy_analysis: Some(EntropyReport {
            average_entropy: 3.5,
            high_entropy_blocks: vec![
                create_test_entropy_block("complex1.rs", 4.5),
                create_test_entropy_block("complex2.rs", 4.2),
            ],
            low_entropy_patterns: vec![
                create_test_entropy_block("repetitive1.rs", 1.5),
                create_test_entropy_block("repetitive2.rs", 1.8),
            ],
            recommendations: vec![
                "Consider breaking down complex functions".to_string(),
                "Extract repeated patterns".to_string(),
            ],
        }),
        refactoring_opportunities: vec![
            create_test_refactoring_hint("Repeated structure", Priority::High),
            create_test_refactoring_hint("Similar logic", Priority::Medium),
            create_test_refactoring_hint("Minor duplication", Priority::Low),
        ],
        metrics: create_test_metrics(15.5, 3.5, 4),
    }
}

// Tests for build_config

#[test]
fn test_build_config_fuzzy() {
    let config = build_config(DuplicateType::Fuzzy, 0.7, 5, 50);
    assert!(config.enable_ast);
    assert!(!config.enable_semantic);
    assert_eq!(config.min_lines, 5);
}

#[test]
fn test_build_config_renamed() {
    let config = build_config(DuplicateType::Renamed, 0.75, 8, 80);
    assert!(config.enable_ast);
    assert!(!config.enable_semantic);
}

#[test]
fn test_build_config_semantic() {
    let config = build_config(DuplicateType::Semantic, 0.6, 6, 60);
    assert!(config.enable_ast);
    assert!(config.enable_semantic);
}

#[test]
fn test_build_config_gapped() {
    let config = build_config(DuplicateType::Gapped, 0.65, 7, 70);
    assert!(config.enable_ast);
    assert!(config.enable_semantic);
}

#[test]
fn test_build_config_all() {
    let config = build_config(DuplicateType::All, 0.5, 4, 40);
    assert!(config.enable_ast);
    assert!(config.enable_semantic);
    assert!(config.enable_entropy);
}

// Tests for is_source_file

#[test]
fn test_is_source_file_rust() {
    assert!(is_source_file(std::path::Path::new("main.rs")));
    assert!(is_source_file(std::path::Path::new("lib.rs")));
}

#[test]
fn test_is_source_file_typescript() {
    assert!(is_source_file(std::path::Path::new("app.ts")));
    assert!(is_source_file(std::path::Path::new("component.tsx")));
}

#[test]
fn test_is_source_file_javascript() {
    assert!(is_source_file(std::path::Path::new("script.js")));
    assert!(is_source_file(std::path::Path::new("component.jsx")));
}

#[test]
fn test_is_source_file_python() {
    assert!(is_source_file(std::path::Path::new("main.py")));
}

#[test]
fn test_is_source_file_c_cpp() {
    assert!(is_source_file(std::path::Path::new("main.c")));
    assert!(is_source_file(std::path::Path::new("main.cpp")));
    assert!(is_source_file(std::path::Path::new("main.cc")));
    assert!(is_source_file(std::path::Path::new("header.h")));
    assert!(is_source_file(std::path::Path::new("header.hpp")));
}

#[test]
fn test_is_source_file_other_languages() {
    assert!(is_source_file(std::path::Path::new("Main.kt")));
    assert!(is_source_file(std::path::Path::new("Main.java")));
    assert!(is_source_file(std::path::Path::new("main.go")));
}

#[test]
fn test_is_source_file_non_source() {
    assert!(!is_source_file(std::path::Path::new("README.md")));
    assert!(!is_source_file(std::path::Path::new("config.toml")));
    assert!(!is_source_file(std::path::Path::new("data.json")));
    assert!(!is_source_file(std::path::Path::new("script.sh")));
}

#[test]
fn test_is_source_file_no_extension() {
    assert!(!is_source_file(std::path::Path::new("Makefile")));
    assert!(!is_source_file(std::path::Path::new("README")));
}

// Tests for should_include_file

#[test]
fn test_should_include_file_no_filters() {
    let path = std::path::Path::new("/project/src/main.rs");
    assert!(should_include_file(path, &None, &None));
}

#[test]
fn test_should_include_file_with_include_pattern_match() {
    let path = std::path::Path::new("/project/src/main.rs");
    assert!(should_include_file(path, &Some("src".to_string()), &None));
}

#[test]
fn test_should_include_file_with_include_pattern_no_match() {
    let path = std::path::Path::new("/project/tests/test.rs");
    assert!(!should_include_file(path, &Some("src".to_string()), &None));
}

#[test]
fn test_should_include_file_with_exclude_pattern_match() {
    let path = std::path::Path::new("/project/target/debug/main.rs");
    assert!(!should_include_file(
        path,
        &None,
        &Some("target".to_string())
    ));
}

#[test]
fn test_should_include_file_with_exclude_pattern_no_match() {
    let path = std::path::Path::new("/project/src/main.rs");
    assert!(should_include_file(
        path,
        &None,
        &Some("target".to_string())
    ));
}

#[test]
fn test_should_include_file_exclude_takes_precedence() {
    let path = std::path::Path::new("/project/src/target/file.rs");
    // File matches include but also matches exclude - exclude wins
    assert!(!should_include_file(
        path,
        &Some("src".to_string()),
        &Some("target".to_string())
    ));
}

// Tests for filter_top_files

#[test]
fn test_filter_top_files_zero() {
    let report = create_empty_report();
    let filtered = filter_top_files(report.clone(), 0);
    assert_eq!(filtered.metrics.total_clones, report.metrics.total_clones);
}

#[test]
fn test_filter_top_files_nonzero() {
    let report = create_populated_report();
    let filtered = filter_top_files(report.clone(), 5);
    // Currently returns report as-is
    assert_eq!(
        filtered.exact_duplicates.len(),
        report.exact_duplicates.len()
    );
}

// Tests for format_report

#[test]
fn test_format_report_json() {
    let report = create_populated_report();
    let result = format_report(&report, DuplicateOutputFormat::Json);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("exact_duplicates"));
    assert!(output.contains("metrics"));
}

#[test]
fn test_format_report_summary() {
    let report = create_populated_report();
    let result = format_report(&report, DuplicateOutputFormat::Summary);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Code Similarity Analysis Summary"));
    assert!(output.contains("Metrics"));
}

#[test]
fn test_format_report_human() {
    let report = create_populated_report();
    let result = format_report(&report, DuplicateOutputFormat::Human);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Metrics"));
}

#[test]
fn test_format_report_detailed() {
    let report = create_populated_report();
    let result = format_report(&report, DuplicateOutputFormat::Detailed);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Comprehensive Code Similarity Report"));
}

#[test]
fn test_format_report_csv() {
    let report = create_populated_report();
    let result = format_report(&report, DuplicateOutputFormat::Csv);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Type,File1,Start1,End1,File2,Start2,End2,Similarity"));
}

#[test]
fn test_format_report_sarif() {
    let report = create_populated_report();
    let result = format_report(&report, DuplicateOutputFormat::Sarif);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("$schema"));
    assert!(output.contains("pmat-similarity"));
}

// Tests for format_summary_report

#[test]
fn test_format_summary_report_empty() {
    let report = create_empty_report();
    let result = format_summary_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("# Code Similarity Analysis Summary"));
    assert!(output.contains("Duplication: 0.0%"));
    assert!(output.contains("Total Clones: 0"));
}

#[test]
fn test_format_summary_report_with_refactoring() {
    let report = create_populated_report();
    let result = format_summary_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Top Refactoring Opportunities"));
}

// Tests for format_summary_metrics

#[test]
fn test_format_summary_metrics() {
    let metrics = create_test_metrics(12.5, 3.2, 42);
    let mut output = String::new();
    let result = format_summary_metrics(&mut output, &metrics);
    assert!(result.is_ok());
    assert!(output.contains("12.5%"));
    assert!(output.contains("3.20"));
    assert!(output.contains("42"));
}

// Tests for format_summary_clone_types

#[test]
fn test_format_summary_clone_types() {
    let report = create_populated_report();
    let mut output = String::new();
    let result = format_summary_clone_types(&mut output, &report);
    assert!(result.is_ok());
    assert!(output.contains("Clone Types"));
    assert!(output.contains("Exact Duplicates: 2"));
    assert!(output.contains("Structural Similarities: 1"));
    assert!(output.contains("Semantic Similarities: 1"));
}

// Tests for format_summary_refactoring_opportunities

#[test]
fn test_format_summary_refactoring_opportunities_empty() {
    let opportunities: Vec<RefactoringHint> = vec![];
    let mut output = String::new();
    let result = format_summary_refactoring_opportunities(&mut output, &opportunities);
    assert!(result.is_ok());
    assert!(!output.contains("Refactoring Opportunities"));
}

#[test]
fn test_format_summary_refactoring_opportunities_nonempty() {
    let opportunities = vec![
        create_test_refactoring_hint("Pattern1", Priority::High),
        create_test_refactoring_hint("Pattern2", Priority::Medium),
    ];
    let mut output = String::new();
    let result = format_summary_refactoring_opportunities(&mut output, &opportunities);
    assert!(result.is_ok());
    assert!(output.contains("Top Refactoring Opportunities"));
    assert!(output.contains("Pattern1"));
    assert!(output.contains("Pattern2"));
}

#[test]
fn test_format_summary_refactoring_opportunities_more_than_five() {
    let opportunities: Vec<_> = (0..10)
        .map(|i| create_test_refactoring_hint(&format!("Pattern{}", i), Priority::High))
        .collect();
    let mut output = String::new();
    let result = format_summary_refactoring_opportunities(&mut output, &opportunities);
    assert!(result.is_ok());
    // Should only show first 5
    assert!(output.contains("Pattern0"));
    assert!(output.contains("Pattern4"));
    assert!(!output.contains("Pattern5"));
}

// Tests for format_detailed_report

#[test]
fn test_format_detailed_report_empty() {
    let report = create_empty_report();
    let result = format_detailed_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Comprehensive Code Similarity Report"));
    assert!(output.contains("Overall Metrics"));
}

#[test]
fn test_format_detailed_report_populated() {
    let report = create_populated_report();
    let result = format_detailed_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Exact Duplicates"));
    assert!(output.contains("Structural Similarities"));
    assert!(output.contains("Entropy Analysis"));
    assert!(output.contains("Refactoring Opportunities"));
}

// Tests for format_metrics_section

#[test]
fn test_format_metrics_section() {
    let metrics = create_test_metrics(25.0, 4.5, 100);
    let mut output = String::new();
    let result = format_metrics_section(&mut output, &metrics);
    assert!(result.is_ok());
    assert!(output.contains("Overall Metrics"));
    assert!(output.contains("25.0%"));
    assert!(output.contains("4.50"));
    assert!(output.contains("100"));
}

// Tests for format_exact_duplicates_section

#[test]
fn test_format_exact_duplicates_section_empty() {
    let duplicates: Vec<SimilarBlock> = vec![];
    let mut output = String::new();
    let result = format_exact_duplicates_section(&mut output, &duplicates);
    assert!(result.is_ok());
    assert!(output.is_empty());
}

#[test]
fn test_format_exact_duplicates_section_nonempty() {
    let duplicates = vec![create_test_similar_block(
        "test_block",
        vec![
            create_test_location("file1.rs", 1, 10),
            create_test_location("file2.rs", 20, 30),
        ],
    )];
    let mut output = String::new();
    let result = format_exact_duplicates_section(&mut output, &duplicates);
    assert!(result.is_ok());
    assert!(output.contains("Exact Duplicates"));
    assert!(output.contains("test_block"));
}

// Tests for format_single_duplicate_block

#[test]
fn test_format_single_duplicate_block() {
    let block = create_test_similar_block(
        "dup123",
        vec![
            create_test_location("src/a.rs", 5, 15),
            create_test_location("src/b.rs", 25, 35),
        ],
    );
    let mut output = String::new();
    let result = format_single_duplicate_block(&mut output, &block);
    assert!(result.is_ok());
    assert!(output.contains("Block dup123"));
    assert!(output.contains("Lines: 10"));
    assert!(output.contains("Tokens: 50"));
    assert!(output.contains("src/a.rs:5-15"));
    assert!(output.contains("src/b.rs:25-35"));
    assert!(output.contains("Preview:"));
}

// Tests for format_structural_similarities_section

#[test]
fn test_format_structural_similarities_section_empty() {
    let similarities: Vec<SimilarBlock> = vec![];
    let mut output = String::new();
    let result = format_structural_similarities_section(&mut output, &similarities);
    assert!(result.is_ok());
    assert!(output.is_empty());
}

#[test]
fn test_format_structural_similarities_section_nonempty() {
    let similarities = vec![SimilarBlock {
        id: "struct_sim".to_string(),
        locations: vec![
            create_test_location("a.rs", 1, 10),
            create_test_location("b.rs", 5, 15),
        ],
        similarity: 0.9,
        clone_type: CloneType::Type2,
        lines: 10,
        tokens: 50,
        content_preview: "fn test()".to_string(),
    }];
    let mut output = String::new();
    let result = format_structural_similarities_section(&mut output, &similarities);
    assert!(result.is_ok());
    assert!(output.contains("Structural Similarities"));
}

#[test]
fn test_format_structural_similarities_section_limits_to_ten() {
    let similarities: Vec<_> = (0..20)
        .map(|i| SimilarBlock {
            id: format!("sim{}", i),
            locations: vec![create_test_location("a.rs", i, i + 10)],
            similarity: 0.8,
            clone_type: CloneType::Type2,
            lines: 10,
            tokens: 50,
            content_preview: "test".to_string(),
        })
        .collect();
    let mut output = String::new();
    let result = format_structural_similarities_section(&mut output, &similarities);
    assert!(result.is_ok());
    // Should only contain first 10
    assert!(output.contains("sim0"));
    assert!(output.contains("sim9"));
    assert!(!output.contains("sim10"));
}

// Tests for format_single_structural_block

#[test]
fn test_format_single_structural_block() {
    let block = SimilarBlock {
        id: "struct_block".to_string(),
        locations: vec![
            create_test_location("x.rs", 100, 120),
            create_test_location("y.rs", 200, 220),
        ],
        similarity: 0.875,
        clone_type: CloneType::Type3,
        lines: 20,
        tokens: 100,
        content_preview: "let x = 1;".to_string(),
    };
    let mut output = String::new();
    let result = format_single_structural_block(&mut output, &block);
    assert!(result.is_ok());
    assert!(output.contains("Similarity struct_block"));
    assert!(output.contains("87.5%"));
    assert!(output.contains("Type3"));
    assert!(output.contains("x.rs:100-120"));
    assert!(output.contains("y.rs:200-220"));
}

// Tests for format_entropy_analysis_section

#[test]
fn test_format_entropy_analysis_section_none() {
    let entropy_analysis: Option<EntropyReport> = None;
    let mut output = String::new();
    let result = format_entropy_analysis_section(&mut output, &entropy_analysis);
    assert!(result.is_ok());
    assert!(output.is_empty());
}

#[test]
fn test_format_entropy_analysis_section_some() {
    let entropy_analysis = Some(EntropyReport {
        average_entropy: 3.5,
        high_entropy_blocks: vec![create_test_entropy_block("high.rs", 4.8)],
        low_entropy_patterns: vec![create_test_entropy_block("low.rs", 1.2)],
        recommendations: vec!["Refactor complex code".to_string()],
    });
    let mut output = String::new();
    let result = format_entropy_analysis_section(&mut output, &entropy_analysis);
    assert!(result.is_ok());
    assert!(output.contains("Entropy Analysis"));
    assert!(output.contains("3.50"));
}

#[test]
fn test_format_entropy_analysis_section_empty_blocks() {
    let entropy_analysis = Some(EntropyReport {
        average_entropy: 2.5,
        high_entropy_blocks: vec![],
        low_entropy_patterns: vec![],
        recommendations: vec![],
    });
    let mut output = String::new();
    let result = format_entropy_analysis_section(&mut output, &entropy_analysis);
    assert!(result.is_ok());
    assert!(output.contains("Entropy Analysis"));
    assert!(!output.contains("High Complexity"));
    assert!(!output.contains("Repetitive Patterns"));
}

// Tests for format_high_entropy_blocks

#[test]
fn test_format_high_entropy_blocks_empty() {
    let entropy = EntropyReport {
        average_entropy: 3.0,
        high_entropy_blocks: vec![],
        low_entropy_patterns: vec![],
        recommendations: vec![],
    };
    let mut output = String::new();
    let result = format_high_entropy_blocks(&mut output, &entropy);
    assert!(result.is_ok());
    assert!(output.is_empty());
}

#[test]
fn test_format_high_entropy_blocks_nonempty() {
    let entropy = EntropyReport {
        average_entropy: 4.0,
        high_entropy_blocks: vec![
            create_test_entropy_block("complex1.rs", 4.5),
            create_test_entropy_block("complex2.rs", 4.8),
        ],
        low_entropy_patterns: vec![],
        recommendations: vec![],
    };
    let mut output = String::new();
    let result = format_high_entropy_blocks(&mut output, &entropy);
    assert!(result.is_ok());
    assert!(output.contains("High Complexity Code"));
    assert!(output.contains("complex1.rs"));
    assert!(output.contains("4.50"));
}

#[test]
fn test_format_high_entropy_blocks_limits_to_five() {
    let entropy = EntropyReport {
        average_entropy: 4.0,
        high_entropy_blocks: (0..10)
            .map(|i| create_test_entropy_block(&format!("file{}.rs", i), 4.0 + i as f64 * 0.1))
            .collect(),
        low_entropy_patterns: vec![],
        recommendations: vec![],
    };
    let mut output = String::new();
    let result = format_high_entropy_blocks(&mut output, &entropy);
    assert!(result.is_ok());
    assert!(output.contains("file0.rs"));
    assert!(output.contains("file4.rs"));
    assert!(!output.contains("file5.rs"));
}

// Tests for format_low_entropy_patterns

#[test]
fn test_format_low_entropy_patterns_empty() {
    let entropy = EntropyReport {
        average_entropy: 3.0,
        high_entropy_blocks: vec![],
        low_entropy_patterns: vec![],
        recommendations: vec![],
    };
    let mut output = String::new();
    let result = format_low_entropy_patterns(&mut output, &entropy);
    assert!(result.is_ok());
    assert!(output.is_empty());
}

#[test]
fn test_format_low_entropy_patterns_nonempty() {
    let entropy = EntropyReport {
        average_entropy: 2.0,
        high_entropy_blocks: vec![],
        low_entropy_patterns: vec![
            create_test_entropy_block("repetitive1.rs", 1.2),
            create_test_entropy_block("repetitive2.rs", 1.5),
        ],
        recommendations: vec![],
    };
    let mut output = String::new();
    let result = format_low_entropy_patterns(&mut output, &entropy);
    assert!(result.is_ok());
    assert!(output.contains("Repetitive Patterns"));
    assert!(output.contains("repetitive1.rs"));
}

// Tests for format_entropy_block_item

#[test]
fn test_format_entropy_block_item() {
    let block = create_test_entropy_block("entropy_test.rs", 3.75);
    let mut output = String::new();
    let result = format_entropy_block_item(&mut output, &block);
    assert!(result.is_ok());
    assert!(output.contains("entropy_test.rs:1"));
    assert!(output.contains("3.75"));
    assert!(output.contains("Suggestion:"));
}

// Tests for format_refactoring_opportunities_section

#[test]
fn test_format_refactoring_opportunities_section_empty() {
    let opportunities: Vec<RefactoringHint> = vec![];
    let mut output = String::new();
    let result = format_refactoring_opportunities_section(&mut output, &opportunities);
    assert!(result.is_ok());
    assert!(output.is_empty());
}

#[test]
fn test_format_refactoring_opportunities_section_nonempty() {
    let opportunities = vec![
        create_test_refactoring_hint("Extract method", Priority::High),
        create_test_refactoring_hint("Consolidate", Priority::Low),
    ];
    let mut output = String::new();
    let result = format_refactoring_opportunities_section(&mut output, &opportunities);
    assert!(result.is_ok());
    assert!(output.contains("Refactoring Opportunities"));
    assert!(output.contains("Extract method"));
    assert!(output.contains("High"));
}

// Tests for format_single_refactoring_hint

#[test]
fn test_format_single_refactoring_hint() {
    let hint = create_test_refactoring_hint("DRY violation", Priority::Medium);
    let mut output = String::new();
    let result = format_single_refactoring_hint(&mut output, &hint);
    assert!(result.is_ok());
    assert!(output.contains("### DRY violation"));
    assert!(output.contains("Priority: Medium"));
    assert!(output.contains("Suggestion:"));
    assert!(output.contains("test1.rs:1-10"));
    assert!(output.contains("test2.rs:5-15"));
}

// Tests for print_performance_metrics

#[test]
fn test_print_performance_metrics() {
    let report = create_populated_report();
    let elapsed = std::time::Duration::from_millis(500);
    // This prints to stderr, so we just ensure it doesn't panic
    print_performance_metrics(&report, elapsed);
}

#[test]
fn test_print_performance_metrics_empty_report() {
    let report = create_empty_report();
    let elapsed = std::time::Duration::from_millis(100);
    print_performance_metrics(&report, elapsed);
}

// Tests for format_csv_report

#[test]
fn test_format_csv_report_empty() {
    let report = create_empty_report();
    let result = format_csv_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    // Should contain header only
    assert!(output.starts_with("Type,File1,Start1,End1,File2,Start2,End2,Similarity"));
    // Should have exactly one line (header)
    assert_eq!(output.lines().count(), 1);
}

#[test]
fn test_format_csv_report_with_duplicates() {
    let report = create_populated_report();
    let result = format_csv_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Exact,"));
    assert!(output.contains(",100.0"));
    assert!(output.contains("Structural,"));
}

#[test]
fn test_format_csv_report_single_location_blocks_skipped() {
    let mut report = create_empty_report();
    report.exact_duplicates.push(SimilarBlock {
        id: "single".to_string(),
        locations: vec![create_test_location("only.rs", 1, 10)],
        similarity: 1.0,
        clone_type: CloneType::Type1,
        lines: 10,
        tokens: 50,
        content_preview: "test".to_string(),
    });
    let result = format_csv_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    // Should only have header, single-location blocks are skipped
    assert_eq!(output.lines().count(), 1);
}

// Tests for format_sarif_report

#[test]
fn test_format_sarif_report_empty() {
    let report = create_empty_report();
    let result = format_sarif_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("\"$schema\""));
    assert!(output.contains("\"version\": \"2.1.0\""));
    assert!(output.contains("\"results\": []"));
}

#[test]
fn test_format_sarif_report_with_duplicates() {
    let report = create_populated_report();
    let result = format_sarif_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("duplicate-code"));
    assert!(output.contains("warning"));
    assert!(output.contains("physicalLocation"));
    assert!(output.contains("startLine"));
}

#[test]
fn test_format_sarif_report_contains_tool_info() {
    let report = create_empty_report();
    let result = format_sarif_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("pmat-similarity"));
    assert!(output.contains("paiml-mcp-agent-toolkit"));
}

// Tests for print_summary

#[test]
fn test_print_summary() {
    let report = create_populated_report();
    // This prints to stderr, just verify it doesn't panic
    print_summary(&report);
}

#[test]
fn test_print_summary_empty() {
    let report = create_empty_report();
    print_summary(&report);
}

#[test]
fn test_print_summary_no_refactoring() {
    let mut report = create_populated_report();
    report.refactoring_opportunities.clear();
    print_summary(&report);
}

// Tests for collect_files (async)

#[tokio::test]
async fn test_collect_files_empty_directory() {
    let temp_dir = TempDir::new().unwrap();
    let result = collect_files(&temp_dir.path().to_path_buf(), &None, &None).await;
    assert!(result.is_ok());
    assert!(result.unwrap().is_empty());
}

#[tokio::test]
async fn test_collect_files_with_source_files() {
    let temp_dir = TempDir::new().unwrap();

    // Create some source files
    let rust_file = temp_dir.path().join("test.rs");
    std::fs::write(&rust_file, "fn main() {}").unwrap();

    let py_file = temp_dir.path().join("test.py");
    std::fs::write(&py_file, "def main(): pass").unwrap();

    let result = collect_files(&temp_dir.path().to_path_buf(), &None, &None).await;
    assert!(result.is_ok());
    let files = result.unwrap();
    assert_eq!(files.len(), 2);
}

#[tokio::test]
async fn test_collect_files_with_include_filter() {
    let temp_dir = TempDir::new().unwrap();

    // Create src subdirectory
    let src_dir = temp_dir.path().join("src");
    std::fs::create_dir(&src_dir).unwrap();

    let src_file = src_dir.join("main.rs");
    std::fs::write(&src_file, "fn main() {}").unwrap();

    let other_file = temp_dir.path().join("other.rs");
    std::fs::write(&other_file, "fn other() {}").unwrap();

    let result = collect_files(
        &temp_dir.path().to_path_buf(),
        &Some("src".to_string()),
        &None,
    )
    .await;
    assert!(result.is_ok());
    let files = result.unwrap();
    assert_eq!(files.len(), 1);
    assert!(files[0].0.to_string_lossy().contains("src"));
}

#[tokio::test]
async fn test_collect_files_with_exclude_filter() {
    let temp_dir = TempDir::new().unwrap();

    // Create target subdirectory
    let target_dir = temp_dir.path().join("target");
    std::fs::create_dir(&target_dir).unwrap();

    let target_file = target_dir.join("build.rs");
    std::fs::write(&target_file, "fn build() {}").unwrap();

    let src_file = temp_dir.path().join("main.rs");
    std::fs::write(&src_file, "fn main() {}").unwrap();

    let result = collect_files(
        &temp_dir.path().to_path_buf(),
        &None,
        &Some("target".to_string()),
    )
    .await;
    assert!(result.is_ok());
    let files = result.unwrap();
    assert_eq!(files.len(), 1);
    assert!(!files[0].0.to_string_lossy().contains("target"));
}

#[tokio::test]
async fn test_collect_files_ignores_non_source_files() {
    let temp_dir = TempDir::new().unwrap();

    std::fs::write(temp_dir.path().join("README.md"), "# Readme").unwrap();
    std::fs::write(temp_dir.path().join("config.toml"), "[config]").unwrap();
    std::fs::write(temp_dir.path().join("main.rs"), "fn main() {}").unwrap();

    let result = collect_files(&temp_dir.path().to_path_buf(), &None, &None).await;
    assert!(result.is_ok());
    let files = result.unwrap();
    assert_eq!(files.len(), 1);
    assert!(files[0].0.to_string_lossy().ends_with(".rs"));
}

// Tests for handle_analyze_similarity (integration, async)

#[tokio::test]
async fn test_handle_analyze_similarity_empty_project() {
    let temp_dir = TempDir::new().unwrap();
    let result = handle_analyze_similarity(
        temp_dir.path().to_path_buf(),
        DuplicateType::Exact,
        0.8,
        6,
        50,
        DuplicateOutputFormat::Summary,
        false,
        None,
        None,
        None,
        0,
    )
    .await;
    assert!(result.is_ok());
}

#[tokio::test]
async fn test_handle_analyze_similarity_with_files() {
    let temp_dir = TempDir::new().unwrap();

    // Create some test files
    let file1 = temp_dir.path().join("test1.rs");
    let file2 = temp_dir.path().join("test2.rs");

    let content = r#"
fn example_function() {
    let x = 1;
    let y = 2;
    let z = x + y;
    println!("{}", z);
}
"#;
    std::fs::write(&file1, content).unwrap();
    std::fs::write(&file2, content).unwrap();

    let result = handle_analyze_similarity(
        temp_dir.path().to_path_buf(),
        DuplicateType::All,
        0.7,
        3,
        10,
        DuplicateOutputFormat::Json,
        true, // enable perf metrics
        None,
        None,
        None,
        5,
    )
    .await;
    assert!(result.is_ok());
}

#[tokio::test]
async fn test_handle_analyze_similarity_with_output_file() {
    let temp_dir = TempDir::new().unwrap();
    let output_file = temp_dir.path().join("output.json");

    let file1 = temp_dir.path().join("test.rs");
    std::fs::write(&file1, "fn main() {}").unwrap();

    let result = handle_analyze_similarity(
        temp_dir.path().to_path_buf(),
        DuplicateType::Exact,
        0.8,
        1,
        5,
        DuplicateOutputFormat::Json,
        false,
        None,
        None,
        Some(output_file.clone()),
        0,
    )
    .await;
    assert!(result.is_ok());
    assert!(output_file.exists());
}

#[tokio::test]
async fn test_handle_analyze_similarity_all_formats() {
    let temp_dir = TempDir::new().unwrap();
    let file1 = temp_dir.path().join("test.rs");
    std::fs::write(&file1, "fn main() { let x = 1; }").unwrap();

    let formats = vec![
        DuplicateOutputFormat::Summary,
        DuplicateOutputFormat::Human,
        DuplicateOutputFormat::Detailed,
        DuplicateOutputFormat::Json,
        DuplicateOutputFormat::Csv,
        DuplicateOutputFormat::Sarif,
    ];

    for format in formats {
        let result = handle_analyze_similarity(
            temp_dir.path().to_path_buf(),
            DuplicateType::Exact,
            0.8,
            1,
            5,
            format,
            false,
            None,
            None,
            None,
            0,
        )
        .await;
        assert!(result.is_ok(), "Failed for format: {:?}", format);
    }
}

#[tokio::test]
async fn test_handle_analyze_similarity_all_detection_types() {
    let temp_dir = TempDir::new().unwrap();
    let file1 = temp_dir.path().join("test.rs");
    std::fs::write(&file1, "fn main() { let x = 1; }").unwrap();

    let types = vec![
        DuplicateType::Exact,
        DuplicateType::Fuzzy,
        DuplicateType::Renamed,
        DuplicateType::Semantic,
        DuplicateType::Gapped,
        DuplicateType::All,
    ];

    for detection_type in types {
        let result = handle_analyze_similarity(
            temp_dir.path().to_path_buf(),
            detection_type.clone(),
            0.7,
            1,
            5,
            DuplicateOutputFormat::Summary,
            false,
            None,
            None,
            None,
            0,
        )
        .await;
        assert!(result.is_ok(), "Failed for type: {:?}", detection_type);
    }
}

// Edge case tests

#[test]
fn test_is_source_file_with_double_extension() {
    assert!(is_source_file(std::path::Path::new("file.test.rs")));
    assert!(is_source_file(std::path::Path::new("file.spec.ts")));
}

#[test]
fn test_is_source_file_with_hidden_file() {
    assert!(is_source_file(std::path::Path::new(".hidden.rs")));
}

#[test]
fn test_should_include_file_with_unicode_path() {
    let path = std::path::Path::new("/project/src/файл.rs");
    assert!(should_include_file(path, &None, &None));
}

#[test]
fn test_format_csv_escaping() {
    // Test that CSV formatting handles special characters
    let mut report = create_empty_report();
    report.exact_duplicates.push(SimilarBlock {
        id: "test".to_string(),
        locations: vec![
            create_test_location("file,with,commas.rs", 1, 10),
            create_test_location("normal.rs", 5, 15),
        ],
        similarity: 1.0,
        clone_type: CloneType::Type1,
        lines: 10,
        tokens: 50,
        content_preview: "test".to_string(),
    });
    let result = format_csv_report(&report);
    assert!(result.is_ok());
}

#[test]
fn test_format_sarif_with_many_locations() {
    let mut report = create_empty_report();
    let locations: Vec<_> = (0..100)
        .map(|i| create_test_location(&format!("file{}.rs", i), i, i + 10))
        .collect();
    report.exact_duplicates.push(SimilarBlock {
        id: "many_locs".to_string(),
        locations,
        similarity: 1.0,
        clone_type: CloneType::Type1,
        lines: 10,
        tokens: 50,
        content_preview: "test".to_string(),
    });
    let result = format_sarif_report(&report);
    assert!(result.is_ok());
}

#[test]
fn test_metrics_with_extreme_values() {
    let metrics = create_test_metrics(100.0, 10.0, 1_000_000);
    let mut output = String::new();
    let result = format_summary_metrics(&mut output, &metrics);
    assert!(result.is_ok());
    assert!(output.contains("100.0%"));
    assert!(output.contains("10.00"));
    assert!(output.contains("1000000"));
}

#[test]
fn test_metrics_with_zero_values() {
    let metrics = create_test_metrics(0.0, 0.0, 0);
    let mut output = String::new();
    let result = format_summary_metrics(&mut output, &metrics);
    assert!(result.is_ok());
    assert!(output.contains("0.0%"));
    assert!(output.contains("0.00"));
    assert!(output.contains(": 0"));
}

#[test]
fn test_location_with_columns() {
    let location = Location {
        file: PathBuf::from("test.rs"),
        start_line: 10,
        end_line: 20,
        start_column: Some(5),
        end_column: Some(50),
    };
    let block = SimilarBlock {
        id: "col_test".to_string(),
        locations: vec![location],
        similarity: 1.0,
        clone_type: CloneType::Type1,
        lines: 10,
        tokens: 50,
        content_preview: "test".to_string(),
    };
    let mut output = String::new();
    let result = format_single_duplicate_block(&mut output, &block);
    assert!(result.is_ok());
}

#[test]
fn test_all_clone_types_in_detailed_report() {
    let mut report = create_empty_report();
    for clone_type in [
        CloneType::Type1,
        CloneType::Type2,
        CloneType::Type3,
        CloneType::Type4,
    ] {
        report.structural_similarities.push(SimilarBlock {
            id: format!("{:?}", clone_type),
            locations: vec![
                create_test_location("a.rs", 1, 10),
                create_test_location("b.rs", 5, 15),
            ],
            similarity: 0.9,
            clone_type,
            lines: 10,
            tokens: 50,
            content_preview: "test".to_string(),
        });
    }
    let result = format_detailed_report(&report);
    assert!(result.is_ok());
    let output = result.unwrap();
    assert!(output.contains("Type1"));
    assert!(output.contains("Type2"));
    assert!(output.contains("Type3"));
    assert!(output.contains("Type4"));
}

#[test]
fn test_all_priority_levels() {
    let mut output = String::new();
    for priority in [Priority::High, Priority::Medium, Priority::Low] {
        let hint = create_test_refactoring_hint(&format!("{:?}_pattern", priority), priority);
        format_single_refactoring_hint(&mut output, &hint).unwrap();
    }
    assert!(output.contains("High"));
    assert!(output.contains("Medium"));
    assert!(output.contains("Low"));
}

#[tokio::test]
async fn test_collect_files_nested_directories() {
    let temp_dir = TempDir::new().unwrap();

    // Create nested directory structure
    let deep_dir = temp_dir.path().join("a").join("b").join("c");
    std::fs::create_dir_all(&deep_dir).unwrap();
    std::fs::write(deep_dir.join("deep.rs"), "fn deep() {}").unwrap();

    let result = collect_files(&temp_dir.path().to_path_buf(), &None, &None).await;
    assert!(result.is_ok());
    let files = result.unwrap();
    assert_eq!(files.len(), 1);
    assert!(files[0].0.to_string_lossy().contains("deep.rs"));
}
