use super::{Difficulty, FunctionRisk, RiskCategory, TestEffort};
use crate::complexity::pattern_adjustments::PatternType;
use crate::core::ComplexityMetrics;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RiskWeights {
    pub coverage: f64,
    pub complexity: f64,
    pub cognitive: f64,
    pub debt: f64,
    pub untested_penalty: f64,
    pub debt_threshold_multiplier: f64,
}

impl Default for RiskWeights {
    fn default() -> Self {
        Self {
            coverage: 0.5,
            complexity: 0.3,
            cognitive: 0.45,
            debt: 0.2,
            untested_penalty: 2.0,
            debt_threshold_multiplier: 1.5,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RiskComponents {
    pub base: f64,
    pub debt_factor: f64,
    pub coverage_penalty: f64,
    pub breakdown: Vec<RiskFactor>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RiskFactor {
    pub name: String,
    pub weight: f64,
    pub raw_value: f64,
    pub contribution: f64,
}

#[derive(Clone, Debug)]
pub struct RiskContext {
    pub file: PathBuf,
    pub function_name: String,
    pub line_range: (usize, usize),
    pub complexity: ComplexityMetrics,
    pub coverage: Option<f64>,
    pub debt_score: Option<f64>,
    pub debt_threshold: Option<f64>,
    pub is_test: bool,
    pub is_recognized_pattern: bool,
    pub pattern_type: Option<PatternType>,
    pub pattern_confidence: f32,
}

pub trait RiskCalculator: Send + Sync {
    fn box_clone(&self) -> Box<dyn RiskCalculator>;
    fn calculate(&self, context: &RiskContext) -> FunctionRisk;
    fn calculate_risk_score(&self, context: &RiskContext) -> f64;
    fn calculate_risk_reduction(
        &self,
        current_risk: f64,
        complexity: u32,
        target_coverage: f64,
    ) -> f64;
}

#[derive(Clone, Default)]
pub struct EnhancedRiskStrategy {
    pub weights: RiskWeights,
}

impl RiskCalculator for EnhancedRiskStrategy {
    fn box_clone(&self) -> Box<dyn RiskCalculator> {
        Box::new(self.clone())
    }

    fn calculate(&self, context: &RiskContext) -> FunctionRisk {
        let risk_score = self.calculate_risk_score(context);
        let test_effort = self.estimate_test_effort(&context.complexity);
        let risk_category = self.categorize_risk(
            &context.complexity,
            context.coverage,
            risk_score,
            context.is_test,
        );

        FunctionRisk {
            file: context.file.clone(),
            function_name: context.function_name.clone(),
            line_range: context.line_range,
            cyclomatic_complexity: context.complexity.cyclomatic_complexity,
            cognitive_complexity: context.complexity.cognitive_complexity,
            coverage_percentage: context.coverage,
            risk_score,
            contextual_risk: None, // Populated later if context providers are enabled
            test_effort,
            risk_category,
            is_test_function: context.is_test,
        }
    }

    fn calculate_risk_score(&self, context: &RiskContext) -> f64 {
        let base_risk = self.calculate_base_risk(context);
        let debt_factor = self.calculate_debt_factor(context.debt_score, context.debt_threshold);

        // Apply pattern-based complexity adjustment
        let complexity_factor = self.calculate_pattern_adjusted_factor(context);

        // Don't apply coverage penalty to test functions - they don't need test coverage
        let coverage_penalty = if context.is_test {
            1.0 // No penalty for test functions
        } else {
            self.calculate_coverage_penalty(context.coverage)
        };

        // Apply coverage-based reduction for well-tested patterns
        let coverage_factor = self.calculate_coverage_reduction(context);

        let final_risk =
            base_risk * debt_factor * complexity_factor * coverage_penalty * coverage_factor;

        // Ensure genuinely complex code maintains minimum risk signal
        if context.complexity.cognitive_complexity > 40 && final_risk < 3.0 {
            3.0 // Very complex code always has at least medium risk
        } else {
            final_risk
        }
    }

    fn calculate_risk_reduction(
        &self,
        current_risk: f64,
        _complexity: u32,
        target_coverage: f64,
    ) -> f64 {
        let coverage_factor = target_coverage / 100.0;
        let reduction_rate = if target_coverage >= 80.0 { 0.8 } else { 0.6 };
        current_risk * coverage_factor * reduction_rate
    }
}

impl EnhancedRiskStrategy {
    fn calculate_base_risk(&self, context: &RiskContext) -> f64 {
        let cyclomatic = context.complexity.cyclomatic_complexity as f64;
        let cognitive = context.complexity.cognitive_complexity as f64;

        let complexity_component =
            (cyclomatic * self.weights.complexity + cognitive * self.weights.cognitive) / 50.0;

        // For test functions, don't include coverage in risk calculation
        let coverage_component = if context.is_test {
            0.0 // Test functions don't need test coverage
        } else {
            match context.coverage {
                Some(cov) => {
                    // Coverage is expected in percentage format (0-100)
                    // No automatic conversion - callers must provide percentages
                    (100.0 - cov) / 100.0 * self.weights.coverage
                }
                None => 0.0, // Don't add coverage weight when coverage is unknown
            }
        };

        (complexity_component + coverage_component) * 5.0
    }

    fn calculate_debt_factor(&self, debt_score: Option<f64>, debt_threshold: Option<f64>) -> f64 {
        match (debt_score, debt_threshold) {
            (Some(score), Some(threshold)) if threshold > 0.0 => {
                let ratio = score / threshold;
                match ratio {
                    r if r <= 1.0 => 1.0,
                    r if r <= 2.0 => 1.2,
                    r if r <= 5.0 => 1.5,
                    r if r <= 10.0 => 2.0,
                    _ => 2.5,
                }
            }
            _ => 1.0,
        }
    }

    /// Calculate coverage penalty multiplier.
    ///
    /// Coverage is expected in percentage format (0-100), not fraction format.
    fn calculate_coverage_penalty(&self, coverage: Option<f64>) -> f64 {
        match coverage {
            None => 1.0, // No penalty when coverage is unknown (not untested, just unknown)
            Some(c) => {
                // Coverage is in percentage format (0-100)
                match c {
                    c if c < 20.0 => 3.0,
                    c if c < 40.0 => 2.0,
                    c if c < 60.0 => 1.5,
                    c if c < 80.0 => 1.2,
                    _ => 0.8,
                }
            }
        }
    }

    /// Calculate pattern-based complexity reduction factor
    fn calculate_pattern_adjusted_factor(&self, context: &RiskContext) -> f64 {
        if !context.is_recognized_pattern {
            return 1.0; // No adjustment for non-patterns
        }

        match context.pattern_type {
            Some(PatternType::EnumMatching) | Some(PatternType::StringMatching) => {
                // Simple dispatch patterns get significant reduction
                0.3 // 70% reduction for pattern matching
            }
            Some(PatternType::TraitDelegation) | Some(PatternType::SerializationDispatch) => {
                // Boilerplate patterns get moderate reduction
                0.5 // 50% reduction for necessary boilerplate
            }
            _ => {
                // Algorithmic complexity remains a concern
                if context.complexity.cognitive_complexity > 30 {
                    0.9 // Only 10% reduction for very complex logic
                } else if context.complexity.cognitive_complexity > 20 {
                    0.8 // 20% reduction for complex logic
                } else {
                    0.7 // 30% reduction for moderate complexity
                }
            }
        }
    }

    /// Calculate additional reduction for well-tested pattern code.
    ///
    /// Coverage is expected in percentage format (0-100), not fraction format.
    fn calculate_coverage_reduction(&self, context: &RiskContext) -> f64 {
        if !context.is_recognized_pattern {
            return 1.0; // No additional reduction for non-patterns
        }

        match context.coverage {
            Some(cov) => {
                // Coverage is in percentage format (0-100)
                if cov >= 90.0 {
                    0.8 // Well tested: 20% additional reduction
                } else if cov >= 70.0 {
                    0.9 // Tested: 10% additional reduction
                } else {
                    1.0 // No additional reduction
                }
            }
            None => 1.0, // No coverage: no additional reduction
        }
    }

    fn estimate_test_effort(&self, complexity: &ComplexityMetrics) -> TestEffort {
        TestEffort {
            estimated_difficulty: Self::classify_difficulty(complexity.cognitive_complexity),
            cognitive_load: complexity.cognitive_complexity,
            branch_count: complexity.cyclomatic_complexity,
            recommended_test_cases: Self::calculate_test_cases(complexity.cyclomatic_complexity),
        }
    }

    fn classify_difficulty(cognitive: u32) -> Difficulty {
        const THRESHOLDS: [(u32, Difficulty); 5] = [
            (4, Difficulty::Trivial),
            (10, Difficulty::Simple),
            (20, Difficulty::Moderate),
            (40, Difficulty::Complex),
            (u32::MAX, Difficulty::VeryComplex),
        ];

        THRESHOLDS
            .iter()
            .find(|(threshold, _)| cognitive <= *threshold)
            .map(|(_, difficulty)| difficulty.clone())
            .unwrap_or(Difficulty::VeryComplex)
    }

    fn calculate_test_cases(cyclomatic: u32) -> u32 {
        const MAPPINGS: [(u32, u32); 6] =
            [(3, 1), (7, 2), (10, 3), (15, 5), (20, 7), (u32::MAX, 10)];

        MAPPINGS
            .iter()
            .find(|(threshold, _)| cyclomatic <= *threshold)
            .map(|(_, cases)| *cases)
            .unwrap_or(10)
    }

    fn categorize_risk(
        &self,
        complexity: &ComplexityMetrics,
        coverage: Option<f64>,
        risk_score: f64,
        is_test: bool,
    ) -> RiskCategory {
        let avg_complexity =
            (complexity.cyclomatic_complexity + complexity.cognitive_complexity) / 2;

        match (is_test, coverage) {
            // Test functions: categorize by complexity alone
            (true, _) => categorize_by_complexity_for_test(avg_complexity),
            // Well-tested complex functions
            (false, Some(cov)) if avg_complexity > 10 && cov > 80.0 => RiskCategory::WellTested,
            // Unknown coverage: use complexity-based categorization
            (false, None) => categorize_by_complexity(avg_complexity),
            // Known coverage: use risk score
            (false, Some(_)) => categorize_by_risk_score(risk_score),
        }
    }
}

// Pure helper functions for risk categorization
fn categorize_by_complexity_for_test(avg_complexity: u32) -> RiskCategory {
    match avg_complexity {
        c if c > 20 => RiskCategory::High,   // Very complex test
        c if c > 10 => RiskCategory::Medium, // Complex test
        c if c > 5 => RiskCategory::Low,     // Moderately complex test
        _ => RiskCategory::Low,              // Simple test
    }
}

fn categorize_by_complexity(avg_complexity: u32) -> RiskCategory {
    match avg_complexity {
        c if c > 15 => RiskCategory::Critical,
        c if c > 10 => RiskCategory::High,
        c if c > 5 => RiskCategory::Medium,
        _ => RiskCategory::Low,
    }
}

fn categorize_by_risk_score(risk_score: f64) -> RiskCategory {
    match risk_score {
        r if r >= 8.0 => RiskCategory::Critical,
        r if r >= 6.0 => RiskCategory::High,
        r if r >= 4.0 => RiskCategory::Medium,
        _ => RiskCategory::Low,
    }
}

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

    fn create_test_context(
        cyclomatic: u32,
        cognitive: u32,
        coverage: Option<f64>,
        debt_score: Option<f64>,
    ) -> RiskContext {
        RiskContext {
            file: PathBuf::from("test.rs"),
            function_name: "test_function".to_string(),
            line_range: (1, 100),
            complexity: ComplexityMetrics {
                functions: vec![],
                cyclomatic_complexity: cyclomatic,
                cognitive_complexity: cognitive,
            },
            coverage,
            debt_score,
            debt_threshold: Some(100.0),
            is_test: false,
            is_recognized_pattern: false,
            pattern_type: None,
            pattern_confidence: 0.0,
        }
    }

    #[test]
    fn test_enhanced_strategy_no_coverage_high_complexity() {
        let strategy = EnhancedRiskStrategy::default();
        let context = create_test_context(20, 25, None, None);

        let risk = strategy.calculate(&context);

        // With no coverage, risk score is based on complexity only
        // No penalty is applied, so risk score should be moderate
        assert!(
            risk.risk_score > 1.0,
            "High complexity should still have some risk even without coverage data (got {})",
            risk.risk_score
        );
        // Average complexity (20+25)/2 = 22.5, which is > 15, so Critical
        assert_eq!(risk.risk_category, RiskCategory::Critical);
    }

    #[test]
    fn test_enhanced_strategy_low_coverage_high_debt() {
        let strategy = EnhancedRiskStrategy::default();
        let context = create_test_context(15, 20, Some(20.0), Some(500.0));

        let risk = strategy.calculate(&context);

        assert!(
            risk.risk_score > 7.0,
            "Low coverage with high debt should have very high risk"
        );
        assert_eq!(risk.risk_category, RiskCategory::Critical);
    }

    #[test]
    fn test_enhanced_strategy_good_coverage_low_complexity() {
        let strategy = EnhancedRiskStrategy::default();
        let context = create_test_context(5, 5, Some(85.0), Some(50.0));

        let risk = strategy.calculate(&context);

        assert!(
            risk.risk_score < 2.0,
            "Good coverage with low complexity should have low risk"
        );
        assert_eq!(risk.risk_category, RiskCategory::Low);
    }

    #[test]
    fn test_enhanced_strategy_well_tested_complex() {
        let strategy = EnhancedRiskStrategy::default();
        let context = create_test_context(15, 15, Some(90.0), Some(50.0));

        let risk = strategy.calculate(&context);

        assert_eq!(risk.risk_category, RiskCategory::WellTested);
        assert!(
            risk.risk_score < 3.0,
            "Well-tested complex code should have reduced risk"
        );
    }

    #[test]
    fn test_coverage_penalty_calculations() {
        let strategy = EnhancedRiskStrategy::default();

        assert_eq!(strategy.calculate_coverage_penalty(None), 1.0);
        assert_eq!(strategy.calculate_coverage_penalty(Some(10.0)), 3.0);
        assert_eq!(strategy.calculate_coverage_penalty(Some(30.0)), 2.0);
        assert_eq!(strategy.calculate_coverage_penalty(Some(50.0)), 1.5);
        assert_eq!(strategy.calculate_coverage_penalty(Some(70.0)), 1.2);
        assert_eq!(strategy.calculate_coverage_penalty(Some(85.0)), 0.8);
    }

    #[test]
    fn test_debt_factor_calculations() {
        let strategy = EnhancedRiskStrategy::default();

        assert_eq!(strategy.calculate_debt_factor(Some(50.0), Some(100.0)), 1.0);
        assert_eq!(
            strategy.calculate_debt_factor(Some(150.0), Some(100.0)),
            1.2
        );
        assert_eq!(
            strategy.calculate_debt_factor(Some(400.0), Some(100.0)),
            1.5
        );
        assert_eq!(
            strategy.calculate_debt_factor(Some(900.0), Some(100.0)),
            2.0
        );
        assert_eq!(
            strategy.calculate_debt_factor(Some(1500.0), Some(100.0)),
            2.5
        );
    }

    #[test]
    fn test_risk_score_max_cap() {
        let strategy = EnhancedRiskStrategy::default();
        // Extreme values to test max cap
        let context = create_test_context(100, 100, None, Some(10000.0));

        let risk_score = strategy.calculate_risk_score(&context);

        // With spec 96, scores are no longer capped at 10.0
        // Very high risk can exceed 10.0
        assert!(
            risk_score > 10.0,
            "Risk score with extreme values should exceed 10.0"
        );
    }

    #[test]
    fn test_risk_reduction_calculation() {
        let enhanced = EnhancedRiskStrategy::default();

        let enhanced_reduction = enhanced.calculate_risk_reduction(8.0, 20, 80.0);

        assert!(
            enhanced_reduction < 8.0,
            "Enhanced should show significant reduction"
        );
    }

    #[test]
    fn test_test_effort_estimation() {
        let strategy = EnhancedRiskStrategy::default();
        let complexity = ComplexityMetrics {
            functions: vec![],
            cyclomatic_complexity: 15,
            cognitive_complexity: 25,
        };

        let effort = strategy.estimate_test_effort(&complexity);

        assert_eq!(effort.cognitive_load, 25);
        assert_eq!(effort.branch_count, 15);
        assert_eq!(effort.recommended_test_cases, 5);
        assert!(matches!(effort.estimated_difficulty, Difficulty::Complex));
    }

    #[test]
    fn test_risk_categorization_thresholds() {
        let strategy = EnhancedRiskStrategy::default();

        // Test various risk score thresholds
        let cases = vec![
            (9.0, RiskCategory::Critical),
            (7.0, RiskCategory::High),
            (5.0, RiskCategory::Medium),
            (2.0, RiskCategory::Low),
        ];

        for (score, expected_category) in cases {
            let complexity = ComplexityMetrics {
                functions: vec![],
                cyclomatic_complexity: 10,
                cognitive_complexity: 10,
            };
            let category = strategy.categorize_risk(&complexity, Some(50.0), score, false);
            assert_eq!(
                category, expected_category,
                "Score {score} should map to {expected_category:?}"
            );
        }
    }

    #[test]
    fn test_categorize_by_complexity_for_test() {
        assert_eq!(categorize_by_complexity_for_test(25), RiskCategory::High);
        assert_eq!(categorize_by_complexity_for_test(15), RiskCategory::Medium);
        assert_eq!(categorize_by_complexity_for_test(7), RiskCategory::Low);
        assert_eq!(categorize_by_complexity_for_test(3), RiskCategory::Low);
    }

    #[test]
    fn test_categorize_by_complexity() {
        assert_eq!(categorize_by_complexity(20), RiskCategory::Critical);
        assert_eq!(categorize_by_complexity(12), RiskCategory::High);
        assert_eq!(categorize_by_complexity(7), RiskCategory::Medium);
        assert_eq!(categorize_by_complexity(3), RiskCategory::Low);
    }

    #[test]
    fn test_categorize_by_risk_score() {
        assert_eq!(categorize_by_risk_score(9.0), RiskCategory::Critical);
        assert_eq!(categorize_by_risk_score(7.0), RiskCategory::High);
        assert_eq!(categorize_by_risk_score(5.0), RiskCategory::Medium);
        assert_eq!(categorize_by_risk_score(2.0), RiskCategory::Low);
    }

    /// Bug fix test: Coverage values should be consistently interpreted.
    /// The old code used a heuristic `if cov <= 1.0` to guess format, but this
    /// fails for actual percentage values between 0-1 (e.g., 0.5% coverage).
    ///
    /// Coverage should be in percentage format (0-100), not fraction format.
    #[test]
    fn test_coverage_format_consistency() {
        let strategy = EnhancedRiskStrategy::default();

        // 1% coverage should be treated as very low coverage (penalty 3.0)
        // NOT as 100% coverage (which would get penalty 0.8)
        let one_percent_penalty = strategy.calculate_coverage_penalty(Some(1.0));
        assert_eq!(
            one_percent_penalty, 3.0,
            "1.0 should be treated as 1% coverage (very low), not 100%"
        );

        // 0.5% coverage should also be treated as very low
        let half_percent_penalty = strategy.calculate_coverage_penalty(Some(0.5));
        assert_eq!(
            half_percent_penalty, 3.0,
            "0.5 should be treated as 0.5% coverage (very low), not 50%"
        );

        // Explicit percentages should work correctly
        assert_eq!(
            strategy.calculate_coverage_penalty(Some(85.0)),
            0.8,
            "85.0 should be treated as 85% coverage (good)"
        );
        assert_eq!(
            strategy.calculate_coverage_penalty(Some(50.0)),
            1.5,
            "50.0 should be treated as 50% coverage (medium)"
        );
    }
}
