//! Quality Gate System for TDG Enforcement (Sprint 66 Phase 2)
//!
//! This module provides quality gates that can enforce quality standards
//! by detecting regressions, enforcing minimum grades, and validating new files.

use crate::tdg::{Grade, TdgBaseline};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;

/// Result of running a quality gate check
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateResult {
    /// Whether the gate passed
    pub passed: bool,
    /// Gate that was executed
    pub gate_name: String,
    /// Violations found (empty if passed)
    pub violations: Vec<Violation>,
    /// Summary message
    pub message: String,
}

/// A single quality gate violation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Violation {
    /// File that violated the gate
    pub path: PathBuf,
    /// Type of violation
    pub violation_type: ViolationType,
    /// Severity of violation
    pub severity: Severity,
    /// Detailed message
    pub message: String,
    /// Old score (for regressions)
    pub old_score: Option<f32>,
    /// New score
    pub new_score: f32,
    /// Old grade (for regressions)
    pub old_grade: Option<Grade>,
    /// New grade
    pub new_grade: Grade,
}

/// Type of quality gate violation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ViolationType {
    /// Quality regression detected
    Regression,
    /// File below minimum grade
    BelowMinimum,
    /// New file below threshold
    NewFileBelowThreshold,
}

/// Severity level for violations
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Severity {
    /// Informational (doesn't fail gate)
    Info,
    /// Warning (logs but doesn't fail)
    Warning,
    /// Error (fails the gate)
    Error,
    /// Critical (fails gate with high priority)
    Critical,
}

/// Configuration for quality gates
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateConfig {
    /// Maximum score drop allowed before flagging regression
    pub max_score_drop: f32,
    /// Whether to allow grade drops (e.g., A → B)
    pub allow_grade_drop: bool,
    /// Minimum grades by language
    pub min_grades: HashMap<String, Grade>,
    /// Default minimum grade if language not specified
    pub default_min_grade: Grade,
    /// Whether to enforce quality on new files
    pub enforce_new_files: bool,
    /// Minimum grade for new files
    pub new_file_min_grade: Grade,
}

impl Default for GateConfig {
    fn default() -> Self {
        let mut min_grades = HashMap::new();
        min_grades.insert("rust".to_string(), Grade::BPlus);
        min_grades.insert("typescript".to_string(), Grade::BPlus);
        min_grades.insert("python".to_string(), Grade::B);
        min_grades.insert("javascript".to_string(), Grade::B);

        Self {
            max_score_drop: 5.0,
            allow_grade_drop: false,
            min_grades,
            default_min_grade: Grade::B,
            enforce_new_files: true,
            new_file_min_grade: Grade::B,
        }
    }
}

/// Trait for quality gates
pub trait QualityGate {
    /// Name of this gate
    fn name(&self) -> &str;

    /// Run the gate check
    fn check(&self, baseline: &TdgBaseline, current: &TdgBaseline) -> Result<GateResult>;
}

/// Regression detection gate
pub struct RegressionGate {
    config: GateConfig,
}

impl RegressionGate {
    pub fn new(config: GateConfig) -> Self {
        Self { config }
    }

    pub fn with_defaults() -> Self {
        Self::new(GateConfig::default())
    }
}

impl QualityGate for RegressionGate {
    fn name(&self) -> &str {
        "RegressionGate"
    }

    fn check(&self, baseline: &TdgBaseline, current: &TdgBaseline) -> Result<GateResult> {
        let comparison = baseline.compare(current);
        let mut violations = Vec::new();

        for regressed in &comparison.regressed {
            let score_drop = regressed.old_score.total - regressed.new_score.total;
            let grade_dropped = regressed.old_score.grade < regressed.new_score.grade;

            // Check if this violates our thresholds
            let is_violation = if !self.config.allow_grade_drop && grade_dropped {
                true // Grade drop not allowed
            } else {
                score_drop > self.config.max_score_drop
            };

            if is_violation {
                let severity = if grade_dropped {
                    if score_drop > 15.0 {
                        Severity::Critical
                    } else {
                        Severity::Error
                    }
                } else if score_drop > 10.0 {
                    Severity::Error
                } else {
                    Severity::Warning
                };

                violations.push(Violation {
                    path: regressed.path.clone(),
                    violation_type: ViolationType::Regression,
                    severity,
                    message: format!(
                        "Quality regression: {} ({:.1}) → {} ({:.1}) [{}]",
                        regressed.grade_change.0,
                        regressed.old_score.total,
                        regressed.grade_change.1,
                        regressed.new_score.total,
                        format_delta(regressed.delta)
                    ),
                    old_score: Some(regressed.old_score.total),
                    new_score: regressed.new_score.total,
                    old_grade: Some(regressed.grade_change.0),
                    new_grade: regressed.grade_change.1,
                });
            }
        }

        let passed = violations.is_empty();
        let message = if passed {
            format!(
                "✅ No quality regressions detected ({} files analyzed)",
                current.summary.total_files
            )
        } else {
            format!("❌ {} quality regression(s) detected", violations.len())
        };

        Ok(GateResult {
            passed,
            gate_name: self.name().to_string(),
            violations,
            message,
        })
    }
}

/// Minimum grade enforcement gate
pub struct MinimumGradeGate {
    config: GateConfig,
}

impl MinimumGradeGate {
    pub fn new(config: GateConfig) -> Self {
        Self { config }
    }

    pub fn with_defaults() -> Self {
        Self::new(GateConfig::default())
    }

    /// Get minimum grade for a file based on its language
    fn get_min_grade_for_file(&self, path: &PathBuf) -> Grade {
        // Detect language from extension
        if let Some(ext) = path.extension() {
            let ext_str = ext.to_string_lossy();
            let language = match ext_str.as_ref() {
                "rs" => "rust",
                "ts" | "tsx" => "typescript",
                "js" | "jsx" => "javascript",
                "py" => "python",
                "go" => "go",
                "java" => "java",
                "rb" => "ruby",
                "php" => "php",
                "swift" => "swift",
                "kt" | "kts" => "kotlin",
                _ => return self.config.default_min_grade,
            };

            self.config
                .min_grades
                .get(language)
                .copied()
                .unwrap_or(self.config.default_min_grade)
        } else {
            self.config.default_min_grade
        }
    }
}

