//! Rmtree command: delete the worktree for a story without deleting the story file.
//!
//! `{bot_name} rmtree {number}` finds the worktree for the given story number,
//! stops any running agent, and removes the worktree directory and branch.
//! The story file in the pipeline is left untouched.

use crate::agents::{AgentPool, AgentStatus};
use crate::chat::util::strip_bot_mention;
use std::path::Path;

/// A parsed rmtree command from a Matrix message body.
#[derive(Debug, PartialEq)]
pub enum RmtreeCommand {
    /// Remove the worktree for the story with this number.
    Rmtree { story_number: String },
    /// The user typed `rmtree` but without a valid numeric argument.
    BadArgs,
}

/// Parse an rmtree command from a raw Matrix message body.
///
/// Strips the bot mention prefix and checks whether the first word is `rmtree`.
/// Returns `None` when the message is not an rmtree command at all.
pub fn extract_rmtree_command(
    message: &str,
    bot_name: &str,
    bot_user_id: &str,
) -> Option<RmtreeCommand> {
    let stripped = strip_bot_mention(message, bot_name, bot_user_id);
    let trimmed = stripped
        .trim()
        .trim_start_matches(|c: char| !c.is_alphanumeric());

    let (cmd, args) = match trimmed.split_once(char::is_whitespace) {
        Some((c, a)) => (c, a.trim()),
        None => (trimmed, ""),
    };

    if !cmd.eq_ignore_ascii_case("rmtree") {
        return None;
    }

    if !args.is_empty() && args.chars().all(|c| c.is_ascii_digit()) {
        Some(RmtreeCommand::Rmtree {
            story_number: args.to_string(),
        })
    } else {
        Some(RmtreeCommand::BadArgs)
    }
}

