use crate::core::{ComplexityMetrics, FileMetrics, FunctionMetrics};

fn calculate_total_complexity(functions: &[FunctionMetrics]) -> (u32, u32) {
    functions.iter().fold((0, 0), |(cyc, cog), f| {
        (cyc + f.cyclomatic, cog + f.cognitive)
    })
}

pub mod filters;

pub type Transformer<T> = Box<dyn Fn(T) -> T>;

pub fn compose_transformers<T: 'static>(transformers: Vec<Transformer<T>>) -> Transformer<T> {
    Box::new(move |input| transformers.iter().fold(input, |acc, f| f(acc)))
}

pub fn transform_metrics<F>(metrics: FileMetrics, f: F) -> FileMetrics
where
    F: Fn(FileMetrics) -> FileMetrics,
{
    f(metrics)
}

pub fn map_functions<F>(metrics: FileMetrics, f: F) -> FileMetrics
where
    F: Fn(FunctionMetrics) -> FunctionMetrics,
{
    let functions: Vec<_> = metrics.complexity.functions.into_iter().map(f).collect();
    let (cyclomatic, cognitive) = calculate_total_complexity(&functions);

    FileMetrics {
        complexity: ComplexityMetrics {
            functions,
            cyclomatic_complexity: cyclomatic,
            cognitive_complexity: cognitive,
        },
        ..metrics
    }
}

pub fn filter_functions<F>(metrics: FileMetrics, predicate: F) -> FileMetrics
where
    F: Fn(&FunctionMetrics) -> bool,
{
    let functions: Vec<_> = metrics
        .complexity
        .functions
        .into_iter()
        .filter(|f| predicate(f))
        .collect();
    let (cyclomatic, cognitive) = calculate_total_complexity(&functions);

    FileMetrics {
        complexity: ComplexityMetrics {
            functions,
            cyclomatic_complexity: cyclomatic,
            cognitive_complexity: cognitive,
        },
        ..metrics
    }
}

pub fn sort_functions_by_complexity(metrics: FileMetrics) -> FileMetrics {
    let mut functions = metrics.complexity.functions;
    functions.sort_by(|a, b| b.cyclomatic.cmp(&a.cyclomatic));
    let (cyclomatic, cognitive) = calculate_total_complexity(&functions);

    FileMetrics {
        complexity: ComplexityMetrics {
            functions,
            cyclomatic_complexity: cyclomatic,
            cognitive_complexity: cognitive,
        },
        ..metrics
    }
}

pub fn limit_results(metrics: FileMetrics, limit: usize) -> FileMetrics {
    let functions: Vec<_> = metrics
        .complexity
        .functions
        .into_iter()
        .take(limit)
        .collect();
    let (cyclomatic, cognitive) = calculate_total_complexity(&functions);

    FileMetrics {
        complexity: ComplexityMetrics {
            functions,
            cyclomatic_complexity: cyclomatic,
            cognitive_complexity: cognitive,
        },
        debt_items: metrics.debt_items.into_iter().take(limit).collect(),
        ..metrics
    }
}

pub fn combine_file_metrics(metrics: Vec<FileMetrics>) -> FileMetrics {
    metrics.into_iter().fold(
        FileMetrics {
            path: std::path::PathBuf::new(),
            language: crate::core::Language::Unknown,
            complexity: ComplexityMetrics {
                functions: Vec::new(),
                cyclomatic_complexity: 0,
                cognitive_complexity: 0,
            },
            debt_items: Vec::new(),
            dependencies: Vec::new(),
            duplications: Vec::new(),
            total_lines: 0,
            module_scope: None,
            classes: None,
        },
        |mut acc, m| {
            acc.complexity
                .functions
                .extend(m.complexity.functions.clone());
            acc.complexity.cyclomatic_complexity += m.complexity.cyclomatic_complexity;
            acc.complexity.cognitive_complexity += m.complexity.cognitive_complexity;
            acc.debt_items.extend(m.debt_items);
            acc.dependencies.extend(m.dependencies);
            acc.duplications.extend(m.duplications);
            acc.total_lines += m.total_lines;
            acc
        },
    )
}

