//! Template engine for project generation and Rust tool scaffolding

use crate::error::Result;
use crate::types::TemplateVariables;
use serde::{Deserialize, Serialize};
use std::path::Path;

/// Rust tool types for custom scaffolding
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum RustToolType {
    /// Criterion benchmarking framework
    #[serde(rename = "criterion")]
    Criterion,
    /// Metrics and telemetry instrumentation
    #[serde(rename = "metrics")]
    Metrics,
    /// Bacon build watcher
    #[serde(rename = "bacon")]
    Bacon,
}

impl RustToolType {
    /// Get string representation
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Criterion => "criterion",
            Self::Metrics => "metrics",
            Self::Bacon => "bacon",
        }
    }

    /// Get description of the tool
    pub fn description(&self) -> &'static str {
        match self {
            Self::Criterion => "Benchmark testing framework for Rust",
            Self::Metrics => "Metrics and telemetry instrumentation",
            Self::Bacon => "Build watcher and command runner",
        }
    }

    /// Parse from string
    #[allow(clippy::should_implement_trait)]
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "criterion" => Some(Self::Criterion),
            "metrics" => Some(Self::Metrics),
            "bacon" => Some(Self::Bacon),
            _ => None,
        }
    }

    /// Get all available tools
    pub fn all() -> Vec<Self> {
        vec![Self::Criterion, Self::Metrics, Self::Bacon]
    }
}

/// Rust tool template definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RustToolTemplate {
    /// Tool type
    pub tool_type: RustToolType,
    /// Template files to generate (path -> content)
    pub files: std::collections::BTreeMap<String, String>,
    /// Cargo.toml dependencies to add
    pub dependencies: Vec<(String, String)>,
}

impl RustToolTemplate {
    /// Create a new Criterion template
    pub fn criterion(project_name: &str, _version: &str) -> Self {
        let benchmark_rs = format!(
            r#"use criterion::{{black_box, criterion_group, criterion_main, Criterion}};
use {project_name}::*;

fn benchmark_main(c: &mut Criterion) {{
    c.bench_function("example", |b| {{
        b.iter(|| {{
            // Add your benchmark code here
            black_box(1 + 1)
        }});
    }});
}}

criterion_group!(benches, benchmark_main);
criterion_main!(benches);
"#,
            project_name = project_name.replace("-", "_")
        );

        let mut files = std::collections::BTreeMap::new();
        files.insert("benches/benchmarks.rs".to_string(), benchmark_rs);

        let cargo_toml_section =
            "criterion = { version = \"0.5\", features = [\"html_reports\"] }".to_string();

        Self {
            tool_type: RustToolType::Criterion,
            files,
            dependencies: vec![("criterion".to_string(), cargo_toml_section)],
        }
    }

    /// Create a new Metrics template
    pub fn metrics(_project_name: &str, _version: &str) -> Self {
        let metrics_rs = r#"//! Metrics and telemetry instrumentation
//!
//! This module provides metrics collection and instrumentation
//! for monitoring application performance.

use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

/// Application metrics collector
#[derive(Debug, Clone)]
pub struct Metrics {
    /// Request counter
    pub requests: Arc<AtomicU64>,
    /// Error counter
    pub errors: Arc<AtomicU64>,
    /// Average response time in milliseconds
    pub response_time_ms: Arc<AtomicU64>,
}

impl Metrics {
    /// Create a new metrics instance
    pub fn new() -> Self {
        Self {
            requests: Arc::new(AtomicU64::new(0)),
            errors: Arc::new(AtomicU64::new(0)),
            response_time_ms: Arc::new(AtomicU64::new(0)),
        }
    }

    /// Increment request counter
    pub fn inc_requests(&self) {
        self.requests.fetch_add(1, Ordering::Relaxed);
    }

    /// Increment error counter
    pub fn inc_errors(&self) {
        self.errors.fetch_add(1, Ordering::Relaxed);
    }

    /// Record response time
    pub fn record_response_time(&self, ms: u64) {
        self.response_time_ms.store(ms, Ordering::Relaxed);
    }

    /// Get current metrics snapshot
    pub fn snapshot(&self) -> MetricsSnapshot {
        MetricsSnapshot {
            requests: self.requests.load(Ordering::Relaxed),
            errors: self.errors.load(Ordering::Relaxed),
            response_time_ms: self.response_time_ms.load(Ordering::Relaxed),
        }
    }
}

impl Default for Metrics {
    fn default() -> Self {
        Self::new()
    }
}

/// Metrics snapshot at a point in time
#[derive(Debug, Clone)]
pub struct MetricsSnapshot {
    pub requests: u64,
    pub errors: u64,
    pub response_time_ms: u64,
}
"#
        .to_string();

        let mut files = std::collections::BTreeMap::new();
        files.insert("src/metrics.rs".to_string(), metrics_rs);

        Self {
            tool_type: RustToolType::Metrics,
            files,
            dependencies: vec![],
        }
    }

