//! Handler for the `setup` bot command.
//!
//! Drives the setup wizard from any chat transport (Matrix, Slack, WhatsApp).
//!
//! Usage:
//! - `setup`         — show wizard progress and current step instructions
//! - `setup confirm` — confirm the current step (writes staged content to disk)
//! - `setup skip`    — skip the current step
//! - `setup retry`   — discard staged content and reset the current step

use super::CommandContext;
use crate::http::mcp::wizard_tools::{
    generation_hint, is_script_step, step_output_path, write_if_missing,
};
use crate::io::wizard::{StepStatus, WizardState, format_wizard_state};

pub(super) fn handle_setup(ctx: &CommandContext) -> Option<String> {
    let sub = ctx.args.trim().to_ascii_lowercase();

    match sub.as_str() {
        "" => Some(wizard_status_reply(ctx)),
        "generate" => Some(wizard_generate_reply(ctx)),
        "confirm" => Some(wizard_confirm_reply(ctx)),
        "skip" => Some(wizard_skip_reply(ctx)),
        "retry" => Some(wizard_retry_reply(ctx)),
        _ => Some(format!(
            "Unknown sub-command `{sub}`. Usage: `setup`, `setup generate`, `setup confirm`, `setup skip`, `setup retry`."
        )),
    }
}

/// Mark the current step as generating and return the generation hint.
///
/// This mirrors `wizard_generate` (with no content) from the MCP tools, making
/// the interview flow accessible from chat transports (Matrix, Slack, WhatsApp).
fn wizard_generate_reply(ctx: &CommandContext) -> String {
    let root = ctx.effective_root();
    let mut state = match WizardState::load(root) {
        Some(s) => s,
        None => return "No wizard active.".to_string(),
    };
    if state.completed {
        return "Wizard is already complete.".to_string();
    }

    let idx = state.current_step_index();
    let step = state.steps[idx].step;

    state.set_step_status(step, StepStatus::Generating, None);
    if let Err(e) = state.save(root) {
        return format!("Failed to save wizard state: {e}");
    }

    let hint = generation_hint(step, root);
    format!(
        "Step '{}' marked as generating.\n\n{hint}\n\nOnce you have the content, stage it via the API and then run `setup confirm` to write it to disk.",
        step.label()
    )
}

/// Compose a status reply for the `setup` command (no args).
///
/// If no wizard state exists, automatically initializes it so the user does
/// not need to run `huskies init` manually.
fn wizard_status_reply(ctx: &CommandContext) -> String {
    if WizardState::load(ctx.effective_root()).is_none() {
        WizardState::init_if_missing(ctx.effective_root());
    }
    match WizardState::load(ctx.effective_root()) {
        Some(state) => format_wizard_state(&state),
        None => "Unable to initialize setup wizard. Ensure the `.huskies/` directory exists."
            .to_string(),
    }
}

/// Confirm the current wizard step, writing any staged content to disk.
fn wizard_confirm_reply(ctx: &CommandContext) -> String {
    let root = ctx.effective_root();
    let mut state = match WizardState::load(root) {
        Some(s) => s,
        None => return "No wizard active.".to_string(),
    };
    if state.completed {
        return "Wizard is already complete.".to_string();
    }

    let idx = state.current_step_index();
    let step = state.steps[idx].step;
    let content = state.steps[idx].content.clone();

    // Write content to disk (only if a file path exists and the file is absent).
    let write_msg = if let (Some(c), Some(ref path)) = (&content, step_output_path(root, step)) {
        let executable = is_script_step(step);
        match write_if_missing(path, c, executable) {
            Ok(true) => format!(" File written: `{}`.", path.display()),
            Ok(false) => format!(" File `{}` already exists — skipped.", path.display()),
            Err(e) => return format!("Error: {e}"),
        }
    } else {
        String::new()
    };

    if let Err(e) = state.confirm_step(step) {
        return format!("Cannot confirm step: {e}");
    }
    if let Err(e) = state.save(root) {
        return format!("Failed to save wizard state: {e}");
    }

    if state.completed {
        format!(
            "Step '{}' confirmed.{write_msg}\n\nSetup wizard complete!",
            step.label()
        )
    } else {
        let next = &state.steps[state.current_step_index()];
        format!(
            "Step '{}' confirmed.{write_msg}\n\nNext: {} — run `wizard_generate` to begin.",
            step.label(),
            next.step.label()
        )
    }
}

/// Skip the current wizard step without writing any file.
fn wizard_skip_reply(ctx: &CommandContext) -> String {
    let root = ctx.effective_root();
    let mut state = match WizardState::load(root) {
        Some(s) => s,
        None => return "No wizard active.".to_string(),
    };
    if state.completed {
        return "Wizard is already complete.".to_string();
    }

    let idx = state.current_step_index();
    let step = state.steps[idx].step;

    if let Err(e) = state.skip_step(step) {
        return format!("Cannot skip step: {e}");
    }
    if let Err(e) = state.save(root) {
        return format!("Failed to save wizard state: {e}");
    }

    if state.completed {
        format!("Step '{}' skipped. Setup wizard complete!", step.label())
    } else {
        let next = &state.steps[state.current_step_index()];
        format!(
            "Step '{}' skipped.\n\nNext: {} — run `wizard_generate` to begin.",
            step.label(),
            next.step.label()
        )
    }
}

