// File type aware scoring configuration
// Implements: zshrc-refactor-spec-improvements.md Phase 2

use crate::bash_quality::linter::suppressions::FileType;

/// Scoring weights for different file types
#[derive(Debug, Clone)]
pub struct ScoringWeights {
    pub test_coverage_weight: f64,
    pub function_complexity_weight: f64,
    pub lint_warnings_weight: f64,
    pub determinism_weight: f64,
}

impl ScoringWeights {
    /// Get scoring weights appropriate for file type
    pub fn for_file_type(file_type: FileType) -> Self {
        match file_type {
            FileType::Script => Self {
                test_coverage_weight: 0.30,
                function_complexity_weight: 0.25,
                lint_warnings_weight: 0.25,
                determinism_weight: 0.20,
            },
            FileType::Config => Self {
                // Config files have different quality standards
                test_coverage_weight: 0.20,      // Lower (tests optional)
                function_complexity_weight: 0.45, // Much higher (readability key for configs)
                lint_warnings_weight: 0.25,      // Moderate (some false positives expected)
                determinism_weight: 0.10,        // Lower (interactive features OK)
            },
            FileType::Library => Self {
                test_coverage_weight: 0.40,      // Highest (libraries must be tested)
                function_complexity_weight: 0.30,
                lint_warnings_weight: 0.20,
                determinism_weight: 0.10,
            },
        }
    }
}

/// Grade thresholds for different file types
pub fn grade_thresholds(file_type: FileType) -> Vec<(f64, &'static str)> {
    match file_type {
        FileType::Script => {
            // Strict thresholds for production scripts
            vec![
                (9.5, "A+"),
                (9.0, "A"),
                (8.5, "A-"),
                (8.0, "B+"),
                (7.5, "B"),
                (7.0, "B-"),
                (6.5, "C+"),
                (6.0, "C"),
                (5.5, "C-"),
                (5.0, "D"),
                (0.0, "F"),
            ]
        },
        FileType::Config => {
            // More lenient for config files
            vec![
                (9.0, "A+"),
                (8.5, "A"),
                (8.0, "A-"),
                (7.5, "B+"),
                (7.0, "B"),
                (6.5, "B-"),
                (6.0, "C+"),
                (5.5, "C"),
                (5.0, "C-"),
                (4.5, "D"),
                (0.0, "F"),
            ]
        },
        FileType::Library => {
            // Medium thresholds for libraries
            vec![
                (9.3, "A+"),
                (8.8, "A"),
                (8.3, "A-"),
                (7.8, "B+"),
                (7.3, "B"),
                (6.8, "B-"),
                (6.3, "C+"),
                (5.8, "C"),
                (5.3, "C-"),
                (4.8, "D"),
                (0.0, "F"),
            ]
        },
    }
}

