//! Init command implementation for ralph.
//!
//! Handles initialization of ralph.yml configuration files, either from
//! a minimal backend template or from an embedded preset.

use crate::backend_support;
use crate::presets::list_presets;
#[cfg(test)]
use crate::presets::{get_preset, preset_names};
use std::fs;
use std::path::Path;

/// Errors that can occur during initialization.
#[derive(Debug, thiserror::Error)]
pub enum InitError {
    #[error(
        "ralph.yml already exists. Use --force to overwrite.\nSee: docs/reference/troubleshooting.md#config-file-exists"
    )]
    FileExists,

    #[cfg(test)]
    #[error(
        "Unknown preset '{0}'. Available presets: {1}\nSee: docs/reference/troubleshooting.md#unknown-preset"
    )]
    UnknownPreset(String, String),

    #[error("{0}")]
    UnknownBackend(String),

    #[error("Failed to write ralph.yml: {0}")]
    WriteError(#[from] std::io::Error),

    #[cfg(test)]
    #[error("Failed to parse/generate YAML: {0}")]
    YamlError(String),
}

/// Valid backend names.
const VALID_BACKENDS: &[&str] = backend_support::VALID_BACKENDS;

/// Generates the minimal config template for a given backend.
fn generate_template(backend: &str) -> String {
    format!(
        r#"# Ralph Orchestrator Configuration
# Generated by: ralph init --backend {backend}
# Docs: https://github.com/mikeyobrien/ralph-orchestrator

cli:
  backend: "{backend}"

event_loop:
  prompt_file: "PROMPT.md"
  completion_promise: "LOOP_COMPLETE"
  max_iterations: 100
  # max_runtime_seconds: 14400    # 4 hours max

# ─────────────────────────────────────────────────────────────────────────────
# Additional Configuration (uncomment to customize)
# ─────────────────────────────────────────────────────────────────────────────

# core:
#   scratchpad: ".ralph/agent/scratchpad.md"
#   specs_dir: ".ralph/specs/"

# Custom hats for multi-agent workflows:
# hats:
#   builder:
#     name: "Builder"
#     triggers: ["build.task"]
#     publishes: ["build.done", "build.blocked"]
#
#   reviewer:
#     name: "Reviewer"
#     triggers: ["review.request"]
#     publishes: ["review.approved", "review.changes_requested"]

# Create PROMPT.md with your task, then run: ralph run
"#
    )
}

/// Checks if ralph.yml exists and handles the force flag.
fn check_file_exists(force: bool) -> Result<(), InitError> {
    let path = Path::new("ralph.yml");
    if path.exists() && !force {
        return Err(InitError::FileExists);
    }
    Ok(())
}

/// Initializes ralph.yml from a minimal backend template.
///
/// # Arguments
/// * `backend` - The backend name (claude, kiro, gemini, codex, forge, amp, copilot, opencode, pi, custom)
/// * `force` - If true, overwrite existing ralph.yml
///
/// # Errors
/// Returns error if file exists (without force) or backend is invalid.
pub fn init_from_backend(backend: &str, force: bool) -> Result<(), InitError> {
    // Validate backend
    if !VALID_BACKENDS.contains(&backend) {
        return Err(InitError::UnknownBackend(
            backend_support::unknown_backend_message(backend),
        ));
    }

    check_file_exists(force)?;

    let content = generate_template(backend);
    fs::write("ralph.yml", content)?;

    Ok(())
}

/// Initializes ralph.yml from an embedded preset.
///
/// # Arguments
/// * `preset_name` - The name of the preset to use
/// * `backend_override` - Optional backend to override the preset's backend
/// * `force` - If true, overwrite existing ralph.yml
///
/// # Errors
/// Returns error if file exists (without force) or preset doesn't exist.
#[cfg(test)]
pub fn init_from_preset(
    preset_name: &str,
    backend_override: Option<&str>,
    force: bool,
) -> Result<(), InitError> {
    let preset = get_preset(preset_name).ok_or_else(|| {
        let available = preset_names().join(", ");
        InitError::UnknownPreset(preset_name.to_string(), available)
    })?;

    // Validate backend if provided
    if let Some(backend) = backend_override
        && !VALID_BACKENDS.contains(&backend)
    {
        return Err(InitError::UnknownBackend(
            backend_support::unknown_backend_message(backend),
        ));
    }

    check_file_exists(force)?;

    let content = if let Some(backend) = backend_override {
        override_backend_in_yaml(preset.content, backend)?
    } else {
        preset.content.to_string()
    };

    fs::write("ralph.yml", content)?;

    Ok(())
}