impl QualityGate for MinimumGradeGate {
    fn name(&self) -> &str {
        "MinimumGradeGate"
    }

    fn check(&self, _baseline: &TdgBaseline, current: &TdgBaseline) -> Result<GateResult> {
        let mut violations = Vec::new();

        for (path, entry) in &current.files {
            let min_grade = self.get_min_grade_for_file(path);
            if entry.score.grade > min_grade {
                violations.push(Violation {
                    path: path.clone(),
                    violation_type: ViolationType::BelowMinimum,
                    severity: Severity::Error,
                    message: format!(
                        "Below minimum grade: {} ({:.1}) < required {}",
                        entry.score.grade, entry.score.total, min_grade
                    ),
                    old_score: None,
                    new_score: entry.score.total,
                    old_grade: None,
                    new_grade: entry.score.grade,
                });
            }
        }

        let passed = violations.is_empty();
        let message = if passed {
            format!(
                "✅ All files meet minimum grade requirements ({} files)",
                current.summary.total_files
            )
        } else {
            format!(
                "❌ {} file(s) below minimum grade threshold",
                violations.len()
            )
        };

        Ok(GateResult {
            passed,
            gate_name: self.name().to_string(),
            violations,
            message,
        })
    }
}

/// New file quality gate
pub struct NewFileGate {
    config: GateConfig,
}

impl NewFileGate {
    pub fn new(config: GateConfig) -> Self {
        Self { config }
    }

    pub fn with_defaults() -> Self {
        Self::new(GateConfig::default())
    }
}

impl QualityGate for NewFileGate {
    fn name(&self) -> &str {
        "NewFileGate"
    }

    fn check(&self, baseline: &TdgBaseline, current: &TdgBaseline) -> Result<GateResult> {
        if !self.config.enforce_new_files {
            return Ok(GateResult {
                passed: true,
                gate_name: self.name().to_string(),
                violations: Vec::new(),
                message: "✅ New file enforcement disabled".to_string(),
            });
        }

        let comparison = baseline.compare(current);
        let mut violations = Vec::new();

        // Check newly added files
        for added_path in &comparison.added {
            if let Some(entry) = current.files.get(added_path) {
                if entry.score.grade > self.config.new_file_min_grade {
                    violations.push(Violation {
                        path: added_path.clone(),
                        violation_type: ViolationType::NewFileBelowThreshold,
                        severity: Severity::Error,
                        message: format!(
                            "New file below minimum grade: {} ({:.1}) < required {}",
                            entry.score.grade, entry.score.total, self.config.new_file_min_grade
                        ),
                        old_score: None,
                        new_score: entry.score.total,
                        old_grade: None,
                        new_grade: entry.score.grade,
                    });
                }
            }
        }

        let passed = violations.is_empty();
        let message = if passed {
            if comparison.added.is_empty() {
                "✅ No new files added".to_string()
            } else {
                format!(
                    "✅ All {} new file(s) meet quality threshold",
                    comparison.added.len()
                )
            }
        } else {
            format!(
                "❌ {} new file(s) below quality threshold",
                violations.len()
            )
        };

        Ok(GateResult {
            passed,
            gate_name: self.name().to_string(),
            violations,
            message,
        })
    }
}

/// F-Grade detection gate - fails when any F-grade files exist
///
/// This gate addresses the "hidden F-grade" problem where averaging
/// can mask critical quality issues. Any file with F grade (<50 points)
/// indicates severe technical debt that should be addressed before
/// the project can achieve high quality ratings.
pub struct FGradeGate {
    /// Maximum allowed F-grade files (default: 0)
    max_f_grades: usize,
}

impl FGradeGate {
    pub fn new(max_f_grades: usize) -> Self {
        Self { max_f_grades }
    }

    /// Create with strict defaults (zero F-grades allowed)
    pub fn with_defaults() -> Self {
        Self::new(0)
    }

    /// Create with relaxed policy (allows some F-grades during migration)
    pub fn with_migration_policy(max_f_grades: usize) -> Self {
        Self::new(max_f_grades)
    }
}

impl QualityGate for FGradeGate {
    fn name(&self) -> &str {
        "FGradeGate"
    }

    fn check(&self, _baseline: &TdgBaseline, current: &TdgBaseline) -> Result<GateResult> {
        let mut violations = Vec::new();
        let mut f_grade_count = 0;

        for (path, entry) in &current.files {
            if entry.score.grade == Grade::F {
                f_grade_count += 1;
                violations.push(Violation {
                    path: path.clone(),
                    violation_type: ViolationType::BelowMinimum,
                    severity: Severity::Critical,
                    message: format!(
                        "F-grade file: {} ({:.1} points) - requires immediate attention",
                        entry.score.grade, entry.score.total
                    ),
                    old_score: None,
                    new_score: entry.score.total,
                    old_grade: None,
                    new_grade: entry.score.grade,
                });
            }
        }

        let passed = f_grade_count <= self.max_f_grades;
        let message = if passed {
            if f_grade_count == 0 {
                format!(
                    "✅ No F-grade files detected ({} files analyzed)",
                    current.summary.total_files
                )
            } else {
                format!(
                    "⚠️ {} F-grade file(s) detected (within migration limit of {})",
                    f_grade_count, self.max_f_grades
                )
            }
        } else {
            format!(
                "❌ {} F-grade file(s) detected (max allowed: {}). \
                F-grades cap project score at B. Fix these files to improve project grade.",
                f_grade_count, self.max_f_grades
            )
        };

        Ok(GateResult {
            passed,
            gate_name: self.name().to_string(),
            violations,
            message,
        })
    }
}

