use std::path::PathBuf;
use std::str::FromStr;

use clap::Parser;

pub mod agent_meta;
pub mod claude;

/// Clap value parser for the Profile enum.
fn parse_profile(s: &str) -> Result<claude::Profile, String> {
    claude::Profile::from_str(s)
}
pub mod loop_runner;
pub mod monitor;
pub mod personalities;
pub mod progress;
pub mod prompt;
pub mod room;

/// Autonomous agent wrapper for room — runs `claude -p` with auto-restart
/// on context exhaustion.
///
/// Implements the "ralph loop" pattern: spawns fresh `claude -p` instances
/// in a loop, feeding room context and progress files on each restart.
/// Context exhaustion is not task death — progress persists in files.
#[derive(Parser, Debug)]
#[command(name = "room-ralph", version, about)]
pub struct Cli {
    /// Room ID to join
    #[arg(env = "RALPH_ROOM")]
    pub room_id: String,

    /// Username to register with
    #[arg(env = "RALPH_USERNAME")]
    pub username: String,

    /// Claude model to use
    #[arg(long, default_value = "opus", env = "RALPH_MODEL")]
    pub model: String,

    /// GitHub issue number — enables progress file persistence
    #[arg(long, env = "RALPH_ISSUE")]
    pub issue: Option<String>,

    /// Run in a detached tmux session (ralph-<username>)
    #[arg(long)]
    pub tmux: bool,

    /// Max iterations before stopping (0 = unlimited)
    #[arg(long, default_value_t = 50)]
    pub max_iter: u32,

    /// Seconds between iterations
    #[arg(long, default_value_t = 5)]
    pub cooldown: u64,

    /// Custom system prompt file (replaces built-in prompt)
    #[arg(long)]
    pub prompt: Option<PathBuf>,

    /// Personality — either a builtin name (coder, reviewer, researcher,
    /// coordinator, documenter) or a file path whose contents are prepended
    /// to the system prompt. Builtins also set profile and model defaults.
    #[arg(long)]
    pub personality: Option<String>,

    /// Print available builtin personalities and exit
    #[arg(long)]
    pub list_personalities: bool,

    /// Additional directories for claude --add-dir (repeatable)
    #[arg(long = "add-dir")]
    pub add_dirs: Vec<PathBuf>,

    /// Allowed tools for claude (comma-separated, passed as --allowedTools).
    /// Controls auto-approval — tools not listed may still be available
    /// but require user approval (which auto-denies in -p mode for most tools).
    #[arg(long = "allow-tools", value_delimiter = ',')]
    pub allow_tools: Vec<String>,

    /// Disallowed tools for claude (comma-separated, passed as --disallowedTools).
    /// Hard-blocks tools — they are completely removed from the session.
    /// Supports granular patterns like `Bash(python3:*)`.
    #[arg(
        long = "disallow-tools",
        env = "RALPH_DISALLOWED_TOOLS",
        value_delimiter = ','
    )]
    pub disallow_tools: Vec<String>,

    /// Tool profile — predefined allow/disallow lists for common agent roles.
    /// Valid profiles: coder, reviewer, coordinator, notion, reader.
    /// Explicit --allow-tools/--disallow-tools merge on top of the profile.
    #[arg(long, env = "RALPH_PROFILE", value_parser = parse_profile)]
    pub profile: Option<claude::Profile>,

    /// Override the broker socket path (passed through to all `room` subcommands).
    /// Useful for connecting to a daemon socket instead of the default per-room socket.
    #[arg(long, env = "ROOM_SOCKET")]
    pub socket: Option<PathBuf>,

    /// Skip all tool restrictions — passes no --allowedTools or --disallowedTools
    /// to claude, giving full unrestricted tool access.
    /// Overrides --profile, --allow-tools, and --disallow-tools when set.
    #[arg(long, env = "RALPH_ALLOW_ALL")]
    pub allow_all: bool,

    /// Send a heartbeat /set_status every N iterations (0 = disabled)
    #[arg(long, default_value_t = 5, env = "RALPH_HEARTBEAT_INTERVAL")]
    pub heartbeat_interval: u32,

    /// Print the prompt that would be sent, then exit
    #[arg(long)]
    pub dry_run: bool,
}

#[cfg(test)]
mod tests {
    use super::*;
    use clap::Parser;
    use std::sync::Mutex;

    /// Mutex to serialize env-var tests (env is process-global state).
    static ENV_LOCK: Mutex<()> = Mutex::new(());