/// Overrides the backend field in YAML content using regex for surgical replacement.
/// Preserves all comments and formatting.
#[cfg(test)]
fn override_backend_in_yaml(content: &str, backend: &str) -> Result<String, InitError> {
    use regex::Regex;

    // Pattern to find backend: "value" or backend: value within cli section
    // This regex finds lines that start with whitespace followed by backend:
    let backend_re = Regex::new(r#"(?m)^(\s+backend:\s*)["']?[^"'\n]+["']?"#)
        .map_err(|e| InitError::YamlError(e.to_string()))?;

    // Check if there's a cli: section
    let cli_re = Regex::new(r"(?m)^cli:\s*$").map_err(|e| InitError::YamlError(e.to_string()))?;

    if cli_re.is_match(content) {
        // cli section exists - replace or insert backend
        if backend_re.is_match(content) {
            // Replace existing backend line
            let result = backend_re.replace(content, format!(r#"${{1}}"{backend}""#));
            Ok(result.into_owned())
        } else {
            // Insert backend after cli: line
            let result = cli_re.replace(content, format!("cli:\n  backend: \"{backend}\""));
            Ok(result.into_owned())
        }
    } else {
        // No cli section - add one at the start (after any leading comments)
        let first_key_re =
            Regex::new(r"(?m)^[a-z_]+:\s*").map_err(|e| InitError::YamlError(e.to_string()))?;

        if let Some(m) = first_key_re.find(content) {
            let pos = m.start();
            let mut result = String::with_capacity(content.len() + 50);
            result.push_str(&content[..pos]);
            result.push_str(&format!("cli:\n  backend: \"{backend}\"\n\n"));
            result.push_str(&content[pos..]);
            Ok(result)
        } else {
            // No keys at all - just prepend
            Ok(format!("cli:\n  backend: \"{backend}\"\n\n{content}"))
        }
    }
}

/// Formats the list of presets for display.
pub fn format_preset_list() -> String {
    let mut output = String::from("Available hat collections:\n\n");

    for preset in list_presets() {
        output.push_str(&format!("  {:<25} {}\n", preset.name, preset.description));
    }

    output.push_str("\nUsage:\n");
    output.push_str("  ralph init --backend <backend>\n");
    output.push_str("  ralph run -c ralph.yml -H builtin:<collection>\n");
    output
}

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

    #[test]
    fn test_generate_template_claude() {
        let template = generate_template("claude");
        assert!(template.contains("backend: \"claude\""));
        assert!(template.contains("prompt_file: \"PROMPT.md\""));
        assert!(template.contains("LOOP_COMPLETE"));
    }

    #[test]
    fn test_generate_template_kiro() {
        let template = generate_template("kiro");
        assert!(template.contains("backend: \"kiro\""));
    }

    #[test]
    fn test_generate_template_forge() {
        let template = generate_template("forge");
        assert!(template.contains("backend: \"forge\""));
    }

    #[test]
    fn test_template_is_valid_yaml() {
        for backend in VALID_BACKENDS {
            let template = generate_template(backend);
            let result: Result<serde_yaml::Value, _> = serde_yaml::from_str(&template);
            assert!(
                result.is_ok(),
                "Template for {} should be valid YAML: {:?}",
                backend,
                result.err()
            );
        }
    }

    #[test]
    fn test_format_preset_list() {
        let output = format_preset_list();
        assert!(output.contains("Available hat collections:"));
        assert!(output.contains("code-assist"));
        assert!(output.contains("debug"));
        assert!(output.contains("research"));
        assert!(output.contains("review"));
        assert!(output.contains("pdd-to-code-assist"));
        assert!(output.contains("Usage:"));
        assert!(output.contains("-H builtin:<collection>"));
    }

    #[test]
    fn test_init_from_preset_code_assist_writes_config() {
        let temp_dir = TempDir::new().expect("create temp dir");
        let _cwd = CwdGuard::set(temp_dir.path());

        init_from_preset("code-assist", None, false).expect("init_from_preset succeeds");

        let content = fs::read_to_string("ralph.yml").expect("read ralph.yml");
        assert!(
            content.contains("build.start") && content.contains("LOOP_COMPLETE"),
            "expected event loop configuration in generated config"
        );
    }

    #[test]
    fn test_unknown_backend_error() {
        // We can't actually test file operations without filesystem mocking,
        // but we can test the validation logic
        let result = init_from_backend("invalid-backend", false);
        assert!(matches!(result, Err(InitError::UnknownBackend(_))));
    }

    #[test]
    fn test_unknown_backend_message_actionable() {
        let result = init_from_backend("invalid-backend", false);
        assert!(result.is_err());

        let err = result.expect_err("expected init error");
        let msg = err.to_string();
        assert!(msg.contains("Invalid-backend") || msg.contains("invalid-backend"));
        assert!(msg.contains("Valid backends"));
    }

    #[test]
    fn test_file_exists_message_actionable() {
        let err = InitError::FileExists;
        let msg = err.to_string();
        assert!(msg.contains("--force"));
        assert!(msg.contains("docs/reference/troubleshooting.md#config-file-exists"));
    }

    #[test]
    fn test_override_backend_replaces_existing() {
        let content = r#"cli:
  backend: "claude"
event_loop:
  max_iterations: 100
"#;
        let result = super::override_backend_in_yaml(content, "kiro").unwrap();
        assert!(result.contains(r#"backend: "kiro""#));
        assert!(!result.contains(r#"backend: "claude""#));
    }

    #[test]
    fn test_override_backend_inserts_when_missing() {
        let content = r#"cli:
  prompt_mode: "arg"
event_loop:
  max_iterations: 100
"#;
        let result = super::override_backend_in_yaml(content, "gemini").unwrap();
        assert!(result.contains(r#"backend: "gemini""#));
    }

    #[test]
    fn test_override_backend_adds_cli_section() {
        let content = r"# Comment header
event_loop:
  max_iterations: 100
";
        let result = super::override_backend_in_yaml(content, "codex").unwrap();
        assert!(result.contains("cli:"));
        assert!(result.contains(r#"backend: "codex""#));
        // Comment should still be present
        assert!(result.contains("# Comment header"));
    }

    #[test]
    fn test_override_backend_preserves_comments() {
        let content = r#"# This is a preset
# With helpful comments
cli:
  backend: "claude"  # inline comment
"#;
        let result = super::override_backend_in_yaml(content, "amp").unwrap();
        assert!(result.contains("# This is a preset"));
        assert!(result.contains("# With helpful comments"));
    }
}
