//! Project migration and bootstrap
//!
//! Enables conversion of existing projects to use PLM without losing data.
//! Supports multiple project types: Rust, Node.js, Python, Go, etc.

use crate::config::ProjectConfig;
use crate::error::Result;
use crate::types::ProjectType;
use crate::LifecycleError;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Project detection result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectedProject {
    /// Project root path
    pub root_path: PathBuf,
    /// Detected project type
    pub project_type: DetectedProjectType,
    /// Project name (inferred)
    pub name: String,
    /// Current version (from manifest or git)
    pub version: String,
    /// Detected programming languages
    pub languages: Vec<String>,
    /// Primary language
    pub primary_language: String,
    /// Existing description (if any)
    pub description: Option<String>,
    /// Found dependency files
    pub dependency_files: Vec<PathBuf>,
    /// Is Git repository
    pub is_git_repo: bool,
    /// Detected build tools
    pub build_tools: Vec<String>,
    /// Documentation directories found
    pub doc_dirs: Vec<PathBuf>,
}

/// Detected project type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DetectedProjectType {
    /// Rust project (Cargo.toml)
    Rust,
    /// Node.js project (package.json)
    NodeJs,
    /// Python project (setup.py, pyproject.toml, requirements.txt)
    Python,
    /// Go project (go.mod)
    Go,
    /// Java project (pom.xml, build.gradle)
    Java,
    /// C# project (*.csproj)
    CSharp,
    /// C++ project (CMakeLists.txt)
    Cpp,
    /// Unknown project
    Unknown,
}

/// Project migrator
#[derive(Debug)]
pub struct ProjectMigrator {
    /// Root path to scan
    root_path: PathBuf,
}

impl ProjectMigrator {
    /// Create new migrator
    pub fn new(root_path: PathBuf) -> Self {
        Self { root_path }
    }

    /// Detect project type from root directory
    pub fn detect_project(&self) -> Result<DetectedProject> {
        if !self.root_path.exists() {
            return Err(LifecycleError::integration(format!(
                "Project path does not exist: {:?}",
                self.root_path
            )));
        }

        // Detect project type
        let project_type = self.detect_project_type()?;

        // Extract metadata
        let name = self.extract_project_name(&project_type)?;
        let version = self.extract_version(&project_type)?;
        let languages = self.detect_languages(&project_type)?;
        let primary_language = languages
            .first()
            .cloned()
            .unwrap_or_else(|| "unknown".to_string());
        let description = self.extract_description();
        let dependency_files = self.find_dependency_files();
        let is_git_repo = self.is_git_repository();
        let build_tools = self.detect_build_tools();
        let doc_dirs = self.find_doc_directories();

        Ok(DetectedProject {
            root_path: self.root_path.clone(),
            project_type,
            name,
            version,
            languages,
            primary_language,
            description,
            dependency_files,
            is_git_repo,
            build_tools,
            doc_dirs,
        })
    }

    /// Convert detected project to PLM config
    pub fn migrate_to_config(&self, detected: &DetectedProject) -> Result<ProjectConfig> {
        let mut config = ProjectConfig::new(&detected.name);

        config.project.version = detected.version.clone();
        config.project.description = detected.description.clone().unwrap_or_default();
        config.primary_language = detected.primary_language.clone();
        config.languages = detected.languages.clone();
        config.project_type = self.map_to_project_type(&detected.project_type);
        config.project.created = Utc::now();
        config.project.last_updated = Utc::now();

        tracing::info!(
            "Migrated project '{}' ({:?}) to PLM config",
            detected.name,
            detected.project_type
        );

        Ok(config)
    }

    /// Initialize PLM workspace for detected project
    pub async fn initialize_workspace(&self, config: &ProjectConfig) -> Result<()> {
        // Create .vapora directory structure
        let vapora_dir = self.root_path.join(".vapora");
        let dirs = vec![
            vapora_dir.clone(),
            vapora_dir.join("config"),
            vapora_dir.join("tracking"),
            vapora_dir.join("sbom"),
            vapora_dir.join("backups"),
        ];

        for dir in dirs {
            tokio::fs::create_dir_all(&dir).await.map_err(|e| {
                LifecycleError::integration(format!("Failed to create directory {:?}: {}", dir, e))
            })?;
        }

        // Save config
        let config_path = vapora_dir.join("config").join("project.toml");
        let config_str = toml::to_string_pretty(config).map_err(|e| {
            LifecycleError::integration(format!("Failed to serialize config: {}", e))
        })?;

        tokio::fs::write(&config_path, config_str)
            .await
            .map_err(|e| LifecycleError::integration(format!("Failed to write config: {}", e)))?;

        tracing::info!("PLM workspace initialized at {:?}", vapora_dir);

        Ok(())
    }

