//! Setup wizard — multi-step project onboarding flow with per-step status tracking.
use serde::{Deserialize, Serialize};
use serde_json;
use std::fs;
use std::path::Path;

/// Ordered wizard steps for project setup.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WizardStep {
    /// Step 1: scaffold .huskies/ directory structure and project.toml
    Scaffold,
    /// Step 2: generate specs/00_CONTEXT.md
    Context,
    /// Step 3: generate specs/tech/STACK.md
    Stack,
    /// Step 4: create script/test
    TestScript,
    /// Step 5: create script/build
    BuildScript,
    /// Step 6: create script/lint
    LintScript,
    /// Step 7: create script/release
    ReleaseScript,
    /// Step 8: create script/test_coverage
    TestCoverage,
}

impl WizardStep {
    /// All steps in order.
    pub const ALL: &[WizardStep] = &[
        WizardStep::Scaffold,
        WizardStep::Context,
        WizardStep::Stack,
        WizardStep::TestScript,
        WizardStep::BuildScript,
        WizardStep::LintScript,
        WizardStep::ReleaseScript,
        WizardStep::TestCoverage,
    ];

    /// Human-readable label for this step.
    pub fn label(&self) -> &'static str {
        match self {
            WizardStep::Scaffold => "Scaffold directory structure",
            WizardStep::Context => "Generate project context (00_CONTEXT.md)",
            WizardStep::Stack => "Generate tech stack spec (STACK.md)",
            WizardStep::TestScript => "Create test script (script/test)",
            WizardStep::BuildScript => "Create build script (script/build)",
            WizardStep::LintScript => "Create lint script (script/lint)",
            WizardStep::ReleaseScript => "Create release script (script/release)",
            WizardStep::TestCoverage => "Create test coverage script (script/test_coverage)",
        }
    }

    /// Zero-based index of this step.
    pub fn index(&self) -> usize {
        Self::ALL.iter().position(|s| s == self).unwrap_or(0)
    }
}

/// Status of an individual wizard step.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StepStatus {
    /// Not yet started.
    Pending,
    /// Agent is generating content for this step.
    Generating,
    /// Content generated, awaiting user confirmation.
    AwaitingConfirmation,
    /// User confirmed this step.
    Confirmed,
    /// User skipped this step.
    Skipped,
}

/// State of a single wizard step.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StepState {
    pub step: WizardStep,
    pub status: StepStatus,
    /// The generated content (if any) for preview.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
}

/// Persistent wizard state, stored in `.huskies/wizard_state.json`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WizardState {
    pub steps: Vec<StepState>,
    /// True when all steps are confirmed or skipped.
    pub completed: bool,
}

impl Default for WizardState {
    fn default() -> Self {
        Self {
            steps: WizardStep::ALL
                .iter()
                .map(|&step| StepState {
                    step,
                    status: StepStatus::Pending,
                    content: None,
                })
                .collect(),
            completed: false,
        }
    }
}

impl WizardState {
    /// Path to the wizard state file relative to the project root.
    fn state_path(project_root: &Path) -> std::path::PathBuf {
        project_root.join(".huskies").join("wizard_state.json")
    }

    /// Load wizard state from disk, or return None if it doesn't exist.
    pub fn load(project_root: &Path) -> Option<Self> {
        let path = Self::state_path(project_root);
        let content = fs::read_to_string(&path).ok()?;
        serde_json::from_str(&content).ok()
    }

    /// Save wizard state to disk.
    pub fn save(&self, project_root: &Path) -> Result<(), String> {
        let path = Self::state_path(project_root);
        let content =
            serde_json::to_string_pretty(self).map_err(|e| format!("Serialize error: {e}"))?;
        fs::write(&path, content).map_err(|e| format!("Failed to write wizard state: {e}"))
    }

    /// Create wizard state file if it doesn't already exist.
    /// Step 1 (Scaffold) is automatically confirmed since `huskies init`
    /// has already run the scaffold.
    pub fn init_if_missing(project_root: &Path) {
        if Self::load(project_root).is_some() {
            return;
        }
        let mut state = Self::default();
        // Scaffold step is done by the time the server starts.
        state.steps[0].status = StepStatus::Confirmed;
        let _ = state.save(project_root);
    }