    /// Create a new Bacon template
    pub fn bacon(project_name: &str, _version: &str) -> Self {
        let bacon_toml = format!(
            r#"# Bacon configuration for {}

[jobs.check]
command = ["cargo", "check", "--all-targets"]
need_stdout = false

[jobs.clippy]
command = ["cargo", "clippy", "--all-targets", "--all-features"]
need_stdout = false

[jobs.test]
command = ["cargo", "test", "--all"]
need_stdout = false

[jobs.doc]
command = ["cargo", "doc", "--no-deps", "--open"]
need_stdout = false

[jobs.build]
command = ["cargo", "build", "--release"]
need_stdout = false

[jobs.bench]
command = ["cargo", "bench"]
need_stdout = false

# Default job when bacon starts
default_job = "check"

# Watch patterns
# bacon watches all .rs files by default
[[watch_patterns]]
ignore = [
    "target",
    ".git",
    ".vscode",
    ".idea",
]
"#,
            project_name
        );

        let mut files = std::collections::BTreeMap::new();
        files.insert("bacon.toml".to_string(), bacon_toml);

        Self {
            tool_type: RustToolType::Bacon,
            files,
            dependencies: vec![],
        }
    }

    /// Create template for a given tool type
    pub fn for_tool(tool_type: RustToolType, project_name: &str, version: &str) -> Self {
        match tool_type {
            RustToolType::Criterion => Self::criterion(project_name, version),
            RustToolType::Metrics => Self::metrics(project_name, version),
            RustToolType::Bacon => Self::bacon(project_name, version),
        }
    }
}

/// Template engine
#[derive(Debug)]
pub struct TemplateEngine {
    /// Templates path
    pub templates_path: std::path::PathBuf,
    /// Template variables
    pub variables: TemplateVariables,
}

impl TemplateEngine {
    /// Create a new template engine
    pub fn new(templates_path: impl AsRef<Path>) -> Self {
        Self {
            templates_path: templates_path.as_ref().to_path_buf(),
            variables: TemplateVariables::new(),
        }
    }

    /// Render a template file
    pub fn render_file(&self, template_path: &Path, output_path: &Path) -> Result<()> {
        let content = std::fs::read_to_string(template_path)?;
        let rendered = self.render_string(&content)?;
        std::fs::write(output_path, rendered)?;
        Ok(())
    }

    /// Render a template string
    pub fn render_string(&self, template: &str) -> Result<String> {
        let mut result = template.to_string();

        // Simple variable substitution
        // {variable_name;} for required
        // {variable_name:default;} for optional with default
        for (key, value) in &self.variables.variables {
            let required_pattern = format!("{{{};}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}{{", key);
            result = result.replace(&required_pattern, value);

            let optional_pattern = format!("{{{{{}:.*?;}}}}}}", key);
            if let Ok(re) = regex::Regex::new(&optional_pattern) {
                result = re.replace_all(&result, value.as_str()).to_string();
            }
        }

        Ok(result)
    }

    /// Collect variables interactively
    pub fn collect_variables_interactive(&mut self) -> Result<()> {
        // This will be implemented in Phase 4
        Ok(())
    }

    /// Collect variables from config
    pub fn collect_variables_from_config(&mut self, variables: &TemplateVariables) -> Result<()> {
        self.variables = variables.clone();
        Ok(())
    }

    /// Generate project structure from template
    pub fn generate_project_structure(&self, _project_type: &str) -> Result<()> {
        // This will be implemented in Phase 4
        Ok(())
    }

    /// List available templates
    pub fn list_templates(&self) -> Result<Vec<String>> {
        // This will be implemented in Phase 4
        Ok(vec![])
    }

    /// Generate Rust tool template files in the project
    pub fn generate_rust_tool(
        &self,
        tool_type: RustToolType,
        project_root: &Path,
        project_name: &str,
        version: &str,
    ) -> Result<RustToolTemplate> {
        let template = RustToolTemplate::for_tool(tool_type, project_name, version);

        // Create files
        for (file_path, content) in &template.files {
            let full_path = project_root.join(file_path);
            let parent_dir = full_path.parent().ok_or_else(|| {
                crate::error::LifecycleError::template(format!("Invalid path: {:?}", file_path))
            })?;

            std::fs::create_dir_all(parent_dir)?;
            std::fs::write(&full_path, content)?;
        }

        Ok(template)
    }

