//! Logic for the `timebomb init` subcommand.
//!
//! Bootstraps a `.timebomb.toml` configuration file in a target directory by
//! auto-detecting the tech stack and writing a tailored config. Also prints a
//! CI snippet to stdout so the user can copy it into their pipeline.

use crate::error::{Error, Result};
use colored::Colorize;
use std::io::{self, BufRead, Write};
use std::path::{Path, PathBuf};

// ---------------------------------------------------------------------------
// DetectedStack
// ---------------------------------------------------------------------------

/// Describes which tech stacks were detected in the target directory.
#[derive(Debug, Default)]
pub struct DetectedStack {
    /// `Cargo.toml` present
    pub has_rust: bool,
    /// `package.json` present
    pub has_node: bool,
    /// `go.mod` present
    pub has_go: bool,
    /// `pyproject.toml` OR `setup.py` OR `requirements.txt` present
    pub has_python: bool,
    /// `pom.xml` OR `build.gradle` present
    pub has_java: bool,
    /// `Gemfile` present
    pub has_ruby: bool,
    /// Any `*.tf` file present
    pub has_terraform: bool,
}

// ---------------------------------------------------------------------------
// detect_stack
// ---------------------------------------------------------------------------

/// Shallow scan of `dir` (no recursion) to detect which tech stacks are present.
pub fn detect_stack(dir: &Path) -> DetectedStack {
    let mut stack = DetectedStack {
        has_rust: dir.join("Cargo.toml").exists(),
        has_node: dir.join("package.json").exists(),
        has_go: dir.join("go.mod").exists(),
        has_python: dir.join("pyproject.toml").exists()
            || dir.join("setup.py").exists()
            || dir.join("requirements.txt").exists(),
        has_java: dir.join("pom.xml").exists() || dir.join("build.gradle").exists(),
        has_ruby: dir.join("Gemfile").exists(),
        ..DetectedStack::default()
    };

    // Terraform: check if any entry in the directory has a `.tf` extension
    if let Ok(entries) = std::fs::read_dir(dir) {
        for entry in entries.flatten() {
            if let Some(ext) = entry.path().extension() {
                if ext == "tf" {
                    stack.has_terraform = true;
                    break;
                }
            }
        }
    }

    stack
}

// ---------------------------------------------------------------------------
// build_config_toml
// ---------------------------------------------------------------------------

/// Build a `.timebomb.toml` string tailored to the detected stack.
pub fn build_config_toml(stack: &DetectedStack) -> String {
    // Accumulate stack-specific excludes
    let mut extra_excludes: Vec<&str> = Vec::new();

    if stack.has_rust {
        extra_excludes.push("target/**");
    }
    if stack.has_node {
        extra_excludes.push("node_modules/**");
        extra_excludes.push("dist/**");
        extra_excludes.push("build/**");
    }
    if stack.has_go {
        extra_excludes.push("vendor/**");
    }
    if stack.has_python {
        extra_excludes.push("__pycache__/**");
        extra_excludes.push("*.pyc");
        extra_excludes.push(".venv/**");
        extra_excludes.push("venv/**");
    }
    if stack.has_java {
        extra_excludes.push("target/**");
        extra_excludes.push("build/**");
        extra_excludes.push(".gradle/**");
    }
    if stack.has_ruby {
        extra_excludes.push("vendor/**");
    }
    if stack.has_terraform {
        extra_excludes.push(".terraform/**");
    }

    // Build the exclude list: universal defaults + stack-specific
    let universal_excludes = vec![".git/**", "*.min.js"];
    let all_excludes: Vec<&str> = universal_excludes
        .into_iter()
        .chain(extra_excludes)
        .collect();

    // Format exclude entries
    let exclude_entries: String = all_excludes
        .iter()
        .map(|e| format!("  \"{}\",\n", e))
        .collect();

    format!(
        r#"# timebomb configuration
# Generated by `timebomb init`
# See https://github.com/yourname/timebomb for documentation

# Triggers to scan for (case-insensitive)
triggers = ["TODO", "FIXME", "HACK", "TEMP", "REMOVEME", "DEBT", "STOPSHIP", "WORKAROUND", "DEPRECATED", "BUG"]

# Warn if a fuse expires within this many days
fuse_days = 14

# File extensions to scan
extensions = [
  "rs", "go", "ts", "js", "py", "rb",
  "java", "sql", "tf", "yaml", "yml",
]

# Paths to exclude
exclude = [
{exclude_entries}]
"#,
        exclude_entries = exclude_entries,
    )
}