    /// Helper: clear all RALPH_*/ROOM_* env vars to avoid cross-test contamination.
    fn clear_ralph_env() {
        for key in [
            "RALPH_ROOM",
            "RALPH_USERNAME",
            "RALPH_MODEL",
            "RALPH_ISSUE",
            "RALPH_DISALLOWED_TOOLS",
            "RALPH_PROFILE",
            "RALPH_ALLOW_ALL",
            "RALPH_HEARTBEAT_INTERVAL",
            "ROOM_SOCKET",
            "ROOM_TOKEN",
        ] {
            unsafe { std::env::remove_var(key) };
        }
    }

    #[test]
    fn cli_args_take_precedence_over_env() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe {
            std::env::set_var("RALPH_ROOM", "env-room");
            std::env::set_var("RALPH_USERNAME", "env-user");
            std::env::set_var("RALPH_MODEL", "env-model");
            std::env::set_var("RALPH_ISSUE", "99");
        }

        let cli = Cli::try_parse_from([
            "room-ralph",
            "cli-room",
            "cli-user",
            "--model",
            "cli-model",
            "--issue",
            "42",
        ])
        .unwrap();

        assert_eq!(cli.room_id, "cli-room");
        assert_eq!(cli.username, "cli-user");
        assert_eq!(cli.model, "cli-model");
        assert_eq!(cli.issue.as_deref(), Some("42"));

