use std::path::Path;

use serde::{Deserialize, Serialize};

/// Node.js project detector
/// Detects: Node.js, npm/yarn/pnpm package managers, Express framework
use super::helpers;
use crate::error::DetectionResult;
use crate::models::{Detection, Evidence, Technology};

/// Package.json structure (minimal for detection)
#[derive(Debug, Deserialize, Serialize)]
pub struct PackageJson {
    pub name: Option<String>,
    pub version: Option<String>,
    pub engines: Option<Engines>,
    pub dependencies: Option<std::collections::HashMap<String, String>>,
    #[serde(rename = "devDependencies")]
    pub dev_dependencies: Option<std::collections::HashMap<String, String>>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Engines {
    pub node: Option<String>,
    pub npm: Option<String>,
}

pub struct NodeJsDetector;

impl Default for NodeJsDetector {
    fn default() -> Self {
        Self
    }
}

impl NodeJsDetector {
    pub fn new() -> Self {
        Self
    }

    /// Detect Node.js by checking package.json
    fn detect_package_json(&self, project_path: &Path) -> DetectionResult<Option<Detection>> {
        if !helpers::file_exists(project_path, "package.json")? {
            return Ok(None);
        }

        let package_json: PackageJson = helpers::read_json(project_path, "package.json")?;

        let mut detection = Detection::new(Technology::NodeJs);

        // Add evidence: file exists
        detection = detection.add_evidence(Evidence::FileExists {
            path: project_path.join("package.json"),
            reason: "package.json is primary Node.js indicator".into(),
        });

        // Extract Node.js version from engines field
        if let Some(engines) = &package_json.engines {
            if let Some(version) = &engines.node {
                detection = detection.with_version(version.clone());
                detection = detection.add_evidence(Evidence::Version {
                    version: version.clone(),
                    source: "package.json engines.node".into(),
                });
            }
        }

        // Detect package manager (npm, yarn, pnpm)
        detection = self.detect_package_manager(project_path, detection)?;

        // Detect framework (Express, etc.)
        if let Some(deps) = &package_json.dependencies {
            if deps.contains_key("express") {
                detection = detection.add_evidence(Evidence::Dependency {
                    name: "express".into(),
                    version: deps.get("express").cloned(),
                    reason: "Express is popular web framework".into(),
                });
            }
        }

        detection.calculate_confidence();
        Ok(Some(detection))
    }

    fn detect_package_manager(
        &self,
        project_path: &Path,
        mut detection: Detection,
    ) -> DetectionResult<Detection> {
        // Check for yarn
        if helpers::file_exists(project_path, "yarn.lock")? {
            detection = detection.add_evidence(Evidence::FileExists {
                path: project_path.join("yarn.lock"),
                reason: "yarn.lock indicates yarn package manager".into(),
            });
        }

        // Check for pnpm
        if helpers::file_exists(project_path, "pnpm-lock.yaml")? {
            detection = detection.add_evidence(Evidence::FileExists {
                path: project_path.join("pnpm-lock.yaml"),
                reason: "pnpm-lock.yaml indicates pnpm package manager".into(),
            });
        }

        // Check for .nvmrc (Node version manager)
        if helpers::file_exists(project_path, ".nvmrc")? {
            if let Ok(content) = helpers::read_file(project_path, ".nvmrc") {
                let version = content.trim().to_string();
                detection = detection.add_evidence(Evidence::Version {
                    version,
                    source: ".nvmrc".into(),
                });
            }
        }

        Ok(detection)
    }
}

impl super::Detector for NodeJsDetector {
    fn name(&self) -> &str {
        "nodejs"
    }

    fn detect(&self, project_path: &Path) -> DetectionResult<Option<Detection>> {
        self.detect_package_json(project_path)
    }

    fn priority(&self) -> u8 {
        80 // High priority - package.json is very reliable
    }
}

#[cfg(test)]
mod tests {
    use tempfile::TempDir;

    use super::*;
    use crate::detectors::Detector;

    #[test]
    fn test_detect_nodejs_with_package_json() -> anyhow::Result<()> {
        let temp_dir = TempDir::new()?;
        let package_json_path = temp_dir.path().join("package.json");

        std::fs::write(
            &package_json_path,
            r#"{
                "name": "test-app",
                "version": "1.0.0",
                "engines": {
                    "node": "20.0.0"
                },
                "dependencies": {
                    "express": "^4.18.0"
                }
            }"#,
        )?;

        let detector = NodeJsDetector::new();
        let result = detector.detect(temp_dir.path())?;

        assert!(result.is_some());
        let detection = result.unwrap();
        assert_eq!(detection.technology, Technology::NodeJs);
        assert_eq!(detection.version, Some("20.0.0".into()));
        assert!(detection.confidence > 0.0);

        Ok(())
    }

    #[test]
    fn test_detect_nodejs_missing_package_json() -> anyhow::Result<()> {
        let temp_dir = TempDir::new()?;

        let detector = NodeJsDetector::new();
        let result = detector.detect(temp_dir.path())?;

        assert!(result.is_none());

        Ok(())
    }
}