    // Helper methods

    fn detect_project_type(&self) -> Result<DetectedProjectType> {
        // Check for Rust
        if self.root_path.join("Cargo.toml").exists() {
            return Ok(DetectedProjectType::Rust);
        }

        // Check for Node.js
        if self.root_path.join("package.json").exists() {
            return Ok(DetectedProjectType::NodeJs);
        }

        // Check for Python
        if self.root_path.join("pyproject.toml").exists()
            || self.root_path.join("setup.py").exists()
            || self.root_path.join("requirements.txt").exists()
        {
            return Ok(DetectedProjectType::Python);
        }

        // Check for Go
        if self.root_path.join("go.mod").exists() {
            return Ok(DetectedProjectType::Go);
        }

        // Check for Java
        if self.root_path.join("pom.xml").exists() || self.root_path.join("build.gradle").exists() {
            return Ok(DetectedProjectType::Java);
        }

        // Check for C#
        if std::fs::read_dir(&self.root_path)
            .ok()
            .map(|entries| {
                entries.filter_map(|e| e.ok()).any(|e| {
                    e.path()
                        .extension()
                        .map(|ext| ext == "csproj")
                        .unwrap_or(false)
                })
            })
            .unwrap_or(false)
        {
            return Ok(DetectedProjectType::CSharp);
        }

        // Check for C++
        if self.root_path.join("CMakeLists.txt").exists() {
            return Ok(DetectedProjectType::Cpp);
        }

        Ok(DetectedProjectType::Unknown)
    }

    fn extract_project_name(&self, project_type: &DetectedProjectType) -> Result<String> {
        match project_type {
            DetectedProjectType::Rust => self
                .extract_from_cargo_toml("name")
                .or_else(|_| self.extract_from_dir_name()),
            DetectedProjectType::NodeJs => self
                .extract_from_package_json("name")
                .or_else(|_| self.extract_from_dir_name()),
            DetectedProjectType::Python => self
                .extract_from_pyproject_toml("name")
                .or_else(|_| self.extract_from_dir_name()),
            _ => self.extract_from_dir_name(),
        }
    }

    fn extract_version(&self, project_type: &DetectedProjectType) -> Result<String> {
        let version = match project_type {
            DetectedProjectType::Rust => self
                .extract_from_cargo_toml("version")
                .unwrap_or_else(|_| "0.1.0".to_string()),
            DetectedProjectType::NodeJs => self
                .extract_from_package_json("version")
                .unwrap_or_else(|_| "0.1.0".to_string()),
            DetectedProjectType::Python => self
                .extract_from_pyproject_toml("version")
                .unwrap_or_else(|_| "0.1.0".to_string()),
            _ => "0.1.0".to_string(),
        };
        Ok(version)
    }

    fn detect_languages(&self, project_type: &DetectedProjectType) -> Result<Vec<String>> {
        match project_type {
            DetectedProjectType::Rust => Ok(vec!["rust".to_string()]),
            DetectedProjectType::NodeJs => {
                Ok(vec!["javascript".to_string(), "typescript".to_string()])
            }
            DetectedProjectType::Python => Ok(vec!["python".to_string()]),
            DetectedProjectType::Go => Ok(vec!["go".to_string()]),
            DetectedProjectType::Java => Ok(vec!["java".to_string()]),
            DetectedProjectType::CSharp => Ok(vec!["csharp".to_string()]),
            DetectedProjectType::Cpp => Ok(vec!["cpp".to_string()]),
            DetectedProjectType::Unknown => Ok(vec!["unknown".to_string()]),
        }
    }