        clear_ralph_env();
    }

    #[test]
    fn env_vars_used_when_args_omitted() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe {
            std::env::set_var("RALPH_ROOM", "env-room");
            std::env::set_var("RALPH_USERNAME", "env-user");
            std::env::set_var("RALPH_MODEL", "haiku");
            std::env::set_var("RALPH_ISSUE", "77");
        }

        let cli = Cli::try_parse_from(["room-ralph"]).unwrap();

        assert_eq!(cli.room_id, "env-room");
        assert_eq!(cli.username, "env-user");
        assert_eq!(cli.model, "haiku");
        assert_eq!(cli.issue.as_deref(), Some("77"));

        clear_ralph_env();
    }

    #[test]
    fn missing_required_without_env_fails() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let result = Cli::try_parse_from(["room-ralph"]);
        assert!(result.is_err(), "should fail without room_id or RALPH_ROOM");

        clear_ralph_env();
    }

    #[test]
    fn partial_env_with_partial_args() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe {
            std::env::set_var("RALPH_USERNAME", "env-user");
        }

        // room_id from CLI, username from env
        let cli = Cli::try_parse_from(["room-ralph", "cli-room"]).unwrap();

        assert_eq!(cli.room_id, "cli-room");
        assert_eq!(cli.username, "env-user");
        assert_eq!(cli.model, "opus"); // default
        assert!(cli.issue.is_none());

        clear_ralph_env();
    }

    #[test]
    fn model_default_used_without_env_or_flag() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();

        assert_eq!(cli.model, "opus");
        assert!(cli.issue.is_none());

        clear_ralph_env();
    }

    #[test]
    fn issue_env_is_optional() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe {
            std::env::set_var("RALPH_ROOM", "r");
            std::env::set_var("RALPH_USERNAME", "u");
        }

        let cli = Cli::try_parse_from(["room-ralph"]).unwrap();
        assert!(
            cli.issue.is_none(),
            "issue should be None when RALPH_ISSUE unset"
        );

        clear_ralph_env();
    }

    #[test]
    fn profile_flag_parses_valid() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli =
            Cli::try_parse_from(["room-ralph", "myroom", "myuser", "--profile", "coder"]).unwrap();

        assert_eq!(cli.profile, Some(claude::Profile::Coder));
        clear_ralph_env();
    }

    #[test]
    fn profile_flag_rejects_invalid() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let result = Cli::try_parse_from(["room-ralph", "myroom", "myuser", "--profile", "hacker"]);
        assert!(result.is_err());
        clear_ralph_env();
    }

    #[test]
    fn profile_defaults_to_none() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();
        assert!(cli.profile.is_none());
        clear_ralph_env();
    }

    #[test]
    fn profile_from_env_var() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe { std::env::set_var("RALPH_PROFILE", "reviewer") };
        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();
        assert_eq!(cli.profile, Some(claude::Profile::Reviewer));
        clear_ralph_env();
    }

    #[test]
    fn profile_flag_overrides_env() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe { std::env::set_var("RALPH_PROFILE", "reader") };
        let cli =
            Cli::try_parse_from(["room-ralph", "myroom", "myuser", "--profile", "notion"]).unwrap();
        assert_eq!(cli.profile, Some(claude::Profile::Notion));
        clear_ralph_env();
    }

    #[test]
    fn socket_flag_sets_path() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from([
            "room-ralph",
            "myroom",
            "myuser",
            "--socket",
            "/tmp/roomd.sock",
        ])
        .unwrap();
        assert_eq!(
            cli.socket,
            Some(PathBuf::from("/tmp/roomd.sock")),
            "socket should be set from --socket flag"
        );
        clear_ralph_env();
    }

    #[test]
    fn socket_defaults_to_none() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();
        assert!(cli.socket.is_none(), "socket should default to None");
        clear_ralph_env();
    }

    #[test]
    fn socket_from_env_var() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe { std::env::set_var("ROOM_SOCKET", "/tmp/daemon.sock") };
        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();
        assert_eq!(
            cli.socket,
            Some(PathBuf::from("/tmp/daemon.sock")),
            "socket should be set from ROOM_SOCKET env var"
        );
        clear_ralph_env();
    }

    #[test]
    fn socket_flag_overrides_env() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe { std::env::set_var("ROOM_SOCKET", "/tmp/env.sock") };
        let cli = Cli::try_parse_from([
            "room-ralph",
            "myroom",
            "myuser",
            "--socket",
            "/tmp/flag.sock",
        ])
        .unwrap();
        assert_eq!(
            cli.socket,
            Some(PathBuf::from("/tmp/flag.sock")),
            "CLI --socket should override ROOM_SOCKET env var"
        );
        clear_ralph_env();
    }

    #[test]
    fn allow_all_flag_defaults_to_false() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();
        assert!(!cli.allow_all, "allow_all should default to false");
        clear_ralph_env();
    }

    #[test]
    fn allow_all_flag_sets_true() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser", "--allow-all"]).unwrap();
        assert!(cli.allow_all, "allow_all should be true when flag is set");
        clear_ralph_env();
    }

    #[test]
    fn allow_all_from_env_var() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe { std::env::set_var("RALPH_ALLOW_ALL", "true") };
        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();
        assert!(
            cli.allow_all,
            "allow_all should be true from RALPH_ALLOW_ALL env var"
        );
        clear_ralph_env();
    }

    #[test]
    fn heartbeat_interval_defaults_to_five() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();
        assert_eq!(cli.heartbeat_interval, 5);
        clear_ralph_env();
    }

    #[test]
    fn heartbeat_interval_from_flag() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from([
            "room-ralph",
            "myroom",
            "myuser",
            "--heartbeat-interval",
            "10",
        ])
        .unwrap();
        assert_eq!(cli.heartbeat_interval, 10);
        clear_ralph_env();
    }

    #[test]
    fn heartbeat_interval_zero_disables() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from([
            "room-ralph",
            "myroom",
            "myuser",
            "--heartbeat-interval",
            "0",
        ])
        .unwrap();
        assert_eq!(cli.heartbeat_interval, 0);
        clear_ralph_env();
    }

    #[test]
    fn heartbeat_interval_from_env() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe { std::env::set_var("RALPH_HEARTBEAT_INTERVAL", "3") };
        let cli = Cli::try_parse_from(["room-ralph", "myroom", "myuser"]).unwrap();
        assert_eq!(cli.heartbeat_interval, 3);
        clear_ralph_env();
    }

    #[test]
    fn heartbeat_interval_flag_overrides_env() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        unsafe { std::env::set_var("RALPH_HEARTBEAT_INTERVAL", "7") };
        let cli = Cli::try_parse_from([
            "room-ralph",
            "myroom",
            "myuser",
            "--heartbeat-interval",
            "2",
        ])
        .unwrap();
        assert_eq!(cli.heartbeat_interval, 2);
        clear_ralph_env();
    }

    #[test]
    fn allow_all_coexists_with_profile() {
        let _lock = ENV_LOCK.lock().unwrap();
        clear_ralph_env();

        let cli = Cli::try_parse_from([
            "room-ralph",
            "myroom",
            "myuser",
            "--allow-all",
            "--profile",
            "reviewer",
        ])
        .unwrap();
        assert!(cli.allow_all);
        assert_eq!(cli.profile, Some(claude::Profile::Reviewer));
        clear_ralph_env();
    }
}
