//! Handler for the `show` command.

use super::CommandContext;

/// Display the full markdown text of a work item identified by its numeric ID.
///
/// Searches all pipeline stages in order and returns the raw file contents of
/// the first matching story, bug, or spike.  Returns a friendly message when
/// no match is found.
pub(super) fn handle_show(ctx: &CommandContext) -> Option<String> {
    let num_str = ctx.args.trim();
    if num_str.is_empty() {
        return Some(format!(
            "Usage: `{} show <number>`\n\nDisplays the full text of a story, bug, or spike.",
            ctx.bot_name
        ));
    }
    if !num_str.chars().all(|c| c.is_ascii_digit()) {
        return Some(format!(
            "Invalid story number: `{num_str}`. Usage: `{} show <number>`",
            ctx.bot_name
        ));
    }

    let stages = [
        "1_backlog",
        "2_current",
        "3_qa",
        "4_merge",
        "5_done",
        "6_archived",
    ];

    for stage in &stages {
        let dir = ctx
            .project_root
            .join(".storkit")
            .join("work")
            .join(stage);
        if !dir.exists() {
            continue;
        }
        if let Ok(entries) = std::fs::read_dir(&dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.extension().and_then(|e| e.to_str()) != Some("md") {
                    continue;
                }
                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
                    let file_num = stem
                        .split('_')
                        .next()
                        .filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
                        .unwrap_or("");
                    if file_num == num_str {
                        return match std::fs::read_to_string(&path) {
                            Ok(contents) => Some(contents),
                            Err(e) => Some(format!("Failed to read story {num_str}: {e}")),
                        };
                    }
                }
            }
        }
    }

    Some(format!(
        "No story, bug, or spike with number **{num_str}** found."
    ))
}

#[cfg(test)]
mod tests {
    use crate::agents::AgentPool;
    use std::collections::HashSet;
    use std::sync::{Arc, Mutex};

    use super::super::{CommandDispatch, try_handle_command};

    fn show_cmd_with_root(root: &std::path::Path, args: &str) -> Option<String> {
        let agents = Arc::new(AgentPool::new_test(3000));
        let ambient_rooms = Arc::new(Mutex::new(HashSet::new()));
        let room_id = "!test:example.com".to_string();
        let dispatch = CommandDispatch {
            bot_name: "Timmy",
            bot_user_id: "@timmy:homeserver.local",
            project_root: root,
            agents: &agents,
            ambient_rooms: &ambient_rooms,
            room_id: &room_id,
        };
        try_handle_command(&dispatch, &format!("@timmy show {args}"))
    }

    fn write_story_file(root: &std::path::Path, stage: &str, filename: &str, content: &str) {
        let dir = root.join(".storkit/work").join(stage);
        std::fs::create_dir_all(&dir).unwrap();
        std::fs::write(dir.join(filename), content).unwrap();
    }

    #[test]
    fn show_command_is_registered() {
        use super::super::commands;
        let found = commands().iter().any(|c| c.name == "show");
        assert!(found, "show command must be in the registry");
    }

    #[test]
    fn show_command_appears_in_help() {
        let result = super::super::tests::try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy help");
        let output = result.unwrap();
        assert!(output.contains("show"), "help should list show command: {output}");
    }

    #[test]
    fn show_command_no_args_returns_usage() {
        let tmp = tempfile::TempDir::new().unwrap();
        let output = show_cmd_with_root(tmp.path(), "").unwrap();
        assert!(
            output.contains("Usage"),
            "no args should show usage hint: {output}"
        );
    }

    #[test]
    fn show_command_non_numeric_args_returns_error() {
        let tmp = tempfile::TempDir::new().unwrap();
        let output = show_cmd_with_root(tmp.path(), "abc").unwrap();
        assert!(
            output.contains("Invalid"),
            "non-numeric arg should return error message: {output}"
        );
    }

    #[test]
    fn show_command_not_found_returns_friendly_message() {
        let tmp = tempfile::TempDir::new().unwrap();
        let output = show_cmd_with_root(tmp.path(), "999").unwrap();
        assert!(
            output.contains("999"),
            "not-found message should include the queried number: {output}"
        );
        assert!(
            output.contains("found"),
            "not-found message should say not found: {output}"
        );
    }

    #[test]
    fn show_command_finds_story_in_backlog() {
        let tmp = tempfile::TempDir::new().unwrap();
        write_story_file(
            tmp.path(),
            "1_backlog",
            "305_story_show_command.md",
            "---\nname: Show command\n---\n\n# Story 305\n\nFull story text here.",
        );
        let output = show_cmd_with_root(tmp.path(), "305").unwrap();
        assert!(
            output.contains("Full story text here."),
            "show should return full story content: {output}"
        );
    }

    #[test]
    fn show_command_finds_story_in_current() {
        let tmp = tempfile::TempDir::new().unwrap();
        write_story_file(
            tmp.path(),
            "2_current",
            "42_story_do_something.md",
            "---\nname: Do something\n---\n\n# Story 42\n\nIn progress.",
        );
        let output = show_cmd_with_root(tmp.path(), "42").unwrap();
        assert!(
            output.contains("In progress."),
            "show should return story from current stage: {output}"
        );
    }

    #[test]
    fn show_command_finds_bug() {
        let tmp = tempfile::TempDir::new().unwrap();
        write_story_file(
            tmp.path(),
            "1_backlog",
            "7_bug_crash_on_login.md",
            "---\nname: Crash on login\n---\n\n## Symptom\n\nCrashes.",
        );
        let output = show_cmd_with_root(tmp.path(), "7").unwrap();
        assert!(
            output.contains("Symptom"),
            "show should return bug content: {output}"
        );
    }

    #[test]
    fn show_command_case_insensitive() {
        let result = super::super::tests::try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy SHOW 1");
        assert!(result.is_some(), "SHOW should match case-insensitively");
    }
}