    /// Check if a Rust tool template already exists
    pub fn rust_tool_exists(&self, tool_type: RustToolType, project_root: &Path) -> bool {
        let template = RustToolTemplate::for_tool(tool_type, "", "");
        for file_path in template.files.keys() {
            let full_path = project_root.join(file_path);
            if full_path.exists() {
                return true;
            }
        }
        false
    }

    /// List all Rust tools that can be generated
    pub fn list_rust_tools() -> Vec<(RustToolType, &'static str)> {
        RustToolType::all()
            .into_iter()
            .map(|tool| (tool, tool.description()))
            .collect()
    }
}

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

    #[test]
    fn test_template_engine_creation() {
        let engine = TemplateEngine::new("/tmp/templates");
        assert_eq!(engine.templates_path.to_str().unwrap(), "/tmp/templates");
    }

    #[test]
    fn test_template_engine_with_variables() {
        let mut engine = TemplateEngine::new("/tmp/templates");
        engine.variables.insert("name", "my-app");
        assert_eq!(engine.variables.get("name"), Some("my-app"));
    }

    // Rust Tool Type tests
    #[test]
    fn test_rust_tool_type_as_str() {
        assert_eq!(RustToolType::Criterion.as_str(), "criterion");
        assert_eq!(RustToolType::Metrics.as_str(), "metrics");
        assert_eq!(RustToolType::Bacon.as_str(), "bacon");
    }

    #[test]
    fn test_rust_tool_type_description() {
        assert_eq!(
            RustToolType::Criterion.description(),
            "Benchmark testing framework for Rust"
        );
        assert_eq!(
            RustToolType::Metrics.description(),
            "Metrics and telemetry instrumentation"
        );
        assert_eq!(
            RustToolType::Bacon.description(),
            "Build watcher and command runner"
        );
    }

    #[test]
    fn test_rust_tool_type_from_str() {
        assert_eq!(
            RustToolType::from_str("criterion"),
            Some(RustToolType::Criterion)
        );
        assert_eq!(
            RustToolType::from_str("metrics"),
            Some(RustToolType::Metrics)
        );
        assert_eq!(RustToolType::from_str("bacon"), Some(RustToolType::Bacon));
        assert_eq!(RustToolType::from_str("invalid"), None);

        // Test case insensitive
        assert_eq!(
            RustToolType::from_str("CRITERION"),
            Some(RustToolType::Criterion)
        );
    }

    #[test]
    fn test_rust_tool_type_all() {
        let all = RustToolType::all();
        assert_eq!(all.len(), 3);
        assert!(all.contains(&RustToolType::Criterion));
        assert!(all.contains(&RustToolType::Metrics));
        assert!(all.contains(&RustToolType::Bacon));
    }

    // Rust Tool Template tests
    #[test]
    fn test_criterion_template_creation() {
        let template = RustToolTemplate::criterion("my-project", "0.1.0");
        assert_eq!(template.tool_type, RustToolType::Criterion);
        assert!(template.files.contains_key("benches/benchmarks.rs"));
        assert_eq!(template.dependencies.len(), 1);
        assert_eq!(template.dependencies[0].0, "criterion");
    }

    #[test]
    fn test_criterion_template_content() {
        let template = RustToolTemplate::criterion("my-project", "0.1.0");
        let content = &template.files["benches/benchmarks.rs"];
        assert!(content.contains("use criterion"));
        assert!(content.contains("criterion_group!"));
        assert!(content.contains("criterion_main!"));
        assert!(content.contains("my_project")); // Project name with underscore
    }

    #[test]
    fn test_metrics_template_creation() {
        let template = RustToolTemplate::metrics("my-project", "0.1.0");
        assert_eq!(template.tool_type, RustToolType::Metrics);
        assert!(template.files.contains_key("src/metrics.rs"));
        assert!(template.dependencies.is_empty());
    }

    #[test]
    fn test_metrics_template_content() {
        let template = RustToolTemplate::metrics("my-project", "0.1.0");
        let content = &template.files["src/metrics.rs"];
        assert!(content.contains("pub struct Metrics"));
        assert!(content.contains("AtomicU64"));
        assert!(content.contains("pub fn new()"));
        assert!(content.contains("MetricsSnapshot"));
    }

    #[test]
    fn test_bacon_template_creation() {
        let template = RustToolTemplate::bacon("my-project", "0.1.0");
        assert_eq!(template.tool_type, RustToolType::Bacon);
        assert!(template.files.contains_key("bacon.toml"));
        assert!(template.dependencies.is_empty());
    }

    #[test]
    fn test_bacon_template_content() {
        let template = RustToolTemplate::bacon("my-project", "0.1.0");
        let content = &template.files["bacon.toml"];
        assert!(content.contains("[jobs.check]"));
        assert!(content.contains("[jobs.test]"));
        assert!(content.contains("my-project"));
        assert!(content.contains("default_job = \"check\""));
    }

    #[test]
    fn test_rust_tool_template_for_tool() {
        let criterion = RustToolTemplate::for_tool(RustToolType::Criterion, "test", "1.0");
        assert_eq!(criterion.tool_type, RustToolType::Criterion);

        let metrics = RustToolTemplate::for_tool(RustToolType::Metrics, "test", "1.0");
        assert_eq!(metrics.tool_type, RustToolType::Metrics);

        let bacon = RustToolTemplate::for_tool(RustToolType::Bacon, "test", "1.0");
        assert_eq!(bacon.tool_type, RustToolType::Bacon);
    }

    #[test]
    fn test_template_engine_list_rust_tools() {
        let tools = TemplateEngine::list_rust_tools();
        assert_eq!(tools.len(), 3);

        let tool_names: Vec<&str> = tools.iter().map(|(t, _)| t.as_str()).collect();
        assert!(tool_names.contains(&"criterion"));
        assert!(tool_names.contains(&"metrics"));
        assert!(tool_names.contains(&"bacon"));
    }

    #[test]
    fn test_template_engine_generate_rust_tool() {
        let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let engine = TemplateEngine::new(temp_dir.path());

        let result = engine.generate_rust_tool(
            RustToolType::Criterion,
            temp_dir.path(),
            "test-project",
            "0.1.0",
        );

        assert!(result.is_ok());

        // Check if files were created
        let benchmark_file = temp_dir.path().join("benches/benchmarks.rs");
        assert!(benchmark_file.exists());

        let content = std::fs::read_to_string(benchmark_file).unwrap();
        assert!(content.contains("use criterion"));
    }

    #[test]
    fn test_template_engine_generate_all_rust_tools() {
        let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let engine = TemplateEngine::new(temp_dir.path());

        for tool_type in RustToolType::all() {
            let result = engine.generate_rust_tool(tool_type, temp_dir.path(), "test", "0.1.0");
            assert!(result.is_ok());
        }

        // Check all files exist
        assert!(temp_dir.path().join("benches/benchmarks.rs").exists());
        assert!(temp_dir.path().join("src/metrics.rs").exists());
        assert!(temp_dir.path().join("bacon.toml").exists());
    }

    #[test]
    fn test_template_engine_rust_tool_exists() {
        let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
        let engine = TemplateEngine::new(temp_dir.path());

        // Initially should not exist
        assert!(!engine.rust_tool_exists(RustToolType::Criterion, temp_dir.path()));

        // Generate criterion template
        engine
            .generate_rust_tool(RustToolType::Criterion, temp_dir.path(), "test", "0.1.0")
            .unwrap();

        // Now should exist
        assert!(engine.rust_tool_exists(RustToolType::Criterion, temp_dir.path()));

        // But metrics should not exist
        assert!(!engine.rust_tool_exists(RustToolType::Metrics, temp_dir.path()));
    }

    #[test]
    fn test_rust_tool_template_metrics_module_structure() {
        let template = RustToolTemplate::metrics("my-app", "1.0.0");
        let content = &template.files["src/metrics.rs"];

        // Check module structure
        assert!(content.contains("//! Metrics and telemetry instrumentation"));
        assert!(content.contains("impl Metrics"));
        assert!(content.contains("impl Default for Metrics"));

        // Check atomic operations
        assert!(content.contains("fetch_add"));
        assert!(content.contains("Ordering::Relaxed"));
    }

    #[test]
    fn test_rust_tool_template_criterion_dependencies() {
        let template = RustToolTemplate::criterion("test", "0.1.0");
        assert!(!template.dependencies.is_empty());
        assert!(template.dependencies[0].1.contains("0.5"));
        assert!(template.dependencies[0].1.contains("html_reports"));
    }
}