// ---------------------------------------------------------------------------
// print_ci_snippet
// ---------------------------------------------------------------------------

/// Print a generic CI integration snippet to stdout.
/// The `_stack` parameter is reserved for future stack-specific output;
/// currently the same GitHub Actions snippet is emitted regardless of the detected stack.
pub fn print_ci_snippet(_stack: &DetectedStack) {
    let divider = "─".repeat(46);
    let divider_colored = divider.cyan().dimmed();

    println!("{}", divider_colored);
    println!("Add timebomb to your CI (GitHub Actions):");
    println!();
    println!("  - name: Check for expired timebombs");
    println!("    uses: actions/checkout@v4");
    println!();
    println!("  - name: Install timebomb");
    println!("    run: cargo install timebomb");
    println!();
    println!("  - name: Run timebomb");
    println!("    run: timebomb sweep --fuse 14d");
    println!();
    println!("{}", divider_colored);
}

// ---------------------------------------------------------------------------
// run_init  — public entry point
// ---------------------------------------------------------------------------

/// Core logic for `timebomb init`.
///
/// # Parameters
/// - `dir` — directory to initialise (write `.timebomb.toml` here)
/// - `yes` — skip confirmation prompts when `true`
pub fn run_init(dir: &Path, yes: bool) -> Result<i32> {
    let config_path: PathBuf = dir.join(".timebomb.toml");

    // 1. Check if .timebomb.toml already exists ------------------------------
    if config_path.exists() {
        eprintln!(
            "warning: .timebomb.toml already exists at {}",
            config_path.display()
        );

        if !yes {
            print!("Overwrite? [y/N]: ");
            io::stdout().flush().map_err(|e| Error::Io {
                source: e,
                path: None,
            })?;

            let stdin = io::stdin();
            let mut line_buf = String::new();
            stdin
                .lock()
                .read_line(&mut line_buf)
                .map_err(|e| Error::Io {
                    source: e,
                    path: None,
                })?;

            let response = line_buf.trim();
            if response != "y" && response != "Y" {
                return Ok(0);
            }
        }
    }

    // 2. Detect stack --------------------------------------------------------
    let stack = detect_stack(dir);

    // 3. Print detected stacks summary ---------------------------------------
    println!("Detected stacks:");
    let mut any_detected = false;

    if stack.has_rust {
        println!("  {} Rust    (Cargo.toml)", "✓".green());
        any_detected = true;
    }
    if stack.has_node {
        println!("  {} Node    (package.json)", "✓".green());
        any_detected = true;
    }
    if stack.has_go {
        println!("  {} Go      (go.mod)", "✓".green());
        any_detected = true;
    }
    if stack.has_python {
        println!(
            "  {} Python  (pyproject.toml / setup.py / requirements.txt)",
            "✓".green()
        );
        any_detected = true;
    }
    if stack.has_java {
        println!("  {} Java    (pom.xml / build.gradle)", "✓".green());
        any_detected = true;
    }
    if stack.has_ruby {
        println!("  {} Ruby    (Gemfile)", "✓".green());
        any_detected = true;
    }
    if stack.has_terraform {
        println!("  {} Terraform (*.tf)", "✓".green());
        any_detected = true;
    }

    if !any_detected {
        println!("  (no known stack files found — using defaults)");
    }

    // 4. Build config TOML ---------------------------------------------------
    let toml_content = build_config_toml(&stack);

    // 5. Print preview -------------------------------------------------------
    println!("\nWill write .timebomb.toml to: {}", config_path.display());

    // 6. Prompt Write? [Y/n] (unless --yes) ----------------------------------
    if !yes {
        print!("Write? [Y/n]: ");
        io::stdout().flush().map_err(|e| Error::Io {
            source: e,
            path: None,
        })?;

        let stdin = io::stdin();
        let mut line_buf = String::new();
        stdin
            .lock()
            .read_line(&mut line_buf)
            .map_err(|e| Error::Io {
                source: e,
                path: None,
            })?;

        let response = line_buf.trim();
        if response == "n" || response == "N" {
            return Ok(0);
        }
        // Empty string, "y", "Y" all continue
    }

    // 7. Write .timebomb.toml ------------------------------------------------
    std::fs::write(&config_path, &toml_content).map_err(|e| Error::Io {
        source: e,
        path: Some(config_path.clone()),
    })?;

    // 8. Print confirmation --------------------------------------------------
    println!("{}", "✓ Wrote .timebomb.toml".green());

    // 9. Print CI snippet ----------------------------------------------------
    print_ci_snippet(&stack);

    // 10. Return success -----------------------------------------------------
    Ok(0)
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    // -----------------------------------------------------------------------
    // detect_stack tests
    // -----------------------------------------------------------------------

    #[test]
    fn test_detect_stack_empty_dir() {
        let tmp = TempDir::new().unwrap();
        let stack = detect_stack(tmp.path());
        assert!(!stack.has_rust);
        assert!(!stack.has_node);
        assert!(!stack.has_go);
        assert!(!stack.has_python);
        assert!(!stack.has_java);
        assert!(!stack.has_ruby);
        assert!(!stack.has_terraform);
    }

    #[test]
    fn test_detect_stack_rust() {
        let tmp = TempDir::new().unwrap();
        fs::write(tmp.path().join("Cargo.toml"), "[package]").unwrap();
        let stack = detect_stack(tmp.path());
        assert!(stack.has_rust);
        assert!(!stack.has_node);
        assert!(!stack.has_go);
        assert!(!stack.has_python);
        assert!(!stack.has_java);
        assert!(!stack.has_ruby);
        assert!(!stack.has_terraform);
    }

    #[test]
    fn test_detect_stack_node() {
        let tmp = TempDir::new().unwrap();
        fs::write(tmp.path().join("package.json"), "{}").unwrap();
        let stack = detect_stack(tmp.path());
        assert!(!stack.has_rust);
        assert!(stack.has_node);
    }

    #[test]
    fn test_detect_stack_python() {
        let tmp = TempDir::new().unwrap();
        fs::write(tmp.path().join("pyproject.toml"), "[build-system]").unwrap();
        let stack = detect_stack(tmp.path());
        assert!(stack.has_python);
        assert!(!stack.has_rust);
    }

    #[test]
    fn test_detect_stack_python_requirements() {
        let tmp = TempDir::new().unwrap();
        fs::write(tmp.path().join("requirements.txt"), "requests==2.0").unwrap();
        let stack = detect_stack(tmp.path());
        assert!(stack.has_python);
    }

    #[test]
    fn test_detect_stack_go() {
        let tmp = TempDir::new().unwrap();
        fs::write(tmp.path().join("go.mod"), "module example.com/foo").unwrap();
        let stack = detect_stack(tmp.path());
        assert!(stack.has_go);
        assert!(!stack.has_rust);
    }

    #[test]
    fn test_detect_stack_terraform() {
        let tmp = TempDir::new().unwrap();
        fs::write(tmp.path().join("main.tf"), "provider \"aws\" {}").unwrap();
        let stack = detect_stack(tmp.path());
        assert!(stack.has_terraform);
        assert!(!stack.has_rust);
    }

    #[test]
    fn test_detect_stack_multiple() {
        let tmp = TempDir::new().unwrap();
        fs::write(tmp.path().join("Cargo.toml"), "[package]").unwrap();
        fs::write(tmp.path().join("package.json"), "{}").unwrap();
        let stack = detect_stack(tmp.path());
        assert!(stack.has_rust);
        assert!(stack.has_node);
        assert!(!stack.has_go);
        assert!(!stack.has_python);
    }

    // -----------------------------------------------------------------------
    // build_config_toml tests
    // -----------------------------------------------------------------------

    #[test]
    fn test_build_config_toml_contains_triggers() {
        let stack = DetectedStack::default();
        let toml = build_config_toml(&stack);
        assert!(
            toml.contains("triggers ="),
            "expected 'triggers =' in output:\n{}",
            toml
        );
    }

    #[test]
    fn test_build_config_toml_contains_fuse_days() {
        let stack = DetectedStack::default();
        let toml = build_config_toml(&stack);
        assert!(
            toml.contains("fuse_days = 14"),
            "expected 'fuse_days = 14' in output:\n{}",
            toml
        );
    }

    #[test]
    fn test_build_config_toml_rust_excludes_target() {
        let stack = DetectedStack {
            has_rust: true,
            ..Default::default()
        };
        let toml = build_config_toml(&stack);
        assert!(
            toml.contains("\"target/**\""),
            "expected '\"target/**\"' in output:\n{}",
            toml
        );
    }

    #[test]
    fn test_build_config_toml_node_excludes_node_modules() {
        let stack = DetectedStack {
            has_node: true,
            ..Default::default()
        };
        let toml = build_config_toml(&stack);
        assert!(
            toml.contains("\"node_modules/**\""),
            "expected '\"node_modules/**\"' in output:\n{}",
            toml
        );
    }

    #[test]
    fn test_build_config_toml_python_excludes_pycache() {
        let stack = DetectedStack {
            has_python: true,
            ..Default::default()
        };
        let toml = build_config_toml(&stack);
        assert!(
            toml.contains("\"__pycache__/**\""),
            "expected '\"__pycache__/**\"' in output:\n{}",
            toml
        );
    }

    #[test]
    fn test_build_config_toml_is_valid_toml() {
        let stack = DetectedStack {
            has_rust: true,
            has_node: true,
            has_python: true,
            ..Default::default()
        };
        let toml_str = build_config_toml(&stack);
        let parsed = toml::from_str::<toml::Value>(&toml_str);
        assert!(
            parsed.is_ok(),
            "expected valid TOML, got error: {:?}\n\nContent:\n{}",
            parsed.err(),
            toml_str
        );
    }

    // -----------------------------------------------------------------------
    // run_init tests
    // -----------------------------------------------------------------------

    #[test]
    fn test_run_init_writes_file() {
        let tmp = TempDir::new().unwrap();
        let result = run_init(tmp.path(), true);
        assert!(result.is_ok(), "run_init failed: {:?}", result.err());
        assert_eq!(result.unwrap(), 0);

        let config_path = tmp.path().join(".timebomb.toml");
        assert!(config_path.exists(), ".timebomb.toml was not created");

        let contents = fs::read_to_string(&config_path).unwrap();
        let parsed = toml::from_str::<toml::Value>(&contents);
        assert!(
            parsed.is_ok(),
            "written file is not valid TOML: {:?}\n\nContent:\n{}",
            parsed.err(),
            contents
        );
    }

    #[test]
    fn test_run_init_existing_file_no_overwrite() {
        // With yes=true, second call should overwrite successfully
        let tmp = TempDir::new().unwrap();

        // First call: creates the file
        let r1 = run_init(tmp.path(), true);
        assert!(r1.is_ok());
        assert_eq!(r1.unwrap(), 0);

        let config_path = tmp.path().join(".timebomb.toml");
        assert!(config_path.exists());

        // Second call with yes=true: should overwrite
        let r2 = run_init(tmp.path(), true);
        assert!(r2.is_ok(), "second run_init failed: {:?}", r2.err());
        assert_eq!(r2.unwrap(), 0);

        // File should still exist and be valid TOML
        assert!(config_path.exists());
        let contents = fs::read_to_string(&config_path).unwrap();
        let parsed = toml::from_str::<toml::Value>(&contents);
        assert!(parsed.is_ok(), "file is not valid TOML after overwrite");
    }

    #[test]
    fn test_run_init_creates_correct_content() {
        let tmp = TempDir::new().unwrap();

        // Create Cargo.toml so Rust stack is detected
        fs::write(
            tmp.path().join("Cargo.toml"),
            "[package]\nname = \"test\"\n",
        )
        .unwrap();

        let result = run_init(tmp.path(), true);
        assert!(result.is_ok(), "run_init failed: {:?}", result.err());

        let config_path = tmp.path().join(".timebomb.toml");
        let contents = fs::read_to_string(&config_path).unwrap();

        assert!(
            contents.contains("\"target/**\""),
            "expected '\"target/**\"' in written config:\n{}",
            contents
        );

        // Also verify it parses as valid TOML
        let parsed = toml::from_str::<toml::Value>(&contents);
        assert!(parsed.is_ok(), "written file is not valid TOML");
    }
}
