//! Handler for the `show` command.

use super::CommandContext;

/// Strip YAML front matter and return a summary of useful fields + the remaining body.
fn strip_front_matter(text: &str) -> (String, String) {
    let trimmed = text.trim_start();
    if !trimmed.starts_with("---") {
        return (String::new(), text.to_string());
    }

    // Find the closing ---
    if let Some(end) = trimmed[3..].find("\n---") {
        let yaml_block = &trimmed[3..3 + end].trim();
        let body = &trimmed[3 + end + 4..]; // skip past closing ---

        // Extract useful fields from YAML (simple line-based parsing)
        let mut parts = Vec::new();
        for line in yaml_block.lines() {
            let line = line.trim();
            if line.starts_with("depends_on:") {
                let val = line.trim_start_matches("depends_on:").trim();
                if !val.is_empty() && val != "[]" {
                    parts.push(format!("**Depends on:** {val}"));
                }
            } else if line.starts_with("agent:") {
                let val = line.trim_start_matches("agent:").trim().trim_matches('"');
                if !val.is_empty() {
                    parts.push(format!("**Agent:** {val}"));
                }
            } else if line.starts_with("blocked:") {
                let val = line.trim_start_matches("blocked:").trim();
                if val == "true" {
                    parts.push("**Blocked:** yes".to_string());
                }
            } else if line.starts_with("retry_count:") {
                let val = line.trim_start_matches("retry_count:").trim();
                if val != "0" && !val.is_empty() {
                    parts.push(format!("**Retries:** {val}"));
                }
            } else if line.starts_with("qa:") {
                let val = line.trim_start_matches("qa:").trim().trim_matches('"');
                if val == "human" {
                    parts.push("**QA:** human review required".to_string());
                }
            } else if line.starts_with("merge_failure:") {
                let val = line
                    .trim_start_matches("merge_failure:")
                    .trim()
                    .trim_matches('"');
                if !val.is_empty() {
                    parts.push(format!("**Merge failure:** {val}"));
                }
            }
        }

        (parts.join(" · "), body.to_string())
    } else {
        // No closing ---, return as-is
        (String::new(), text.to_string())
    }
}

/// Display the full markdown text of a work item identified by its numeric ID.
///
/// Lookup priority: CRDT → content store → filesystem (Story 512).
/// 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.services.bot_name
        ));
    }
    if !num_str.chars().all(|c| c.is_ascii_digit()) {
        return Some(format!(
            "Invalid story number: `{num_str}`. Usage: `{} show <number>`",
            ctx.services.bot_name
        ));
    }

    // Find the story by numeric prefix: CRDT → content store.
    let (story_id, _stage_dir, _path, content) =
        match crate::chat::lookup::find_story_by_number(ctx.effective_root(), num_str) {
            Some(found) => found,
            None => {
                return Some(format!(
                    "No story, bug, or spike with number **{num_str}** found."
                ));
            }
        };

    // `content` comes from the CRDT / content store.  If unavailable, report
    // it rather than silently reading a stale on-disk copy.
    let text = content.unwrap_or_else(|| {
        format!("Story {story_id} found in pipeline but its content is unavailable.")
    });

    // Strip front matter block and extract useful metadata to show inline.
    let (front_matter_summary, body) = strip_front_matter(&text);

    // Convert markdown headings to bold text for consistent rendering across
    // Matrix clients. Element X doesn't style <h2> tags distinctly, but bold
    // text renders consistently everywhere.
    let formatted = body
        .lines()
        .map(|line| {
            let trimmed = line.trim_start();
            if let Some(rest) = trimmed.strip_prefix("### ") {
                format!("\n**{}**", rest)
            } else if let Some(rest) = trimmed.strip_prefix("## ") {
                format!("\n**{}**", rest)
            } else if let Some(rest) = trimmed.strip_prefix("# ") {
                format!("\n**{}**", rest)
            } else {
                line.to_string()
            }
        })
        .collect::<Vec<_>>()
        .join("\n");

    if front_matter_summary.is_empty() {
        Some(formatted.trim().to_string())
    } else {
        Some(format!("{front_matter_summary}\n{}", formatted.trim()))
    }
}

#[cfg(test)]
mod tests {
    use super::super::{CommandDispatch, try_handle_command};

    fn show_cmd_with_root(root: &std::path::Path, args: &str) -> Option<String> {
        let services = crate::services::Services::new_test(root.to_path_buf(), "Timmy".to_string());
        let room_id = "!test:example.com".to_string();
        let dispatch = CommandDispatch {
            services: &services,
            project_root: &services.project_root,
            bot_user_id: "@timmy:homeserver.local",
            room_id: &room_id,
        };
        try_handle_command(&dispatch, &format!("@timmy show {args}"))
    }

    use crate::chat::test_helpers::write_story_file;

    #[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");
    }
}
