//! Rebuild command: trigger a server rebuild and restart.
//!
//! `{bot_name} rebuild` stops all running agents, rebuilds the server binary
//! with `cargo build`, and re-execs the process with the new binary.  If the
//! build fails the error is reported back to the room and the server keeps
//! running.

use crate::agents::AgentPool;
use crate::chat::util::strip_bot_mention;
use std::path::Path;
use std::sync::Arc;

/// A parsed rebuild command.
#[derive(Debug, PartialEq)]
pub struct RebuildCommand;

/// Parse a rebuild command from a raw message body.
///
/// Strips the bot mention prefix and checks whether the command word is
/// `rebuild`.  Returns `None` when the message is not a rebuild command.
pub fn extract_rebuild_command(
    message: &str,
    bot_name: &str,
    bot_user_id: &str,
) -> Option<RebuildCommand> {
    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 = match trimmed.split_once(char::is_whitespace) {
        Some((c, _)) => c,
        None => trimmed,
    };

    if cmd.eq_ignore_ascii_case("rebuild") {
        Some(RebuildCommand)
    } else {
        None
    }
}

/// Handle a rebuild command: trigger server rebuild and restart.
///
/// Returns a string describing the outcome.  On build failure the error
/// message is returned so it can be posted to the room; the server keeps
/// running.  On success this function never returns (the process re-execs).
pub async fn handle_rebuild(
    bot_name: &str,
    project_root: &Path,
    agents: &Arc<AgentPool>,
) -> String {
    crate::slog!("[matrix-bot] rebuild command received (bot={bot_name})");
    match crate::rebuild::rebuild_and_restart(agents, project_root, None).await {
        Ok(msg) => msg,
        Err(e) => format!("Rebuild failed: {e}"),
    }
}

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

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

    #[test]
    fn extract_with_display_name() {
        let cmd = extract_rebuild_command("Timmy rebuild", "Timmy", "@timmy:home.local");
        assert_eq!(cmd, Some(RebuildCommand));
    }

    #[test]
    fn extract_with_full_user_id() {
        let cmd = extract_rebuild_command(
            "@timmy:home.local rebuild",
            "Timmy",
            "@timmy:home.local",
        );
        assert_eq!(cmd, Some(RebuildCommand));
    }

    #[test]
    fn extract_with_localpart() {
        let cmd = extract_rebuild_command("@timmy rebuild", "Timmy", "@timmy:home.local");
        assert_eq!(cmd, Some(RebuildCommand));
    }

    #[test]
    fn extract_case_insensitive() {
        let cmd = extract_rebuild_command("Timmy REBUILD", "Timmy", "@timmy:home.local");
        assert_eq!(cmd, Some(RebuildCommand));
    }

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

    #[test]
    fn extract_ignores_extra_args() {
        // "rebuild" with trailing text is still a rebuild command
        let cmd = extract_rebuild_command("Timmy rebuild now", "Timmy", "@timmy:home.local");
        assert_eq!(cmd, Some(RebuildCommand));
    }

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