/// Calculate grade based on score and file type
pub fn calculate_grade(score: f64, file_type: FileType) -> String {
    let thresholds = grade_thresholds(file_type);

    for (threshold, grade) in thresholds {
        if score >= threshold {
            return grade.to_string();
        }
    }

    "F".to_string()
}

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

    #[test]
    fn test_scoring_weights_script() {
        let weights = ScoringWeights::for_file_type(FileType::Script);
        assert_eq!(weights.test_coverage_weight, 0.30);
        assert_eq!(weights.lint_warnings_weight, 0.25);
    }

    #[test]
    fn test_scoring_weights_config() {
        let weights = ScoringWeights::for_file_type(FileType::Config);
        assert_eq!(weights.test_coverage_weight, 0.20);
        assert_eq!(weights.function_complexity_weight, 0.45); // Much higher for configs
        assert_eq!(weights.lint_warnings_weight, 0.25); // Moderate for configs
        assert_eq!(weights.determinism_weight, 0.10);    // Much lower

        // Verify weights sum to 1.0
        let sum = weights.test_coverage_weight + weights.function_complexity_weight
            + weights.lint_warnings_weight + weights.determinism_weight;
        assert!((sum - 1.0).abs() < 0.001);
    }

    #[test]
    fn test_grade_calculation_config() {
        // 8.3 should be A- for config files (threshold 8.0)
        let grade = calculate_grade(8.3, FileType::Config);
        assert_eq!(grade, "A-");

        // Same score is B+ for scripts (threshold 8.5)
        let grade = calculate_grade(8.3, FileType::Script);
        assert_eq!(grade, "B+");
    }

    #[test]
    fn test_grade_calculation_boundaries() {
        assert_eq!(calculate_grade(9.0, FileType::Config), "A+");
        assert_eq!(calculate_grade(8.5, FileType::Config), "A");
        assert_eq!(calculate_grade(8.0, FileType::Config), "A-");
        assert_eq!(calculate_grade(7.5, FileType::Config), "B+");
    }

    // Property-based tests
    #[cfg(test)]
    mod property_tests {
        use super::*;
        use proptest::prelude::*;

        proptest! {
            #[test]
            fn prop_weights_sum_to_one(file_type in prop::sample::select(vec![FileType::Script, FileType::Config, FileType::Library])) {
                let weights = ScoringWeights::for_file_type(file_type);
                let sum = weights.test_coverage_weight
                    + weights.function_complexity_weight
                    + weights.lint_warnings_weight
                    + weights.determinism_weight;

                // Weights should approximately sum to 1.0 (allowing for floating point errors)
                prop_assert!((sum - 1.0).abs() < 0.001, "Weights sum to {}, expected ~1.0", sum);
            }

            #[test]
            fn prop_all_weights_positive(file_type in prop::sample::select(vec![FileType::Script, FileType::Config, FileType::Library])) {
                let weights = ScoringWeights::for_file_type(file_type);

                prop_assert!(weights.test_coverage_weight > 0.0);
                prop_assert!(weights.function_complexity_weight > 0.0);
                prop_assert!(weights.lint_warnings_weight > 0.0);
                prop_assert!(weights.determinism_weight > 0.0);
            }

            #[test]
            fn prop_config_weights_more_lenient(file_type in prop::sample::select(vec![FileType::Script, FileType::Config])) {
                let weights = ScoringWeights::for_file_type(file_type);

                if matches!(file_type, FileType::Config) {
                    // Config files should have lower determinism weight
                    prop_assert!(weights.determinism_weight < 0.15);
                    // Config files should have higher complexity weight (readability key)
                    prop_assert!(weights.function_complexity_weight > 0.40);
                }
            }

            #[test]
            fn prop_grades_monotonic(score in 0.0f64..10.0) {
                // Higher scores should never result in lower grades
                let grade1 = calculate_grade(score, FileType::Script);
                let grade2 = calculate_grade(score + 0.1, FileType::Script);

                // Grade values (for comparison)
                let grade_values = |g: &str| match g {
                    "A+" => 11,
                    "A" => 10,
                    "A-" => 9,
                    "B+" => 8,
                    "B" => 7,
                    "B-" => 6,
                    "C+" => 5,
                    "C" => 4,
                    "C-" => 3,
                    "D" => 2,
                    "F" => 1,
                    _ => 0,
                };

                prop_assert!(grade_values(&grade2) >= grade_values(&grade1),
                    "Score {} -> {}, but {} + 0.1 -> {} (should be >= in grade)",
                    score, grade1, score, grade2);
            }

            #[test]
            fn prop_perfect_score_is_a_plus(file_type in prop::sample::select(vec![FileType::Script, FileType::Config, FileType::Library])) {
                let grade = calculate_grade(10.0, file_type);
                prop_assert_eq!(grade, "A+");
            }

            #[test]
            fn prop_zero_score_is_f(file_type in prop::sample::select(vec![FileType::Script, FileType::Config, FileType::Library])) {
                let grade = calculate_grade(0.0, file_type);
                prop_assert_eq!(grade, "F");
            }

            #[test]
            fn prop_config_more_lenient_than_script(score in 7.0f64..9.0) {
                let config_grade = calculate_grade(score, FileType::Config);
                let script_grade = calculate_grade(score, FileType::Script);

                // Grade values for comparison
                let grade_value = |g: &str| match g {
                    "A+" => 11, "A" => 10, "A-" => 9, "B+" => 8, "B" => 7,
                    "B-" => 6, "C+" => 5, "C" => 4, "C-" => 3, "D" => 2, "F" => 1, _ => 0,
                };

                // Config should get same or better grade for same score
                prop_assert!(grade_value(&config_grade) >= grade_value(&script_grade),
                    "Score {} -> Config: {}, Script: {} (config should be >= script)",
                    score, config_grade, script_grade);
            }

            #[test]
            fn prop_grade_thresholds_consistent(file_type in prop::sample::select(vec![FileType::Script, FileType::Config, FileType::Library])) {
                let thresholds = grade_thresholds(file_type);

                // Thresholds should be in descending order
                for i in 0..thresholds.len()-1 {
                    prop_assert!(thresholds[i].0 > thresholds[i+1].0,
                        "Threshold {} ({}) should be > threshold {} ({})",
                        i, thresholds[i].0, i+1, thresholds[i+1].0);
                }

                // First threshold should be highest
                prop_assert!(thresholds[0].0 >= 8.0, "Highest threshold should be >= 8.0 (A grade territory)");

                // Last threshold should be 0.0 (F grade)
                prop_assert_eq!(thresholds.last().unwrap().0, 0.0);
                prop_assert_eq!(thresholds.last().unwrap().1, "F");
            }
        }
    }
}