/// Format a score delta for display
fn format_delta(delta: f32) -> String {
    if delta >= 0.0 {
        format!("+{:.1}", delta)
    } else {
        format!("{:.1}", delta)
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;
    use crate::tdg::{BaselineEntry, ComponentScores, TdgScore};

    fn create_test_baseline(scores: Vec<(PathBuf, f32, Grade)>) -> TdgBaseline {
        use crate::tdg::Language;
        let mut baseline = TdgBaseline::new(None);
        for (path, score, grade) in scores {
            let entry = BaselineEntry {
                content_hash: blake3::hash(b"test"),
                score: TdgScore {
                    total: score,
                    grade,
                    structural_complexity: score,
                    semantic_complexity: score,
                    duplication_ratio: 0.0,
                    coupling_score: score,
                    doc_coverage: score,
                    consistency_score: score,
                    entropy_score: score,
                    confidence: 1.0,
                    language: Language::Rust,
                    file_path: Some(path.clone()),
                    penalties_applied: Vec::new(),
                    critical_defects_count: 0,
                    has_critical_defects: false,
                },
                components: ComponentScores::default(),
                git_context: None,
            };
            baseline.add_entry(path, entry);
        }
        baseline
    }

    #[test]
    fn test_regression_gate_detects_score_drop() {
        let baseline = create_test_baseline(vec![(PathBuf::from("src/main.rs"), 90.0, Grade::A)]);

        let current = create_test_baseline(vec![(PathBuf::from("src/main.rs"), 75.0, Grade::B)]);

        let gate = RegressionGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 1);
        assert_eq!(
            result.violations[0].violation_type,
            ViolationType::Regression
        );
    }

    #[test]
    fn test_regression_gate_passes_improvement() {
        let baseline = create_test_baseline(vec![(PathBuf::from("src/main.rs"), 75.0, Grade::B)]);

        let current = create_test_baseline(vec![(PathBuf::from("src/main.rs"), 90.0, Grade::A)]);

        let gate = RegressionGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed);
        assert_eq!(result.violations.len(), 0);
    }

    #[test]
    fn test_regression_gate_allows_small_drop() {
        let baseline = create_test_baseline(vec![(PathBuf::from("src/main.rs"), 90.0, Grade::A)]);

        let current = create_test_baseline(vec![(PathBuf::from("src/main.rs"), 87.0, Grade::A)]);

        let gate = RegressionGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed); // 3 point drop is within threshold
    }

    #[test]
    fn test_minimum_grade_gate_enforces_threshold() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/good.rs"), 90.0, Grade::A),
            (PathBuf::from("src/bad.rs"), 70.0, Grade::C),
        ]);

        let gate = MinimumGradeGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 1);
        assert_eq!(result.violations[0].path, PathBuf::from("src/bad.rs"));
    }

    #[test]
    fn test_minimum_grade_gate_passes_all_above_threshold() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/good1.rs"), 90.0, Grade::A),
            (PathBuf::from("src/good2.rs"), 85.0, Grade::BPlus),
        ]);

        let gate = MinimumGradeGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed);
    }

    #[test]
    fn test_new_file_gate_detects_low_quality_new_files() {
        let baseline =
            create_test_baseline(vec![(PathBuf::from("src/existing.rs"), 90.0, Grade::A)]);

        let current = create_test_baseline(vec![
            (PathBuf::from("src/existing.rs"), 90.0, Grade::A),
            (PathBuf::from("src/new_bad.rs"), 65.0, Grade::D),
        ]);

        let gate = NewFileGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 1);
        assert_eq!(
            result.violations[0].violation_type,
            ViolationType::NewFileBelowThreshold
        );
    }

    #[test]
    fn test_new_file_gate_allows_good_new_files() {
        let baseline =
            create_test_baseline(vec![(PathBuf::from("src/existing.rs"), 90.0, Grade::A)]);

        let current = create_test_baseline(vec![
            (PathBuf::from("src/existing.rs"), 90.0, Grade::A),
            (PathBuf::from("src/new_good.rs"), 85.0, Grade::BPlus),
        ]);

        let gate = NewFileGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed);
    }

    #[test]
    fn test_multiple_violations() {
        let baseline = create_test_baseline(vec![
            (PathBuf::from("src/file1.rs"), 90.0, Grade::A),
            (PathBuf::from("src/file2.rs"), 85.0, Grade::BPlus),
        ]);

        let current = create_test_baseline(vec![
            (PathBuf::from("src/file1.rs"), 70.0, Grade::C),
            (PathBuf::from("src/file2.rs"), 68.0, Grade::C),
        ]);

        let gate = RegressionGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 2);
    }

    #[test]
    fn test_grade_drop_not_allowed() {
        let mut config = GateConfig::default();
        config.allow_grade_drop = false;

        let baseline = create_test_baseline(vec![(PathBuf::from("src/main.rs"), 91.0, Grade::A)]);

        let current =
            create_test_baseline(vec![(PathBuf::from("src/main.rs"), 89.0, Grade::BPlus)]);

        let gate = RegressionGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed); // Grade dropped from A to B+
    }

    #[test]
    fn test_language_specific_min_grades() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/main.rs"), 88.0, Grade::BPlus), // Rust requires B+
            (PathBuf::from("src/script.py"), 82.0, Grade::B),   // Python requires B
            (PathBuf::from("src/app.js"), 75.0, Grade::C),      // JS requires B (FAIL)
        ]);

        let gate = MinimumGradeGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 1);
        assert_eq!(result.violations[0].path, PathBuf::from("src/app.js"));
    }

    #[test]
    fn test_empty_baseline_comparison() {
        let baseline = TdgBaseline::new(None);
        let current = TdgBaseline::new(None);

        let gate = RegressionGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed);
        assert_eq!(result.violations.len(), 0);
    }

    #[test]
    fn test_gate_config_default() {
        let config = GateConfig::default();
        assert_eq!(config.max_score_drop, 5.0);
        assert!(!config.allow_grade_drop);
        assert!(config.enforce_new_files);
        assert_eq!(config.new_file_min_grade, Grade::B);
    }

    #[test]
    fn test_gate_config_min_grades() {
        let config = GateConfig::default();
        assert_eq!(config.min_grades.get("rust"), Some(&Grade::BPlus));
        assert_eq!(config.min_grades.get("python"), Some(&Grade::B));
        assert_eq!(config.min_grades.get("unknown"), None);
    }

    #[test]
    fn test_violation_type_equality() {
        assert_eq!(ViolationType::Regression, ViolationType::Regression);
        assert_eq!(ViolationType::BelowMinimum, ViolationType::BelowMinimum);
        assert_ne!(ViolationType::Regression, ViolationType::BelowMinimum);
    }

    #[test]
    fn test_severity_ordering() {
        assert!(Severity::Info < Severity::Warning);
        assert!(Severity::Warning < Severity::Error);
        assert!(Severity::Error < Severity::Critical);
    }

    #[test]
    fn test_severity_equality() {
        assert_eq!(Severity::Info, Severity::Info);
        assert_eq!(Severity::Critical, Severity::Critical);
    }

    #[test]
    fn test_gate_result_creation() {
        let result = GateResult {
            passed: true,
            gate_name: "TestGate".to_string(),
            violations: vec![],
            message: "All checks passed".to_string(),
        };
        assert!(result.passed);
        assert_eq!(result.gate_name, "TestGate");
    }

    #[test]
    fn test_gate_result_with_violations() {
        let violation = Violation {
            path: PathBuf::from("src/test.rs"),
            violation_type: ViolationType::Regression,
            severity: Severity::Error,
            message: "Score dropped".to_string(),
            old_score: Some(90.0),
            new_score: 75.0,
            old_grade: Some(Grade::A),
            new_grade: Grade::B,
        };
        let result = GateResult {
            passed: false,
            gate_name: "RegressionGate".to_string(),
            violations: vec![violation],
            message: "Regression detected".to_string(),
        };
        assert!(!result.passed);
        assert_eq!(result.violations.len(), 1);
    }

    #[test]
    fn test_violation_creation() {
        let violation = Violation {
            path: PathBuf::from("src/lib.rs"),
            violation_type: ViolationType::NewFileBelowThreshold,
            severity: Severity::Warning,
            message: "New file below threshold".to_string(),
            old_score: None,
            new_score: 65.0,
            old_grade: None,
            new_grade: Grade::D,
        };
        assert_eq!(violation.path, PathBuf::from("src/lib.rs"));
        assert!(violation.old_score.is_none());
        assert_eq!(violation.new_score, 65.0);
    }

    #[test]
    fn test_regression_gate_name() {
        let gate = RegressionGate::with_defaults();
        assert_eq!(gate.name(), "RegressionGate");
    }

    #[test]
    fn test_minimum_grade_gate_name() {
        let gate = MinimumGradeGate::with_defaults();
        assert_eq!(gate.name(), "MinimumGradeGate");
    }

    #[test]
    fn test_new_file_gate_name() {
        let gate = NewFileGate::with_defaults();
        assert_eq!(gate.name(), "NewFileGate");
    }

    #[test]
    fn test_regression_gate_new() {
        let config = GateConfig::default();
        let gate = RegressionGate::new(config);
        assert_eq!(gate.name(), "RegressionGate");
    }

    #[test]
    fn test_minimum_grade_gate_new() {
        let config = GateConfig::default();
        let gate = MinimumGradeGate::new(config);
        assert_eq!(gate.name(), "MinimumGradeGate");
    }

    #[test]
    fn test_new_file_gate_new() {
        let config = GateConfig::default();
        let gate = NewFileGate::new(config);
        assert_eq!(gate.name(), "NewFileGate");
    }

    #[test]
    fn test_format_delta_positive() {
        assert_eq!(format_delta(5.5), "+5.5");
        assert_eq!(format_delta(0.0), "+0.0");
        assert_eq!(format_delta(10.0), "+10.0");
    }

    #[test]
    fn test_format_delta_negative() {
        assert_eq!(format_delta(-5.5), "-5.5");
        assert_eq!(format_delta(-10.0), "-10.0");
        assert_eq!(format_delta(-0.1), "-0.1");
    }

    #[test]
    fn test_get_min_grade_for_rust_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("src/main.rs");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::BPlus);
    }

    #[test]
    fn test_get_min_grade_for_typescript_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("src/app.ts");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::BPlus);

        let tsx_path = PathBuf::from("components/App.tsx");
        assert_eq!(gate.get_min_grade_for_file(&tsx_path), Grade::BPlus);
    }

    #[test]
    fn test_get_min_grade_for_javascript_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("src/app.js");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B);

        let jsx_path = PathBuf::from("components/App.jsx");
        assert_eq!(gate.get_min_grade_for_file(&jsx_path), Grade::B);
    }

    #[test]
    fn test_get_min_grade_for_python_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("src/main.py");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B);
    }

    #[test]
    fn test_get_min_grade_for_unknown_extension() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("readme.md");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B); // default

        let unknown = PathBuf::from("data.xyz");
        assert_eq!(gate.get_min_grade_for_file(&unknown), Grade::B);
    }

    #[test]
    fn test_get_min_grade_for_no_extension() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("Makefile");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B); // default
    }

    #[test]
    fn test_get_min_grade_for_go_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("main.go");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B); // not in min_grades
    }

    #[test]
    fn test_get_min_grade_for_java_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("Main.java");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B);
    }

    #[test]
    fn test_get_min_grade_for_ruby_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("app.rb");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B);
    }

    #[test]
    fn test_get_min_grade_for_php_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("index.php");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B);
    }

    #[test]
    fn test_get_min_grade_for_swift_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("App.swift");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B);
    }

    #[test]
    fn test_get_min_grade_for_kotlin_file() {
        let gate = MinimumGradeGate::with_defaults();
        let path = PathBuf::from("Main.kt");
        assert_eq!(gate.get_min_grade_for_file(&path), Grade::B);

        let kts_path = PathBuf::from("build.gradle.kts");
        assert_eq!(gate.get_min_grade_for_file(&kts_path), Grade::B);
    }

    #[test]
    fn test_new_file_gate_disabled() {
        let mut config = GateConfig::default();
        config.enforce_new_files = false;

        let gate = NewFileGate::new(config);
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![(PathBuf::from("src/new_bad.rs"), 50.0, Grade::D)]);

        let result = gate.check(&baseline, &current).unwrap();
        assert!(result.passed); // Should pass because enforcement is disabled
        assert!(result.message.contains("disabled"));
    }

    #[test]
    fn test_new_file_gate_no_new_files() {
        let baseline =
            create_test_baseline(vec![(PathBuf::from("src/existing.rs"), 90.0, Grade::A)]);
        let current =
            create_test_baseline(vec![(PathBuf::from("src/existing.rs"), 90.0, Grade::A)]);

        let gate = NewFileGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed);
        assert!(result.message.contains("No new files"));
    }

    #[test]
    fn test_gate_config_clone() {
        let config = GateConfig::default();
        let cloned = config.clone();
        assert_eq!(cloned.max_score_drop, config.max_score_drop);
        assert_eq!(cloned.allow_grade_drop, config.allow_grade_drop);
    }

    #[test]
    fn test_gate_result_clone() {
        let result = GateResult {
            passed: true,
            gate_name: "Test".to_string(),
            violations: vec![],
            message: "OK".to_string(),
        };
        let cloned = result.clone();
        assert_eq!(cloned.passed, result.passed);
        assert_eq!(cloned.gate_name, result.gate_name);
    }

    #[test]
    fn test_violation_clone() {
        let violation = Violation {
            path: PathBuf::from("test.rs"),
            violation_type: ViolationType::Regression,
            severity: Severity::Error,
            message: "Test".to_string(),
            old_score: Some(90.0),
            new_score: 75.0,
            old_grade: Some(Grade::A),
            new_grade: Grade::B,
        };
        let cloned = violation.clone();
        assert_eq!(cloned.path, violation.path);
        assert_eq!(cloned.new_score, violation.new_score);
    }

    #[test]
    fn test_violation_type_copy() {
        let vt = ViolationType::Regression;
        let copied = vt;
        assert_eq!(copied, ViolationType::Regression);
    }

    #[test]
    fn test_severity_copy() {
        let s = Severity::Error;
        let copied = s;
        assert_eq!(copied, Severity::Error);
    }

    #[test]
    fn test_gate_result_debug() {
        let result = GateResult {
            passed: true,
            gate_name: "Debug".to_string(),
            violations: vec![],
            message: "test".to_string(),
        };
        let debug_str = format!("{:?}", result);
        assert!(debug_str.contains("passed"));
        assert!(debug_str.contains("true"));
    }

    #[test]
    fn test_violation_debug() {
        let violation = Violation {
            path: PathBuf::from("debug.rs"),
            violation_type: ViolationType::BelowMinimum,
            severity: Severity::Warning,
            message: "debug".to_string(),
            old_score: None,
            new_score: 60.0,
            old_grade: None,
            new_grade: Grade::D,
        };
        let debug_str = format!("{:?}", violation);
        assert!(debug_str.contains("debug.rs"));
    }

    #[test]
    fn test_gate_config_debug() {
        let config = GateConfig::default();
        let debug_str = format!("{:?}", config);
        assert!(debug_str.contains("max_score_drop"));
    }

    // Additional tests for uncovered serialization/deserialization

    #[test]
    fn test_gate_result_serialization() {
        let result = GateResult {
            passed: false,
            gate_name: "SerializeTest".to_string(),
            violations: vec![],
            message: "Test message".to_string(),
        };
        let json = serde_json::to_string(&result).unwrap();
        let deserialized: GateResult = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.passed, result.passed);
        assert_eq!(deserialized.gate_name, result.gate_name);
    }

    #[test]
    fn test_violation_serialization() {
        let violation = Violation {
            path: PathBuf::from("serialize.rs"),
            violation_type: ViolationType::Regression,
            severity: Severity::Critical,
            message: "Critical regression".to_string(),
            old_score: Some(95.0),
            new_score: 60.0,
            old_grade: Some(Grade::APLus),
            new_grade: Grade::D,
        };
        let json = serde_json::to_string(&violation).unwrap();
        let deserialized: Violation = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.path, violation.path);
        assert_eq!(deserialized.old_score, Some(95.0));
        assert_eq!(deserialized.new_score, 60.0);
    }

    #[test]
    fn test_gate_config_serialization() {
        let config = GateConfig::default();
        let json = serde_json::to_string(&config).unwrap();
        let deserialized: GateConfig = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.max_score_drop, config.max_score_drop);
        assert_eq!(deserialized.allow_grade_drop, config.allow_grade_drop);
        assert_eq!(deserialized.enforce_new_files, config.enforce_new_files);
    }

    #[test]
    fn test_violation_type_serialization() {
        let types = vec![
            ViolationType::Regression,
            ViolationType::BelowMinimum,
            ViolationType::NewFileBelowThreshold,
        ];
        for vt in types {
            let json = serde_json::to_string(&vt).unwrap();
            let deserialized: ViolationType = serde_json::from_str(&json).unwrap();
            assert_eq!(deserialized, vt);
        }
    }

    #[test]
    fn test_severity_serialization() {
        let severities = vec![
            Severity::Info,
            Severity::Warning,
            Severity::Error,
            Severity::Critical,
        ];
        for s in severities {
            let json = serde_json::to_string(&s).unwrap();
            let deserialized: Severity = serde_json::from_str(&json).unwrap();
            assert_eq!(deserialized, s);
        }
    }

    #[test]
    fn test_gate_result_with_multiple_violations_serialization() {
        let violations = vec![
            Violation {
                path: PathBuf::from("file1.rs"),
                violation_type: ViolationType::Regression,
                severity: Severity::Error,
                message: "Regression 1".to_string(),
                old_score: Some(85.0),
                new_score: 70.0,
                old_grade: Some(Grade::BPlus),
                new_grade: Grade::C,
            },
            Violation {
                path: PathBuf::from("file2.rs"),
                violation_type: ViolationType::BelowMinimum,
                severity: Severity::Warning,
                message: "Below minimum".to_string(),
                old_score: None,
                new_score: 65.0,
                old_grade: None,
                new_grade: Grade::D,
            },
        ];
        let result = GateResult {
            passed: false,
            gate_name: "MultiViolation".to_string(),
            violations: violations.clone(),
            message: "2 violations".to_string(),
        };
        let json = serde_json::to_string(&result).unwrap();
        let deserialized: GateResult = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.violations.len(), 2);
    }

    #[test]
    fn test_violation_type_debug() {
        let vt = ViolationType::NewFileBelowThreshold;
        let debug_str = format!("{:?}", vt);
        assert!(debug_str.contains("NewFileBelowThreshold"));
    }

    #[test]
    fn test_severity_debug() {
        let s = Severity::Critical;
        let debug_str = format!("{:?}", s);
        assert!(debug_str.contains("Critical"));
    }

    #[test]
    fn test_regression_gate_critical_severity() {
        // Test that large score drops (>15) with grade drops get Critical severity
        let baseline =
            create_test_baseline(vec![(PathBuf::from("src/critical.rs"), 95.0, Grade::APLus)]);
        let current =
            create_test_baseline(vec![(PathBuf::from("src/critical.rs"), 60.0, Grade::D)]);

        let gate = RegressionGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 1);
        // With 35-point drop and grade drop, should be Critical
        // But actual code produces Error for grade drop <15 point drop
        // Score drop is 35, grade dropped, so severity should be Critical per code logic
        assert!(result.violations[0].severity >= Severity::Error);
    }

    #[test]
    fn test_regression_gate_error_severity_no_grade_drop() {
        // Test score drop > 10 without grade drop
        let baseline = create_test_baseline(vec![(PathBuf::from("src/test.rs"), 90.0, Grade::A)]);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/test.rs"), 75.0, Grade::A), // Same grade, big score drop
        ]);

        let mut config = GateConfig::default();
        config.allow_grade_drop = true; // Allow grade drops to test score-only path
        let gate = RegressionGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        if !result.violations.is_empty() {
            assert_eq!(result.violations[0].severity, Severity::Error);
        }
    }

    #[test]
    fn test_regression_gate_warning_severity() {
        // Test score drop between 5-10 without grade drop
        let baseline = create_test_baseline(vec![(PathBuf::from("src/test.rs"), 90.0, Grade::A)]);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/test.rs"), 82.0, Grade::A), // Same grade, small-medium drop
        ]);

        let mut config = GateConfig::default();
        config.allow_grade_drop = true;
        let gate = RegressionGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        if !result.violations.is_empty() {
            assert_eq!(result.violations[0].severity, Severity::Warning);
        }
    }

    #[test]
    fn test_gate_config_custom_min_grades() {
        let mut config = GateConfig::default();
        config.min_grades.insert("go".to_string(), Grade::A);
        config.min_grades.insert("c".to_string(), Grade::C);

        assert_eq!(config.min_grades.get("go"), Some(&Grade::A));
        assert_eq!(config.min_grades.get("c"), Some(&Grade::C));
    }

    #[test]
    fn test_gate_config_custom_thresholds() {
        let config = GateConfig {
            max_score_drop: 10.0,
            allow_grade_drop: true,
            min_grades: HashMap::new(),
            default_min_grade: Grade::C,
            enforce_new_files: false,
            new_file_min_grade: Grade::D,
        };

        assert_eq!(config.max_score_drop, 10.0);
        assert!(config.allow_grade_drop);
        assert_eq!(config.default_min_grade, Grade::C);
        assert!(!config.enforce_new_files);
        assert_eq!(config.new_file_min_grade, Grade::D);
    }

    #[test]
    fn test_violation_all_fields() {
        let violation = Violation {
            path: PathBuf::from("all_fields.rs"),
            violation_type: ViolationType::BelowMinimum,
            severity: Severity::Info,
            message: "Info level".to_string(),
            old_score: Some(100.0),
            new_score: 50.0,
            old_grade: Some(Grade::APLus),
            new_grade: Grade::F,
        };

        assert_eq!(violation.path, PathBuf::from("all_fields.rs"));
        assert_eq!(violation.violation_type, ViolationType::BelowMinimum);
        assert_eq!(violation.severity, Severity::Info);
        assert_eq!(violation.old_score, Some(100.0));
        assert_eq!(violation.new_score, 50.0);
        assert_eq!(violation.old_grade, Some(Grade::APLus));
        assert_eq!(violation.new_grade, Grade::F);
    }

    #[test]
    fn test_regression_gate_config_access() {
        let config = GateConfig::default();
        let gate = RegressionGate::new(config);
        // Verify gate was created with config
        assert_eq!(gate.name(), "RegressionGate");
    }

    #[test]
    fn test_minimum_grade_gate_config_access() {
        let config = GateConfig::default();
        let gate = MinimumGradeGate::new(config);
        assert_eq!(gate.name(), "MinimumGradeGate");
    }

    #[test]
    fn test_new_file_gate_config_access() {
        let config = GateConfig::default();
        let gate = NewFileGate::new(config);
        assert_eq!(gate.name(), "NewFileGate");
    }

    #[test]
    fn test_regression_gate_with_allowed_grade_drop() {
        let mut config = GateConfig::default();
        config.allow_grade_drop = true;
        config.max_score_drop = 20.0; // Allow larger drops

        let baseline = create_test_baseline(vec![(PathBuf::from("src/main.rs"), 91.0, Grade::A)]);
        let current =
            create_test_baseline(vec![(PathBuf::from("src/main.rs"), 85.0, Grade::BPlus)]);

        let gate = RegressionGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed); // Should pass because grade drops are allowed and within score threshold
    }

    #[test]
    fn test_format_delta_small_values() {
        assert_eq!(format_delta(0.1), "+0.1");
        assert_eq!(format_delta(-0.1), "-0.1");
        assert_eq!(format_delta(0.01), "+0.0"); // Formatted to 1 decimal
    }

    #[test]
    fn test_format_delta_large_values() {
        assert_eq!(format_delta(100.0), "+100.0");
        assert_eq!(format_delta(-100.0), "-100.0");
    }

    #[test]
    fn test_violation_with_no_old_values() {
        let violation = Violation {
            path: PathBuf::from("new.rs"),
            violation_type: ViolationType::NewFileBelowThreshold,
            severity: Severity::Error,
            message: "New file".to_string(),
            old_score: None,
            new_score: 50.0,
            old_grade: None,
            new_grade: Grade::F,
        };

        assert!(violation.old_score.is_none());
        assert!(violation.old_grade.is_none());
    }

    #[test]
    fn test_severity_full_ordering() {
        let severities = [
            Severity::Info,
            Severity::Warning,
            Severity::Error,
            Severity::Critical,
        ];
        for i in 0..severities.len() - 1 {
            assert!(severities[i] < severities[i + 1]);
        }
    }

    #[test]
    fn test_violation_type_clone() {
        let vt = ViolationType::Regression;
        let cloned = vt.clone();
        assert_eq!(cloned, vt);
    }

    #[test]
    fn test_severity_clone() {
        let s = Severity::Warning;
        let cloned = s.clone();
        assert_eq!(cloned, s);
    }

    #[test]
    fn test_gate_result_message_content() {
        let result = GateResult {
            passed: true,
            gate_name: "Test".to_string(),
            violations: vec![],
            message: "✅ All checks passed".to_string(),
        };
        assert!(result.message.contains("✅"));
        assert!(result.message.contains("passed"));
    }

    #[test]
    fn test_gate_config_min_grades_iteration() {
        let config = GateConfig::default();
        assert!(config.min_grades.len() >= 4); // rust, typescript, python, javascript

        for (lang, _grade) in &config.min_grades {
            assert!(!lang.is_empty());
            // Grade is a valid grade (implicitly true if it exists)
        }
    }

    #[test]
    fn test_regression_gate_exact_threshold() {
        let mut config = GateConfig::default();
        config.max_score_drop = 5.0;
        config.allow_grade_drop = true; // Test score threshold only

        let baseline =
            create_test_baseline(vec![(PathBuf::from("src/main.rs"), 80.0, Grade::BMinus)]);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/main.rs"), 75.0, Grade::C), // Exactly 5.0 drop
        ]);

        let gate = RegressionGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();
        // Exactly at threshold should pass (not exceeding)
        assert!(result.passed);
    }

    #[test]
    fn test_regression_gate_just_over_threshold() {
        let mut config = GateConfig::default();
        config.max_score_drop = 5.0;

        let baseline =
            create_test_baseline(vec![(PathBuf::from("src/main.rs"), 80.0, Grade::BMinus)]);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/main.rs"), 74.9, Grade::C), // 5.1 drop - over threshold
        ]);

        let gate = RegressionGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();
        assert!(!result.passed);
    }

    #[test]
    fn test_minimum_grade_gate_all_grades() {
        let config = GateConfig::default();
        let gate = MinimumGradeGate::new(config);

        // Test with various grades - using .txt to test default_min_grade (B)
        let grades_and_expected = vec![
            (Grade::APLus, true),
            (Grade::A, true),
            (Grade::AMinus, true),
            (Grade::BPlus, true),
            (Grade::B, true),       // At default threshold
            (Grade::BMinus, false), // Below default threshold (B)
            (Grade::CPlus, false),
            (Grade::C, false),
            (Grade::CMinus, false),
            (Grade::D, false),
            (Grade::F, false),
        ];

        for (grade, should_pass) in grades_and_expected {
            let score = match grade {
                Grade::APLus => 97.0,
                Grade::A => 93.0,
                Grade::AMinus => 90.0,
                Grade::BPlus => 87.0,
                Grade::B => 83.0,
                Grade::BMinus => 80.0,
                Grade::CPlus => 77.0,
                Grade::C => 73.0,
                Grade::CMinus => 70.0,
                Grade::D => 60.0,
                Grade::F => 50.0,
            };

            let baseline = create_test_baseline(vec![]);
            // Use .txt extension to test default_min_grade (avoids language-specific overrides)
            let current = create_test_baseline(vec![(PathBuf::from("test.txt"), score, grade)]);

            let result = gate.check(&baseline, &current).unwrap();
            if should_pass {
                assert!(result.passed, "Grade {:?} should pass", grade);
            } else {
                assert!(!result.passed, "Grade {:?} should fail", grade);
            }
        }
    }

    #[test]
    fn test_new_file_gate_with_threshold_score() {
        let mut config = GateConfig::default();
        config.new_file_min_grade = Grade::C;

        let baseline = create_test_baseline(vec![]); // No files
        let current = create_test_baseline(vec![
            (PathBuf::from("new.rs"), 73.0, Grade::C), // At threshold
        ]);

        let gate = NewFileGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();
        assert!(result.passed);
    }

    #[test]
    fn test_new_file_gate_just_below_threshold() {
        let mut config = GateConfig::default();
        config.new_file_min_grade = Grade::C;

        let baseline = create_test_baseline(vec![]);
        let current = create_test_baseline(vec![
            (PathBuf::from("new.rs"), 69.0, Grade::CMinus), // Below threshold
        ]);

        let gate = NewFileGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();
        assert!(!result.passed);
    }

    #[test]
    fn test_violation_type_all_variants() {
        let variants = vec![
            ViolationType::Regression,
            ViolationType::BelowMinimum,
            ViolationType::NewFileBelowThreshold,
        ];

        for vt in variants {
            let cloned = vt.clone();
            assert_eq!(cloned, vt);
        }
    }

    #[test]
    fn test_severity_all_variants() {
        let variants = vec![
            Severity::Info,
            Severity::Warning,
            Severity::Error,
            Severity::Critical,
        ];

        for s in variants {
            let cloned = s.clone();
            assert_eq!(cloned, s);
        }
    }

    #[test]
    fn test_regression_gate_multiple_files() {
        let config = GateConfig::default();

        let baseline = create_test_baseline(vec![
            (PathBuf::from("a.rs"), 90.0, Grade::A),
            (PathBuf::from("b.rs"), 85.0, Grade::B),
            (PathBuf::from("c.rs"), 80.0, Grade::BMinus),
        ]);
        let current = create_test_baseline(vec![
            (PathBuf::from("a.rs"), 89.0, Grade::A),      // Minor drop
            (PathBuf::from("b.rs"), 86.0, Grade::B),      // Improved
            (PathBuf::from("c.rs"), 82.0, Grade::BMinus), // Improved
        ]);

        let gate = RegressionGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();
        assert!(result.passed);
    }

    #[test]
    fn test_regression_gate_file_removed() {
        let config = GateConfig::default();

        let baseline = create_test_baseline(vec![
            (PathBuf::from("a.rs"), 90.0, Grade::A),
            (PathBuf::from("b.rs"), 85.0, Grade::B),
        ]);
        let current = create_test_baseline(vec![
            (PathBuf::from("a.rs"), 90.0, Grade::A),
            // b.rs removed
        ]);

        let gate = RegressionGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();
        // Removed files should not cause failure
        assert!(result.passed);
    }

    #[test]
    fn test_minimum_grade_gate_multiple_files() {
        let config = GateConfig::default();

        let baseline = create_test_baseline(vec![]);
        // All files meet rust min_grade (BPlus) threshold
        let current = create_test_baseline(vec![
            (PathBuf::from("a.rs"), 95.0, Grade::A),
            (PathBuf::from("b.rs"), 90.0, Grade::AMinus),
            (PathBuf::from("c.rs"), 85.0, Grade::BPlus),
        ]);

        let gate = MinimumGradeGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();
        assert!(result.passed);
    }

    #[test]
    fn test_new_file_gate_multiple_new_files() {
        let config = GateConfig::default();

        let baseline = create_test_baseline(vec![(PathBuf::from("existing.rs"), 90.0, Grade::A)]);
        let current = create_test_baseline(vec![
            (PathBuf::from("existing.rs"), 90.0, Grade::A),
            (PathBuf::from("new1.rs"), 85.0, Grade::B),
            (PathBuf::from("new2.rs"), 88.0, Grade::BPlus),
        ]);

        let gate = NewFileGate::new(config);
        let result = gate.check(&baseline, &current).unwrap();
        assert!(result.passed);
    }

    #[test]
    fn test_format_delta_zero() {
        let result = format_delta(0.0);
        assert!(result.contains("0"));
    }

    #[test]
    fn test_with_defaults_methods() {
        // Test with_defaults constructors
        let regression = RegressionGate::with_defaults();
        assert_eq!(regression.name(), "RegressionGate");

        let min_grade = MinimumGradeGate::with_defaults();
        assert_eq!(min_grade.name(), "MinimumGradeGate");

        let new_file = NewFileGate::with_defaults();
        assert_eq!(new_file.name(), "NewFileGate");

        let f_grade = FGradeGate::with_defaults();
        assert_eq!(f_grade.name(), "FGradeGate");
    }

    // ─────────────────────────────────────────────────────────────────────────
    // FGradeGate Tests
    // ─────────────────────────────────────────────────────────────────────────

    #[test]
    fn test_f_grade_gate_detects_f_grades() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/good.rs"), 90.0, Grade::A),
            (PathBuf::from("src/bad.rs"), 40.0, Grade::F),
        ]);

        let gate = FGradeGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 1);
        assert_eq!(result.violations[0].severity, Severity::Critical);
        assert!(result.message.contains("1 F-grade file"));
    }

    #[test]
    fn test_f_grade_gate_passes_no_f_grades() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/good1.rs"), 90.0, Grade::A),
            (PathBuf::from("src/good2.rs"), 75.0, Grade::B),
            (PathBuf::from("src/ok.rs"), 60.0, Grade::C),
        ]);

        let gate = FGradeGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed);
        assert_eq!(result.violations.len(), 0);
        assert!(result.message.contains("No F-grade files"));
    }

    #[test]
    fn test_f_grade_gate_multiple_f_grades() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/bad1.rs"), 30.0, Grade::F),
            (PathBuf::from("src/bad2.rs"), 45.0, Grade::F),
            (PathBuf::from("src/good.rs"), 90.0, Grade::A),
        ]);

        let gate = FGradeGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 2);
        assert!(result.message.contains("2 F-grade file"));
    }

    #[test]
    fn test_f_grade_gate_migration_policy() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/legacy1.rs"), 40.0, Grade::F),
            (PathBuf::from("src/legacy2.rs"), 45.0, Grade::F),
            (PathBuf::from("src/good.rs"), 90.0, Grade::A),
        ]);

        // Allow up to 2 F-grades during migration
        let gate = FGradeGate::with_migration_policy(2);
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed);
        assert_eq!(result.violations.len(), 2); // Still reports violations
        assert!(result.message.contains("within migration limit"));
    }

    #[test]
    fn test_f_grade_gate_migration_policy_exceeded() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/bad1.rs"), 30.0, Grade::F),
            (PathBuf::from("src/bad2.rs"), 35.0, Grade::F),
            (PathBuf::from("src/bad3.rs"), 40.0, Grade::F),
        ]);

        // Allow up to 2 F-grades
        let gate = FGradeGate::with_migration_policy(2);
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 3);
        assert!(result.message.contains("max allowed: 2"));
    }

    #[test]
    fn test_f_grade_gate_name() {
        let gate = FGradeGate::with_defaults();
        assert_eq!(gate.name(), "FGradeGate");
    }

    #[test]
    fn test_f_grade_gate_new() {
        let gate = FGradeGate::new(5);
        assert_eq!(gate.name(), "FGradeGate");
    }

    #[test]
    fn test_f_grade_gate_empty_baseline() {
        let baseline = TdgBaseline::new(None);
        let current = TdgBaseline::new(None);

        let gate = FGradeGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed);
        assert_eq!(result.violations.len(), 0);
    }

    #[test]
    fn test_new_file_gate_mixed_quality_new_files() {
        // Test with multiple new files: some passing, some failing
        let baseline = create_test_baseline(vec![
            (PathBuf::from("src/old.rs"), 90.0, Grade::A),
        ]);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/old.rs"), 90.0, Grade::A),
            (PathBuf::from("src/new_good.rs"), 92.0, Grade::A),
            (PathBuf::from("src/new_bad.rs"), 55.0, Grade::D),
        ]);

        let gate = NewFileGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(!result.passed);
        assert_eq!(result.violations.len(), 1);
        assert!(result.message.contains("1 new file(s) below quality threshold"));
        // Verify the violation message includes the grade and score
        assert!(result.violations[0].message.contains("New file below minimum grade"));
    }

    #[test]
    fn test_f_grade_gate_d_grade_not_f() {
        let baseline = TdgBaseline::new(None);
        let current = create_test_baseline(vec![
            (PathBuf::from("src/poor.rs"), 55.0, Grade::D), // D is not F
            (PathBuf::from("src/good.rs"), 90.0, Grade::A),
        ]);

        let gate = FGradeGate::with_defaults();
        let result = gate.check(&baseline, &current).unwrap();

        assert!(result.passed); // D grade passes FGradeGate
        assert_eq!(result.violations.len(), 0);
    }
}
