use std::path::PathBuf;

/// Core data structures for project detection and technology inference
use serde::{Deserialize, Serialize};

/// Detected technology or framework in a project
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Technology {
    // Languages
    #[serde(rename = "nodejs")]
    NodeJs,
    Python,
    Rust,
    Go,
    Java,
    Ruby,
    Php,

    // Frameworks
    #[serde(rename = "expressjs")]
    Express,
    Django,
    Axum,
    Gin,
    Spring,
    Rails,
    Laravel,

    // Databases
    Postgres,
    Mysql,
    Mongodb,
    Redis,
    Sqlite,

    // Infrastructure
    Docker,
    Kubernetes,
    Nginx,

    // Package Managers
    Npm,
    Yarn,
    Pnpm,
    Pip,
    Cargo,

    // Build Tools
    Webpack,
    Vite,
    Maven,
    Gradle,
}

impl Technology {
    pub fn display_name(&self) -> &'static str {
        match self {
            Technology::NodeJs => "Node.js",
            Technology::Express => "Express",
            Technology::Postgres => "PostgreSQL",
            Technology::Mysql => "MySQL",
            Technology::Docker => "Docker",
            Technology::Kubernetes => "Kubernetes",
            _ => "Unknown",
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Technology::NodeJs => "nodejs",
            Technology::Python => "python",
            Technology::Rust => "rust",
            Technology::Go => "go",
            Technology::Java => "java",
            Technology::Ruby => "ruby",
            Technology::Php => "php",
            Technology::Express => "expressjs",
            Technology::Django => "django",
            Technology::Axum => "axum",
            Technology::Gin => "gin",
            Technology::Spring => "spring",
            Technology::Rails => "rails",
            Technology::Laravel => "laravel",
            Technology::Postgres => "postgres",
            Technology::Mysql => "mysql",
            Technology::Mongodb => "mongodb",
            Technology::Redis => "redis",
            Technology::Sqlite => "sqlite",
            Technology::Docker => "docker",
            Technology::Kubernetes => "kubernetes",
            Technology::Nginx => "nginx",
            Technology::Npm => "npm",
            Technology::Yarn => "yarn",
            Technology::Pnpm => "pnpm",
            Technology::Pip => "pip",
            Technology::Cargo => "cargo",
            Technology::Webpack => "webpack",
            Technology::Vite => "vite",
            Technology::Maven => "maven",
            Technology::Gradle => "gradle",
        }
    }
}

/// Evidence supporting a technology detection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Evidence {
    /// File exists (e.g., package.json, Cargo.toml)
    FileExists { path: PathBuf, reason: String },

    /// File contains specific pattern
    FileContent {
        path: PathBuf,
        pattern: String,
        line: usize,
        reason: String,
    },

    /// Dependency in manifest (npm, cargo, pip, etc.)
    Dependency {
        name: String,
        version: Option<String>,
        reason: String,
    },

    /// Version info extracted
    Version { version: String, source: String },
}

impl Evidence {
    pub fn weight(&self) -> f32 {
        match self {
            Evidence::FileExists { .. } => 0.4,
            Evidence::FileContent { .. } => 0.5,
            Evidence::Dependency { .. } => 0.6,
            Evidence::Version { .. } => 0.3,
        }
    }
}

/// Result of detecting a single technology
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Detection {
    pub technology: Technology,
    pub version: Option<String>,
    pub evidence: Vec<Evidence>,
    pub confidence: f32,
}

impl Detection {
    pub fn new(technology: Technology) -> Self {
        Self {
            technology,
            version: None,
            evidence: Vec::new(),
            confidence: 0.0,
        }
    }

    pub fn with_version(mut self, version: String) -> Self {
        self.version = Some(version);
        self
    }

    pub fn add_evidence(mut self, evidence: Evidence) -> Self {
        self.evidence.push(evidence);
        self
    }

    pub fn calculate_confidence(&mut self) {
        if self.evidence.is_empty() {
            self.confidence = 0.0;
        } else {
            let total_weight: f32 = self.evidence.iter().map(|e| e.weight()).sum();
            let max_weight = self.evidence.len() as f32 * 0.6; // Max single evidence weight
            self.confidence = (total_weight / max_weight).clamp(0.0, 1.0);
        }
    }

    pub fn is_high_confidence(&self) -> bool {
        self.confidence > 0.7
    }
}

/// Requirement inferred from detections
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Requirement {
    /// Name of taskserv (e.g., "redis", "postgres")
    pub taskserv: String,

    /// Human-readable reason
    pub reason: String,

    /// Confidence (0.0 - 1.0) - higher = more certain
    pub confidence: f32,

    /// Is this required (true) or optional (false)?
    pub required: bool,

    /// Minimum version requirement, if any
    pub min_version: Option<String>,
}

impl Requirement {
    pub fn new(
        taskserv: impl Into<String>,
        reason: impl Into<String>,
        confidence: f32,
        required: bool,
    ) -> Self {
        Self {
            taskserv: taskserv.into(),
            reason: reason.into(),
            confidence,
            required,
            min_version: None,
        }
    }

    pub fn with_min_version(mut self, version: String) -> Self {
        self.min_version = Some(version);
        self
    }

    pub fn is_high_confidence(&self) -> bool {
        self.confidence > 0.7
    }
}

/// Complete project analysis result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectAnalysis {
    pub project_path: PathBuf,
    pub detections: Vec<Detection>,
    pub requirements: Vec<Requirement>,
    pub overall_confidence: f32,
}

impl ProjectAnalysis {
    pub fn new(project_path: PathBuf) -> Self {
        Self {
            project_path,
            detections: Vec::new(),
            requirements: Vec::new(),
            overall_confidence: 0.0,
        }
    }

    pub fn calculate_overall_confidence(&mut self) {
        if self.detections.is_empty() {
            self.overall_confidence = 0.0;
        } else {
            let avg: f32 = self.detections.iter().map(|d| d.confidence).sum::<f32>()
                / self.detections.len() as f32;
            self.overall_confidence = avg;
        }
    }

    pub fn get_detection(&self, tech: Technology) -> Option<&Detection> {
        self.detections.iter().find(|d| d.technology == tech)
    }

    pub fn get_language(&self) -> Option<&Detection> {
        self.detections.iter().find(|d| {
            matches!(
                d.technology,
                Technology::NodeJs
                    | Technology::Python
                    | Technology::Rust
                    | Technology::Go
                    | Technology::Java
                    | Technology::Ruby
                    | Technology::Php
            )
        })
    }

    pub fn has_docker(&self) -> bool {
        self.detections
            .iter()
            .any(|d| d.technology == Technology::Docker)
    }

    pub fn has_database(&self) -> bool {
        self.detections.iter().any(|d| {
            matches!(
                d.technology,
                Technology::Postgres | Technology::Mysql | Technology::Mongodb | Technology::Sqlite
            )
        })
    }
}

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

    #[test]
    fn test_detection_confidence() {
        let det = Detection::new(Technology::NodeJs).add_evidence(Evidence::FileExists {
            path: PathBuf::from("package.json"),
            reason: "Primary Node.js indicator".into(),
        });
        let mut det = det;
        det.calculate_confidence();

        assert!(det.confidence > 0.0);
        assert!(!det.is_high_confidence()); // Only one evidence
    }

    #[test]
    fn test_requirement_confidence() {
        let req = Requirement::new("redis", "API caching", 0.85, false);
        assert!(req.is_high_confidence());
    }
}