    /// Get the current step index (0-based).
    pub fn current_step_index(&self) -> usize {
        self.steps
            .iter()
            .position(|s| !matches!(s.status, StepStatus::Confirmed | StepStatus::Skipped))
            .unwrap_or(self.steps.len())
    }

    /// Mark a step's status and update completion state.
    pub fn set_step_status(
        &mut self,
        step: WizardStep,
        status: StepStatus,
        content: Option<String>,
    ) {
        if let Some(s) = self.steps.iter_mut().find(|s| s.step == step) {
            s.status = status;
            if content.is_some() {
                s.content = content;
            }
        }
        self.completed = self
            .steps
            .iter()
            .all(|s| matches!(s.status, StepStatus::Confirmed | StepStatus::Skipped));
    }

    /// Confirm a step. Returns error if the step is not the current one
    /// (enforces sequential progression).
    pub fn confirm_step(&mut self, step: WizardStep) -> Result<(), String> {
        let current_idx = self.current_step_index();
        let target_idx = step.index();
        if target_idx != current_idx {
            return Err(format!(
                "Cannot confirm step {:?}: current step is {}",
                step, current_idx
            ));
        }
        self.set_step_status(step, StepStatus::Confirmed, None);
        Ok(())
    }

    /// Skip a step. Only the current step can be skipped.
    pub fn skip_step(&mut self, step: WizardStep) -> Result<(), String> {
        let current_idx = self.current_step_index();
        let target_idx = step.index();
        if target_idx != current_idx {
            return Err(format!(
                "Cannot skip step {:?}: current step is {}",
                step, current_idx
            ));
        }
        self.set_step_status(step, StepStatus::Skipped, None);
        Ok(())
    }
}