/// Handle an rmtree command asynchronously.
///
/// Finds the worktree for `story_number` under `.huskies/worktrees/`, stops any
/// running agent, and removes the worktree directory and its feature branch.
/// Returns a markdown-formatted response string.
pub async fn handle_rmtree(
    bot_name: &str,
    story_number: &str,
    project_root: &Path,
    agents: &AgentPool,
) -> String {
    // Find the story_id by listing worktree directories.
    let worktrees = match crate::worktree::list_worktrees(project_root) {
        Ok(wt) => wt,
        Err(e) => return format!("Failed to list worktrees: {e}"),
    };

    let entry = worktrees.into_iter().find(|e| {
        e.story_id
            .split('_')
            .next()
            .filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
            .map(|n| n == story_number)
            .unwrap_or(false)
    });

    let story_id = match entry {
        Some(e) => e.story_id,
        None => {
            return format!("No worktree found for story **{story_number}**.");
        }
    };

    // Stop any running or pending agents for this story.
    let running_agents: Vec<(String, String)> = agents
        .list_agents()
        .await
        .unwrap_or_default()
        .into_iter()
        .filter(|a| {
            a.story_id == story_id
                && matches!(a.status, AgentStatus::Running | AgentStatus::Pending)
        })
        .map(|a| (a.story_id.clone(), a.agent_name.clone()))
        .collect();

    let mut stopped_agents: Vec<String> = Vec::new();
    for (sid, agent_name) in &running_agents {
        if let Err(e) = agents.stop_agent(project_root, sid, agent_name).await {
            return format!("Failed to stop agent '{agent_name}' for story {story_number}: {e}");
        }
        stopped_agents.push(agent_name.clone());
    }

    // Remove the worktree.
    let config = crate::config::ProjectConfig::load(project_root).unwrap_or_default();
    if let Err(e) =
        crate::worktree::remove_worktree_by_story_id(project_root, &story_id, &config).await
    {
        return format!("Failed to remove worktree for story {story_number}: {e}");
    }

    crate::slog!("[matrix-bot] rmtree command: removed worktree for {story_id} (bot={bot_name})");

    let mut response = format!("Removed worktree for **{story_id}**.");
    if !stopped_agents.is_empty() {
        let agent_list = stopped_agents.join(", ");
        response.push_str(&format!(" Stopped agent(s): {agent_list}."));
    }
    response
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    // -- extract_rmtree_command ---------------------------------------------

    #[test]
    fn extract_with_full_user_id() {
        let cmd =
            extract_rmtree_command("@timmy:home.local rmtree 42", "Timmy", "@timmy:home.local");
        assert_eq!(
            cmd,
            Some(RmtreeCommand::Rmtree {
                story_number: "42".to_string()
            })
        );
    }

    #[test]
    fn extract_with_display_name() {
        let cmd = extract_rmtree_command("Timmy rmtree 310", "Timmy", "@timmy:home.local");
        assert_eq!(
            cmd,
            Some(RmtreeCommand::Rmtree {
                story_number: "310".to_string()
            })
        );
    }

    #[test]
    fn extract_with_localpart() {
        let cmd = extract_rmtree_command("@timmy rmtree 7", "Timmy", "@timmy:home.local");
        assert_eq!(
            cmd,
            Some(RmtreeCommand::Rmtree {
                story_number: "7".to_string()
            })
        );
    }

    #[test]
    fn extract_case_insensitive_command() {
        let cmd = extract_rmtree_command("Timmy RMTREE 99", "Timmy", "@timmy:home.local");
        assert_eq!(
            cmd,
            Some(RmtreeCommand::Rmtree {
                story_number: "99".to_string()
            })
        );
    }

    #[test]
    fn extract_no_args_is_bad_args() {
        let cmd = extract_rmtree_command("Timmy rmtree", "Timmy", "@timmy:home.local");
        assert_eq!(cmd, Some(RmtreeCommand::BadArgs));
    }

    #[test]
    fn extract_non_numeric_arg_is_bad_args() {
        let cmd = extract_rmtree_command("Timmy rmtree foo", "Timmy", "@timmy:home.local");
        assert_eq!(cmd, Some(RmtreeCommand::BadArgs));
    }

    #[test]
    fn extract_non_rmtree_command_returns_none() {
        let cmd = extract_rmtree_command("Timmy help", "Timmy", "@timmy:home.local");
        assert_eq!(cmd, None);
    }

    // -- handle_rmtree (integration-style, uses temp filesystem) -----------

    #[tokio::test]
    async fn handle_rmtree_returns_not_found_for_unknown_number() {
        let tmp = tempfile::tempdir().unwrap();
        let project_root = tmp.path();
        std::fs::create_dir_all(project_root.join(".huskies").join("worktrees")).unwrap();
        let agents = std::sync::Arc::new(crate::agents::AgentPool::new_test(3000));
        let response = handle_rmtree("Timmy", "999", project_root, &agents).await;
        assert!(
            response.contains("No worktree found") && response.contains("999"),
            "unexpected response: {response}"
        );
    }

    #[tokio::test]
    async fn handle_rmtree_removes_worktree_and_confirms() {
        let tmp = tempfile::tempdir().unwrap();
        let project_root = tmp.path().join("my-project");
        std::fs::create_dir_all(&project_root).unwrap();

        // Init a git repo so worktree ops work.
        std::process::Command::new("git")
            .args(["init"])
            .current_dir(&project_root)
            .output()
            .unwrap();
        std::process::Command::new("git")
            .args(["commit", "--allow-empty", "-m", "init"])
            .current_dir(&project_root)
            .output()
            .unwrap();

        // Create a real git worktree for story 42.
        let story_id = "42_story_some_feature";
        let wt_path = crate::worktree::worktree_path(&project_root, story_id);
        let branch = format!("feature/story-{story_id}");
        std::process::Command::new("git")
            .args(["worktree", "add", &wt_path.to_string_lossy(), "-b", &branch])
            .current_dir(&project_root)
            .output()
            .unwrap();
        assert!(wt_path.exists(), "worktree should exist before rmtree");

        let agents = std::sync::Arc::new(crate::agents::AgentPool::new_test(3000));
        let response = handle_rmtree("Timmy", "42", &project_root, &agents).await;

        assert!(
            response.contains("42_story_some_feature"),
            "unexpected response: {response}"
        );
        assert!(!wt_path.exists(), "worktree directory should be removed");
    }
}