/// Discard staged content and reset the current step to pending.
fn wizard_retry_reply(ctx: &CommandContext) -> String {
    let root = ctx.effective_root();
    let mut state = match WizardState::load(root) {
        Some(s) => s,
        None => return "No wizard active.".to_string(),
    };
    if state.completed {
        return "Wizard is already complete.".to_string();
    }

    let idx = state.current_step_index();
    let step = state.steps[idx].step;

    if let Some(s) = state.steps.iter_mut().find(|s| s.step == step) {
        s.status = StepStatus::Pending;
        s.content = None;
    }
    if let Err(e) = state.save(root) {
        return format!("Failed to save wizard state: {e}");
    }

    format!(
        "Step '{}' reset to pending. Run `wizard_generate` to regenerate content.",
        step.label()
    )
}

// ── tests ─────────────────────────────────────────────────────────────────────

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

    fn make_ctx<'a>(
        args: &'a str,
        project_root: &'a std::path::Path,
        services: &'a crate::services::Services,
    ) -> CommandContext<'a> {
        CommandContext::new_test(services, args, "!test:example.com", project_root)
    }

    #[test]
    fn setup_no_wizard_auto_initializes() {
        let dir = TempDir::new().unwrap();
        std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        // Bot should auto-initialize and return wizard status, not ask user to run huskies init.
        assert!(result.contains("Setup wizard"));
        assert!(!result.contains("huskies init"));
        // Wizard state file should now exist.
        assert!(WizardState::load(dir.path()).is_some());
    }

    #[test]
    fn setup_with_wizard_shows_status() {
        let dir = TempDir::new().unwrap();
        std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
        WizardState::init_if_missing(dir.path());
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        assert!(result.contains("Setup wizard"));
    }

    #[test]
    fn setup_skip_advances_wizard() {
        let dir = TempDir::new().unwrap();
        std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
        WizardState::init_if_missing(dir.path());
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("skip", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        assert!(result.contains("skipped"));
        let state = WizardState::load(dir.path()).unwrap();
        assert_eq!(state.current_step_index(), 2);
    }

    #[test]
    fn setup_confirm_advances_wizard() {
        let dir = TempDir::new().unwrap();
        std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
        WizardState::init_if_missing(dir.path());
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("confirm", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        assert!(result.contains("confirmed"));
        let state = WizardState::load(dir.path()).unwrap();
        assert_eq!(state.current_step_index(), 2);
    }

    #[test]
    fn setup_retry_resets_step() {
        let dir = TempDir::new().unwrap();
        std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
        WizardState::init_if_missing(dir.path());
        // Stage some content first.
        {
            let mut state = WizardState::load(dir.path()).unwrap();
            state.set_step_status(
                crate::io::wizard::WizardStep::Context,
                crate::io::wizard::StepStatus::AwaitingConfirmation,
                Some("content".to_string()),
            );
            state.save(dir.path()).unwrap();
        }
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("retry", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        assert!(result.contains("reset"));
        let state = WizardState::load(dir.path()).unwrap();
        assert_eq!(
            state.steps[1].status,
            crate::io::wizard::StepStatus::Pending
        );
    }

    #[test]
    fn setup_unknown_sub_command_returns_usage() {
        let dir = TempDir::new().unwrap();
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("foobar", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        assert!(result.contains("Unknown sub-command"));
        assert!(result.contains("Usage"));
    }

    #[test]
    fn setup_generate_marks_generating_and_returns_hint() {
        let dir = TempDir::new().unwrap();
        std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
        WizardState::init_if_missing(dir.path());
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("generate", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        assert!(result.contains("generating"));
        let state = WizardState::load(dir.path()).unwrap();
        assert_eq!(
            state.steps[1].status,
            crate::io::wizard::StepStatus::Generating
        );
    }

    #[test]
    fn setup_generate_bare_project_asks_user() {
        let dir = TempDir::new().unwrap();
        // Bare project — only scaffolding files
        std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
        WizardState::init_if_missing(dir.path());
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("generate", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        assert!(result.contains("bare project"));
        assert!(result.contains("Ask the user"));
    }

    #[test]
    fn setup_generate_no_wizard_returns_error() {
        let dir = TempDir::new().unwrap();
        let services =
            crate::services::Services::new_test(dir.path().to_path_buf(), "Bot".to_string());
        let ctx = make_ctx("generate", dir.path(), &services);
        let result = handle_setup(&ctx).unwrap();
        assert!(result.contains("No wizard active"));
    }
}