/// Format a `WizardState` as a human-readable Markdown summary for display in
/// bot messages and MCP responses.
pub fn format_wizard_state(state: &WizardState) -> String {
    let total = state.steps.len();
    let current_idx = state.current_step_index();

    let header = if state.completed {
        format!("**Setup wizard — complete** ({total}/{total} steps done)")
    } else {
        format!("**Setup wizard — step {}/{}**", current_idx + 1, total)
    };

    let mut lines = vec![header, String::new()];

    for (i, step) in state.steps.iter().enumerate() {
        let marker = match step.status {
            StepStatus::Confirmed => "✓",
            StepStatus::Skipped => "~",
            StepStatus::Generating => "⟳",
            StepStatus::AwaitingConfirmation => "?",
            StepStatus::Pending => "○",
        };
        let is_current = !state.completed && i == current_idx;
        let suffix = if is_current { " ← current" } else { "" };
        let status_str = serde_json::to_value(&step.status)
            .ok()
            .and_then(|v| v.as_str().map(String::from))
            .unwrap_or_default();
        lines.push(format!(
            "  {} {} ({}){suffix}",
            marker,
            step.step.label(),
            status_str
        ));
    }

    if state.completed {
        lines.push(String::new());
        lines.push("All steps done. Your project is fully configured.".to_string());
    } else {
        let current = &state.steps[current_idx];
        lines.push(String::new());
        lines.push(format!("**Current:** {}", current.step.label()));
        let hint = match current.status {
            StepStatus::Pending => {
                "Ready to generate. Proceed by calling wizard_generate.".to_string()
            }
            StepStatus::Generating => "Generating content…".to_string(),
            StepStatus::AwaitingConfirmation => {
                "Content ready for review. Show it to the user and ask if they're happy with it. Then call wizard_confirm, wizard_retry, or wizard_skip based on their response.".to_string()
            }
            StepStatus::Confirmed | StepStatus::Skipped => String::new(),
        };
        if !hint.is_empty() {
            lines.push(hint);
        }
    }

    lines.join("\n")
}

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

    #[test]
    fn default_state_has_all_steps_pending() {
        let state = WizardState::default();
        assert_eq!(state.steps.len(), 8);
        for step in &state.steps {
            assert_eq!(step.status, StepStatus::Pending);
        }
        assert!(!state.completed);
    }

    #[test]
    fn init_if_missing_creates_state_with_scaffold_confirmed() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        WizardState::init_if_missing(&root);

        let state = WizardState::load(&root).unwrap();
        assert_eq!(state.steps[0].status, StepStatus::Confirmed);
        assert_eq!(state.steps[0].step, WizardStep::Scaffold);
        // Rest should be pending
        for step in &state.steps[1..] {
            assert_eq!(step.status, StepStatus::Pending);
        }
    }

    #[test]
    fn init_if_missing_does_not_overwrite_existing() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        // Create a custom state
        let mut state = WizardState::default();
        state.steps[0].status = StepStatus::Confirmed;
        state.steps[1].status = StepStatus::Confirmed;
        state.save(&root).unwrap();

        // init_if_missing should not overwrite
        WizardState::init_if_missing(&root);

        let loaded = WizardState::load(&root).unwrap();
        assert_eq!(loaded.steps[1].status, StepStatus::Confirmed);
    }

    #[test]
    fn save_and_load_round_trip() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        let mut state = WizardState::default();
        state.steps[0].status = StepStatus::Confirmed;
        state.steps[1].status = StepStatus::AwaitingConfirmation;
        state.steps[1].content = Some("# My Project\n\nA cool project.".to_string());
        state.save(&root).unwrap();

        let loaded = WizardState::load(&root).unwrap();
        assert_eq!(loaded.steps[0].status, StepStatus::Confirmed);
        assert_eq!(loaded.steps[1].status, StepStatus::AwaitingConfirmation);
        assert_eq!(
            loaded.steps[1].content.as_deref(),
            Some("# My Project\n\nA cool project.")
        );
    }

    #[test]
    fn current_step_index_correct() {
        let mut state = WizardState::default();
        state.steps[0].status = StepStatus::Confirmed;
        assert_eq!(state.current_step_index(), 1);

        state.steps[1].status = StepStatus::Skipped;
        assert_eq!(state.current_step_index(), 2);
    }

    #[test]
    fn confirm_step_enforces_order() {
        let mut state = WizardState::default();
        state.steps[0].status = StepStatus::Confirmed;

        // Can confirm the current step (Context, index 1)
        assert!(state.confirm_step(WizardStep::Context).is_ok());

        // Cannot confirm a step that's not current
        assert!(state.confirm_step(WizardStep::TestScript).is_err());
    }

    #[test]
    fn skip_step_works() {
        let mut state = WizardState::default();
        state.steps[0].status = StepStatus::Confirmed;

        assert!(state.skip_step(WizardStep::Context).is_ok());
        assert_eq!(state.steps[1].status, StepStatus::Skipped);
        assert_eq!(state.current_step_index(), 2);
    }

    #[test]
    fn completed_when_all_confirmed_or_skipped() {
        let mut state = WizardState::default();
        for step in WizardStep::ALL {
            state.set_step_status(*step, StepStatus::Confirmed, None);
        }
        assert!(state.completed);
    }

    #[test]
    fn not_completed_when_some_pending() {
        let mut state = WizardState::default();
        state.set_step_status(WizardStep::Scaffold, StepStatus::Confirmed, None);
        assert!(!state.completed);
    }

    #[test]
    fn set_step_status_with_content() {
        let mut state = WizardState::default();
        state.set_step_status(
            WizardStep::Context,
            StepStatus::AwaitingConfirmation,
            Some("generated content".to_string()),
        );
        assert_eq!(state.steps[1].status, StepStatus::AwaitingConfirmation);
        assert_eq!(state.steps[1].content.as_deref(), Some("generated content"));
    }

    #[test]
    fn load_returns_none_when_no_file() {
        let dir = TempDir::new().unwrap();
        assert!(WizardState::load(dir.path()).is_none());
    }

    #[test]
    fn step_labels_are_non_empty() {
        for step in WizardStep::ALL {
            assert!(!step.label().is_empty());
        }
    }

    #[test]
    fn step_indices_are_sequential() {
        for (i, step) in WizardStep::ALL.iter().enumerate() {
            assert_eq!(step.index(), i);
        }
    }
}