    fn extract_description(&self) -> Option<String> {
        // Try to read from README
        let readme_path = self.root_path.join("README.md");
        if readme_path.exists() {
            if let Ok(content) = std::fs::read_to_string(&readme_path) {
                // Extract first non-empty line after title
                return content
                    .lines()
                    .find(|line| !line.starts_with('#') && !line.trim().is_empty())
                    .map(|line| line.trim().to_string());
            }
        }
        None
    }

    fn find_dependency_files(&self) -> Vec<PathBuf> {
        let mut files = Vec::new();
        let patterns = vec![
            "Cargo.toml",
            "package.json",
            "package-lock.json",
            "yarn.lock",
            "Gemfile",
            "requirements.txt",
            "pyproject.toml",
            "setup.py",
            "go.mod",
            "go.sum",
            "pom.xml",
            "build.gradle",
        ];

        for pattern in patterns {
            let path = self.root_path.join(pattern);
            if path.exists() {
                files.push(path);
            }
        }

        files
    }

    fn is_git_repository(&self) -> bool {
        self.root_path.join(".git").exists()
    }

    fn detect_build_tools(&self) -> Vec<String> {
        let mut tools = Vec::new();

        if self.root_path.join("Makefile").exists() {
            tools.push("make".to_string());
        }
        if self.root_path.join("CMakeLists.txt").exists() {
            tools.push("cmake".to_string());
        }
        if self.root_path.join("Cargo.toml").exists() {
            tools.push("cargo".to_string());
        }
        if self.root_path.join("package.json").exists() {
            tools.push("npm".to_string());
        }
        if self.root_path.join("pyproject.toml").exists() {
            tools.push("poetry".to_string());
        }

        tools
    }

    fn find_doc_directories(&self) -> Vec<PathBuf> {
        let mut dirs = Vec::new();
        let candidates = vec!["docs", "doc", "documentation", "guide", "guides"];

        for candidate in candidates {
            let path = self.root_path.join(candidate);
            if path.is_dir() {
                dirs.push(path);
            }
        }

        dirs
    }

    fn extract_from_cargo_toml(&self, key: &str) -> Result<String> {
        let content = std::fs::read_to_string(self.root_path.join("Cargo.toml"))?;
        let toml_value: toml::Value = toml::from_str(&content)?;

        toml_value
            .get("package")
            .and_then(|p| p.get(key))
            .and_then(|v| v.as_str())
            .map(|s| s.to_string())
            .ok_or_else(|| {
                LifecycleError::integration(format!("Key '{}' not found in Cargo.toml", key))
            })
    }

    fn extract_from_package_json(&self, key: &str) -> Result<String> {
        let content = std::fs::read_to_string(self.root_path.join("package.json"))?;
        let json: serde_json::Value = serde_json::from_str(&content)?;

        json.get(key)
            .and_then(|v| v.as_str())
            .map(|s| s.to_string())
            .ok_or_else(|| {
                LifecycleError::integration(format!("Key '{}' not found in package.json", key))
            })
    }

    fn extract_from_pyproject_toml(&self, key: &str) -> Result<String> {
        let content = std::fs::read_to_string(self.root_path.join("pyproject.toml"))?;
        let toml_value: toml::Value = toml::from_str(&content)?;

        toml_value
            .get("project")
            .and_then(|p| p.get(key))
            .and_then(|v| v.as_str())
            .map(|s| s.to_string())
            .ok_or_else(|| {
                LifecycleError::integration(format!("Key '{}' not found in pyproject.toml", key))
            })
    }

    fn extract_from_dir_name(&self) -> Result<String> {
        self.root_path
            .file_name()
            .and_then(|name| name.to_str())
            .map(|s| s.to_string())
            .ok_or_else(|| LifecycleError::integration("Cannot extract directory name".to_string()))
    }

