/// Gap Analyzer
///
/// Detects gaps between:
/// 1. Current declaration (what exists)
/// 2. Requirements (what should exist)
///
/// Gap types:
/// - Missing taskserv (required by inference, not in declaration)
/// - Outdated version (current < recommended)
/// - Missing field (incomplete configuration)
/// - Dependency issue (missing dependency)
use crate::models::ProjectAnalysis;

/// Single gap found
#[derive(Debug, Clone)]
pub struct Gap {
    pub kind: GapKind,
    pub severity: Severity,
    pub message: String,
    pub suggestion: String,
}

#[derive(Debug, Clone)]
pub enum GapKind {
    /// Taskserv is required but missing from declaration
    MissingTaskserv {
        name: String,
        reason: String,
        required: bool,
    },

    /// Version is outdated
    OutdatedVersion {
        taskserv: String,
        current: String,
        recommended: String,
    },

    /// Configuration field is missing
    MissingField { schema: String, field: String },

    /// Taskserv missing a dependency
    MissingDependency {
        taskserv: String,
        depends_on: String,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
    Error,   // Blocks deployment
    Warning, // Should fix
    Info,    // Optional improvement
}

/// Gap analysis result
#[derive(Debug, Clone)]
pub struct GapAnalysisResult {
    pub gaps: Vec<Gap>,
    pub error_count: usize,
    pub warning_count: usize,
    pub info_count: usize,
    pub completeness: f32, // 0.0 (empty) to 1.0 (complete)
}

impl GapAnalysisResult {
    pub fn has_errors(&self) -> bool {
        self.error_count > 0
    }

    pub fn is_complete(&self) -> bool {
        self.completeness >= 0.95
    }
}

/// Analyzes gaps in a project/declaration
pub struct GapAnalyzer;

impl GapAnalyzer {
    /// Analyze gaps given current state and requirements
    pub fn analyze(analysis: &ProjectAnalysis, required_taskservs: &[String]) -> GapAnalysisResult {
        let mut gaps = Vec::new();
        let mut error_count = 0;
        let mut warning_count = 0;

        // Check for missing required taskservs
        for taskserv_name in required_taskservs {
            if !Self::has_taskserv(analysis, taskserv_name) {
                let gap = Gap {
                    kind: GapKind::MissingTaskserv {
                        name: taskserv_name.clone(),
                        reason: "Required by project analysis".into(),
                        required: true,
                    },
                    severity: Severity::Error,
                    message: format!("Required taskserv '{}' is missing", taskserv_name),
                    suggestion: format!("Add {} to requirements", taskserv_name),
                };
                error_count += 1;
                gaps.push(gap);
            }
        }

        // Check for recommended but not required taskservs
        for requirement in &analysis.requirements {
            if !requirement.required
                && requirement.confidence > 0.7
                && !Self::has_taskserv(analysis, &requirement.taskserv)
            {
                let gap = Gap {
                    kind: GapKind::MissingTaskserv {
                        name: requirement.taskserv.clone(),
                        reason: requirement.reason.clone(),
                        required: false,
                    },
                    severity: Severity::Warning,
                    message: format!(
                        "Recommended taskserv '{}' not in declaration",
                        requirement.taskserv
                    ),
                    suggestion: format!(
                        "Consider adding {} ({})",
                        requirement.taskserv, requirement.reason
                    ),
                };
                warning_count += 1;
                gaps.push(gap);
            }
        }

        // Check for database without backup
        if analysis.has_database() {
            let has_backup = analysis
                .requirements
                .iter()
                .any(|r| r.taskserv.contains("backup"));
            if !has_backup {
                let gap = Gap {
                    kind: GapKind::MissingField {
                        schema: "TaskservRequirement".into(),
                        field: "backup_strategy".into(),
                    },
                    severity: Severity::Warning,
                    message: "Production database without backup configuration".into(),
                    suggestion: "Add backup strategy for database taskserv".into(),
                };
                warning_count += 1;
                gaps.push(gap);
            }
        }

        // Calculate completeness
        let total_possible =
            required_taskservs.len() + (if analysis.has_database() { 2 } else { 0 });
        let total_met = total_possible - error_count - warning_count;
        let completeness = if total_possible > 0 {
            (total_met as f32) / (total_possible as f32)
        } else {
            1.0
        };

        GapAnalysisResult {
            gaps,
            error_count,
            warning_count,
            info_count: 0,
            completeness: completeness.clamp(0.0, 1.0),
        }
    }

    fn has_taskserv(analysis: &ProjectAnalysis, name: &str) -> bool {
        analysis.requirements.iter().any(|r| r.taskserv == name)
    }
}

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

    use super::*;
    use crate::models::{ProjectAnalysis, Requirement};

    #[test]
    fn test_missing_required_taskserv() {
        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
        analysis
            .requirements
            .push(Requirement::new("postgres", "Database detected", 1.0, true));

        let required = vec!["postgres".to_string(), "redis".to_string()];
        let result = GapAnalyzer::analyze(&analysis, &required);

        assert_eq!(result.error_count, 1);
        assert!(result.has_errors());
    }

    #[test]
    fn test_optional_taskserv_not_error() {
        let mut analysis = ProjectAnalysis::new(PathBuf::from("/test"));
        analysis.requirements.push(Requirement::new(
            "redis",
            "Caching recommendation",
            0.8,
            false,
        ));

        let required = vec![];
        let result = GapAnalyzer::analyze(&analysis, &required);

        assert_eq!(result.error_count, 0);
    }
}