pub fn enrich_with_context(metrics: FileMetrics) -> FileMetrics {
    FileMetrics {
        debt_items: metrics
            .debt_items
            .into_iter()
            .map(|mut item| {
                if item.context.is_none() {
                    item.context = Some(format!("Found in {}", item.file.display()));
                }
                item
            })
            .collect(),
        ..metrics
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    fn create_test_function(name: &str, cyclomatic: u32, cognitive: u32) -> FunctionMetrics {
        FunctionMetrics {
            name: name.to_string(),
            file: PathBuf::from("test.rs"),
            line: 1,
            cyclomatic,
            cognitive,
            nesting: 0,
            length: 10,
            is_test: false,
            visibility: None,
            is_trait_method: false,
            in_test_module: false,
            entropy_score: None,
            is_pure: None,
            purity_confidence: None,
            purity_reason: None,
            call_dependencies: None,
            detected_patterns: None,
            upstream_callers: None,
            downstream_callees: None,
            mapping_pattern_result: None,
            adjusted_complexity: None,
            composition_metrics: None,
            language_specific: None,
            purity_level: None,
            error_swallowing_count: None,
            error_swallowing_patterns: None,
            entropy_analysis: None,
        }
    }

    #[test]
    fn test_calculate_total_complexity_empty() {
        let functions = vec![];
        let (cyc, cog) = calculate_total_complexity(&functions);
        assert_eq!(cyc, 0);
        assert_eq!(cog, 0);
    }

    #[test]
    fn test_calculate_total_complexity_single_function() {
        let functions = vec![create_test_function("test_func", 5, 3)];
        let (cyc, cog) = calculate_total_complexity(&functions);
        assert_eq!(cyc, 5);
        assert_eq!(cog, 3);
    }

    #[test]
    fn test_calculate_total_complexity_multiple_functions() {
        let functions = vec![
            create_test_function("func1", 5, 3),
            create_test_function("func2", 10, 7),
            create_test_function("func3", 2, 1),
        ];
        let (cyc, cog) = calculate_total_complexity(&functions);
        assert_eq!(cyc, 17);
        assert_eq!(cog, 11);
    }

    #[test]
    fn test_calculate_total_complexity_zero_values() {
        let functions = vec![
            create_test_function("func1", 0, 0),
            create_test_function("func2", 0, 0),
        ];
        let (cyc, cog) = calculate_total_complexity(&functions);
        assert_eq!(cyc, 0);
        assert_eq!(cog, 0);
    }

    #[test]
    fn test_calculate_total_complexity_large_values() {
        let functions = vec![
            create_test_function("complex_func1", 100, 80),
            create_test_function("complex_func2", 200, 150),
            create_test_function("complex_func3", 50, 40),
        ];
        let (cyc, cog) = calculate_total_complexity(&functions);
        assert_eq!(cyc, 350);
        assert_eq!(cog, 270);
    }

    #[test]
    fn test_calculate_total_complexity_mixed_values() {
        let functions = vec![
            create_test_function("simple", 1, 1),
            create_test_function("medium", 10, 8),
            create_test_function("complex", 25, 20),
            create_test_function("zero", 0, 0),
        ];
        let (cyc, cog) = calculate_total_complexity(&functions);
        assert_eq!(cyc, 36);
        assert_eq!(cog, 29);
    }

    fn create_test_file_metrics(
        path: &str,
        cyclomatic: u32,
        cognitive: u32,
        num_functions: usize,
    ) -> FileMetrics {
        let functions: Vec<FunctionMetrics> = (0..num_functions)
            .map(|i| {
                create_test_function(
                    &format!("func_{}", i),
                    cyclomatic / num_functions as u32,
                    cognitive / num_functions as u32,
                )
            })
            .collect();

        FileMetrics {
            path: PathBuf::from(path),
            language: crate::core::Language::Rust,
            complexity: ComplexityMetrics {
                functions,
                cyclomatic_complexity: cyclomatic,
                cognitive_complexity: cognitive,
            },
            debt_items: Vec::new(),
            dependencies: Vec::new(),
            duplications: Vec::new(),
            total_lines: 0,
            module_scope: None,
            classes: None,
        }
    }

    #[test]
    fn test_combine_file_metrics_empty() {
        let metrics = vec![];
        let combined = combine_file_metrics(metrics);

        assert_eq!(combined.complexity.functions.len(), 0);
        assert_eq!(combined.complexity.cyclomatic_complexity, 0);
        assert_eq!(combined.complexity.cognitive_complexity, 0);
        assert_eq!(combined.debt_items.len(), 0);
        assert_eq!(combined.dependencies.len(), 0);
        assert_eq!(combined.duplications.len(), 0);
    }

    #[test]
    fn test_combine_file_metrics_single() {
        let metrics = vec![create_test_file_metrics("test1.rs", 10, 8, 2)];
        let combined = combine_file_metrics(metrics);

        assert_eq!(combined.complexity.functions.len(), 2);
        assert_eq!(combined.complexity.cyclomatic_complexity, 10);
        assert_eq!(combined.complexity.cognitive_complexity, 8);
    }

    #[test]
    fn test_combine_file_metrics_multiple() {
        let metrics = vec![
            create_test_file_metrics("test1.rs", 10, 8, 2),
            create_test_file_metrics("test2.rs", 15, 12, 3),
            create_test_file_metrics("test3.rs", 5, 3, 1),
        ];
        let combined = combine_file_metrics(metrics);

        assert_eq!(combined.complexity.functions.len(), 6);
        assert_eq!(combined.complexity.cyclomatic_complexity, 30);
        assert_eq!(combined.complexity.cognitive_complexity, 23);
    }

    #[test]
    fn test_combine_file_metrics_with_debt_items() {
        use crate::core::{DebtItem, DebtType, Priority};

        let mut metrics1 = create_test_file_metrics("test1.rs", 10, 8, 1);
        metrics1.debt_items.push(DebtItem {
            id: "test1".to_string(),
            debt_type: DebtType::Complexity {
                cyclomatic: 10,
                cognitive: 8,
            },
            priority: Priority::Low,
            file: PathBuf::from("test1.rs"),
            line: 10,
            column: Some(5),
            message: "Test debt 1".to_string(),
            context: None,
        });

        let mut metrics2 = create_test_file_metrics("test2.rs", 15, 12, 1);
        metrics2.debt_items.push(DebtItem {
            id: "test2".to_string(),
            debt_type: DebtType::Complexity {
                cyclomatic: 10,
                cognitive: 8,
            },
            priority: Priority::Medium,
            file: PathBuf::from("test2.rs"),
            line: 20,
            column: Some(8),
            message: "Test debt 2".to_string(),
            context: None,
        });

        let combined = combine_file_metrics(vec![metrics1, metrics2]);

        assert_eq!(combined.debt_items.len(), 2);
        assert_eq!(combined.debt_items[0].id, "test1");
        assert_eq!(combined.debt_items[1].id, "test2");
    }

    #[test]
    fn test_combine_file_metrics_with_dependencies() {
        use crate::core::{Dependency, DependencyKind};

        let mut metrics1 = create_test_file_metrics("test1.rs", 10, 8, 1);
        metrics1.dependencies.push(Dependency {
            name: "module1".to_string(),
            kind: DependencyKind::Module,
        });

        let mut metrics2 = create_test_file_metrics("test2.rs", 15, 12, 1);
        metrics2.dependencies.push(Dependency {
            name: "module2".to_string(),
            kind: DependencyKind::Import,
        });

        let combined = combine_file_metrics(vec![metrics1, metrics2]);

        assert_eq!(combined.dependencies.len(), 2);
        assert_eq!(combined.dependencies[0].name, "module1");
        assert_eq!(combined.dependencies[1].name, "module2");
    }

    #[test]
    fn test_enrich_with_context_no_existing_context() {
        use crate::core::{DebtItem, DebtType, Priority};

        let mut metrics = create_test_file_metrics("test.rs", 10, 8, 1);
        metrics.debt_items.push(DebtItem {
            id: "test1".to_string(),
            debt_type: DebtType::Complexity {
                cyclomatic: 10,
                cognitive: 8,
            },
            priority: Priority::Medium,
            file: PathBuf::from("src/main.rs"),
            line: 10,
            column: Some(5),
            message: "Complex function".to_string(),
            context: None,
        });

        let enriched = enrich_with_context(metrics);

        assert_eq!(enriched.debt_items.len(), 1);
        assert!(enriched.debt_items[0].context.is_some());
        assert_eq!(
            enriched.debt_items[0].context.as_ref().unwrap(),
            "Found in src/main.rs"
        );
    }

    #[test]
    fn test_enrich_with_context_preserves_existing_context() {
        use crate::core::{DebtItem, DebtType, Priority};

        let mut metrics = create_test_file_metrics("test.rs", 10, 8, 1);
        metrics.debt_items.push(DebtItem {
            id: "test2".to_string(),
            debt_type: DebtType::Complexity {
                cyclomatic: 10,
                cognitive: 8,
            },
            priority: Priority::High,
            file: PathBuf::from("src/lib.rs"),
            line: 20,
            column: Some(7),
            message: "Needs refactoring".to_string(),
            context: Some("Already has context".to_string()),
        });

        let enriched = enrich_with_context(metrics);

        assert_eq!(enriched.debt_items.len(), 1);
        assert_eq!(
            enriched.debt_items[0].context.as_ref().unwrap(),
            "Already has context"
        );
    }

    #[test]
    fn test_enrich_with_context_multiple_items() {
        use crate::core::{DebtItem, DebtType, Priority};

        let mut metrics = create_test_file_metrics("test.rs", 10, 8, 1);
        metrics.debt_items.push(DebtItem {
            id: "item1".to_string(),
            debt_type: DebtType::Complexity {
                cyclomatic: 10,
                cognitive: 8,
            },
            priority: Priority::Low,
            file: PathBuf::from("src/main.rs"),
            line: 10,
            column: Some(5),
            message: "Item 1".to_string(),
            context: None,
        });
        metrics.debt_items.push(DebtItem {
            id: "item2".to_string(),
            debt_type: DebtType::CodeSmell { smell_type: None },
            priority: Priority::Medium,
            file: PathBuf::from("src/lib.rs"),
            line: 30,
            column: Some(8),
            message: "Item 2".to_string(),
            context: Some("Existing".to_string()),
        });

        let enriched = enrich_with_context(metrics);

        assert_eq!(enriched.debt_items.len(), 2);
        assert_eq!(
            enriched.debt_items[0].context.as_ref().unwrap(),
            "Found in src/main.rs"
        );
        assert_eq!(enriched.debt_items[1].context.as_ref().unwrap(), "Existing");
    }
}