    fn map_to_project_type(&self, detected: &DetectedProjectType) -> ProjectType {
        match detected {
            DetectedProjectType::Rust => ProjectType::RustWeb,
            DetectedProjectType::NodeJs => ProjectType::RustWeb, // Using RustWeb as generic web project type
            DetectedProjectType::Python => ProjectType::Custom("python-project".to_string()),
            DetectedProjectType::Go => ProjectType::Custom("go-service".to_string()),
            DetectedProjectType::Java => ProjectType::Custom("java-project".to_string()),
            DetectedProjectType::CSharp => ProjectType::Custom("csharp-project".to_string()),
            DetectedProjectType::Cpp => ProjectType::Custom("cpp-project".to_string()),
            DetectedProjectType::Unknown => ProjectType::MultiLang,
        }
    }
}

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

    #[test]
    fn test_detect_rust_project() {
        if let Ok(temp) = TempDir::new() {
            let cargo_content = "[package]\nname = \"test-proj\"\nversion = \"0.1.0\"";
            if std::fs::write(temp.path().join("Cargo.toml"), cargo_content).is_ok() {
                let migrator = ProjectMigrator::new(temp.path().to_path_buf());
                if let Ok(detected) = migrator.detect_project() {
                    assert_eq!(detected.project_type, DetectedProjectType::Rust);
                    assert_eq!(detected.name, "test-proj");
                    assert_eq!(detected.version, "0.1.0");
                }
            }
        }
    }

    #[test]
    fn test_detect_nodejs_project() {
        if let Ok(temp) = TempDir::new() {
            let json = r#"{"name": "my-app", "version": "1.2.3"}"#;
            if std::fs::write(temp.path().join("package.json"), json).is_ok() {
                let migrator = ProjectMigrator::new(temp.path().to_path_buf());
                if let Ok(detected) = migrator.detect_project() {
                    assert_eq!(detected.project_type, DetectedProjectType::NodeJs);
                    assert_eq!(detected.name, "my-app");
                    assert_eq!(detected.version, "1.2.3");
                }
            }
        }
    }

    #[test]
    fn test_detect_python_project() {
        if let Ok(temp) = TempDir::new() {
            let pyproject = "[project]\nname = \"py-app\"\nversion = \"2.0.0\"";
            if std::fs::write(temp.path().join("pyproject.toml"), pyproject).is_ok() {
                let migrator = ProjectMigrator::new(temp.path().to_path_buf());
                if let Ok(detected) = migrator.detect_project() {
                    assert_eq!(detected.project_type, DetectedProjectType::Python);
                    assert_eq!(detected.name, "py-app");
                    assert_eq!(detected.version, "2.0.0");
                }
            }
        }
    }

    #[test]
    fn test_find_dependency_files() {
        if let Ok(temp) = TempDir::new() {
            let _ = std::fs::write(temp.path().join("Cargo.toml"), "");
            let _ = std::fs::write(temp.path().join("package.json"), "");

            let migrator = ProjectMigrator::new(temp.path().to_path_buf());
            if let Ok(detected) = migrator.detect_project() {
                assert_eq!(detected.dependency_files.len(), 2);
            }
        }
    }

    #[test]
    fn test_extract_description_from_readme() {
        if let Ok(temp) = TempDir::new() {
            let _ = std::fs::write(temp.path().join("Cargo.toml"), "[package]\nname = \"test\"");
            let _ = std::fs::write(
                temp.path().join("README.md"),
                "# My Project\n\nThis is a test project description.",
            );

            let migrator = ProjectMigrator::new(temp.path().to_path_buf());
            if let Ok(detected) = migrator.detect_project() {
                assert!(detected.description.is_some());
                if let Some(desc) = &detected.description {
                    assert!(desc.contains("test project"));
                }
            }
        }
    }

    #[test]
    fn test_detect_git_repository() {
        if let Ok(temp) = TempDir::new() {
            let _ = std::fs::write(temp.path().join("Cargo.toml"), "");
            let _ = std::fs::create_dir(temp.path().join(".git"));

            let migrator = ProjectMigrator::new(temp.path().to_path_buf());
            if let Ok(detected) = migrator.detect_project() {
                assert!(detected.is_git_repo);
            }
        }
    }

    #[test]
    fn test_migrate_to_config() {
        if let Ok(temp) = TempDir::new() {
            let cargo_content = "[package]\nname = \"migration-test\"\nversion = \"0.2.5\"";
            if std::fs::write(temp.path().join("Cargo.toml"), cargo_content).is_ok() {
                let migrator = ProjectMigrator::new(temp.path().to_path_buf());
                if let Ok(detected) = migrator.detect_project() {
                    if let Ok(config) = migrator.migrate_to_config(&detected) {
                        assert_eq!(config.project.name, "migration-test");
                        assert_eq!(config.project.version, "0.2.5");
                        assert_eq!(config.primary_language, "rust");
                    }
                }
            }
        }
    }
}
