use std::process::ExitCode;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use clap::{CommandFactory, FromArgMatches};
use room_ralph::{loop_runner, room, Cli};

fn check_dependencies() -> Result<(), String> {
    let mut missing = Vec::new();
    for cmd in &["claude", "room"] {
        if std::process::Command::new(cmd)
            .arg("--version")
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status()
            .is_err()
        {
            missing.push(*cmd);
        }
    }
    if missing.is_empty() {
        Ok(())
    } else {
        Err(format!("missing dependencies: {}", missing.join(", ")))
    }
}

fn launch_tmux(cli: &Cli) -> Result<(), String> {
    let session_name = format!("ralph-{}", cli.username);

    let exists = std::process::Command::new("tmux")
        .args(["has-session", "-t", &session_name])
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()
        .map(|s| s.success())
        .unwrap_or(false);

    if exists {
        tracing::info!("tmux session {} already exists — attaching", session_name);
        let status = std::process::Command::new("tmux")
            .args(["attach-session", "-t", &session_name])
            .status()
            .map_err(|e| format!("tmux attach failed: {e}"))?;
        std::process::exit(status.code().unwrap_or(1));
    }

    let exe = std::env::current_exe().map_err(|e| format!("cannot find own path: {e}"))?;
    let args = build_tmux_args(cli);
    let cmd_str = format!("{} {}", exe.display(), args.join(" "));
    std::process::Command::new("tmux")
        .args(["new-session", "-d", "-s", &session_name, &cmd_str])
        .status()
        .map_err(|e| format!("tmux new-session failed: {e}"))?;

    tracing::info!("started tmux session: {}", session_name);
    tracing::info!("attach with: tmux attach -t {}", session_name);
    Ok(())
}

/// Build the CLI argument list for re-launching ralph inside a tmux session.
///
/// Reproduces every non-default CLI flag so the detached session runs with
/// the same configuration as the parent invocation.
fn build_tmux_args(cli: &Cli) -> Vec<String> {
    let mut args = vec![
        cli.room_id.clone(),
        cli.username.clone(),
        "--model".into(),
        cli.model.clone(),
        "--max-iter".into(),
        cli.max_iter.to_string(),
        "--cooldown".into(),
        cli.cooldown.to_string(),
    ];
    if let Some(issue) = &cli.issue {
        args.push("--issue".into());
        args.push(issue.clone());
    }
    if let Some(prompt) = &cli.prompt {
        args.push("--prompt".into());
        args.push(prompt.display().to_string());
    }
    if let Some(personality) = &cli.personality {
        args.push("--personality".into());
        args.push(personality.clone());
    }
    for d in &cli.add_dirs {
        args.push("--add-dir".into());
        args.push(d.display().to_string());
    }
    if let Some(profile) = &cli.profile {
        args.push("--profile".into());
        args.push(profile.to_string());
    }
    if let Some(socket) = &cli.socket {
        args.push("--socket".into());
        args.push(socket.display().to_string());
    }
    if cli.allow_all {
        args.push("--allow-all".into());
    }
    if cli.heartbeat_interval != 5 {
        args.push("--heartbeat-interval".into());
        args.push(cli.heartbeat_interval.to_string());
    }
    args
}

#[tokio::main]
async fn main() -> ExitCode {
    let mut cli = Cli::from_arg_matches(
        &Cli::command()
            .disable_version_flag(true)
            .arg(
                clap::Arg::new("version")
                    .short('v')
                    .short_alias('V')
                    .long("version")
                    .action(clap::ArgAction::Version)
                    .help("Print version"),
            )
            .get_matches(),
    )
    .expect("failed to parse CLI arguments");

    // Set up logging — file + stderr
    let log_file = room::log_file_path(&cli.username);
    let file_appender = tracing_subscriber::fmt::layer()
        .with_ansi(false)
        .with_target(false)
        .with_writer(move || {
            std::fs::OpenOptions::new()
                .create(true)
                .append(true)
                .open(&log_file)
                .unwrap_or_else(|_| std::fs::File::create("/dev/null").unwrap())
        });
    let stderr_layer = tracing_subscriber::fmt::layer().with_target(false);

    use tracing_subscriber::layer::SubscriberExt;
    use tracing_subscriber::util::SubscriberInitExt;
    tracing_subscriber::registry()
        .with(stderr_layer)
        .with(file_appender)
        .init();

    if cli.list_personalities {
        print!("{}", room_ralph::personalities::format_list());
        return ExitCode::SUCCESS;
    }

    if let Err(e) = check_dependencies() {
        tracing::error!("{}", e);
        return ExitCode::FAILURE;
    }

    if cli.tmux {
        match launch_tmux(&cli) {
            Ok(()) => return ExitCode::SUCCESS,
            Err(e) => {
                tracing::error!("{}", e);
                return ExitCode::FAILURE;
            }
        }
    }

    // Signal handling
    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();
    tokio::spawn(async move {
        tokio::signal::ctrl_c().await.ok();
        tracing::info!("caught SIGINT, shutting down");
        r.store(false, Ordering::SeqCst);
    });

    #[cfg(unix)]
    {
        let r = running.clone();
        tokio::spawn(async move {
            let mut sig = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
                .expect("failed to register SIGTERM handler");
            sig.recv().await;
            tracing::info!("caught SIGTERM, shutting down");
            r.store(false, Ordering::SeqCst);
        });
    }

    tracing::info!(
        "room-ralph starting: room={} user={} model={} issue={} max_iter={} allow_all={}",
        cli.room_id,
        cli.username,
        cli.model,
        cli.issue.as_deref().unwrap_or("none"),
        cli.max_iter,
        cli.allow_all,
    );

    let socket_str = cli.socket.as_ref().map(|p| p.display().to_string());
    let socket_ref = socket_str.as_deref();
    let join_result = match room::join_room(&cli.room_id, &cli.username, socket_ref) {
        Ok(r) => r,
        Err(e) => {
            tracing::error!("failed to join room: {}", e);
            return ExitCode::FAILURE;
        }
    };
    let token = join_result.token;

    // Update username if we joined with a suffixed variant
    if join_result.username != cli.username {
        tracing::info!(
            "using username '{}' (requested '{}')",
            join_result.username,
            cli.username
        );
        cli.username = join_result.username;
    }

    // Subscribe to the room so poll/watch deliver messages
    if let Err(e) = room::subscribe_room(&cli.room_id, &token, socket_ref) {
        tracing::warn!("failed to subscribe to room: {}", e);
    }

    let announce = {
        let mut parts = vec![format!(
            "online (room-ralph, model={}, iter limit={}",
            cli.model, cli.max_iter
        )];
        if let Some(p) = &cli.personality {
            parts.push(format!(", personality={p}"));
        }
        if cli.allow_all {
            parts.push(", allow-all".to_string());
        }
        parts.push(")".to_string());
        parts.concat()
    };
    room::send_message(&cli.room_id, &token, &announce, socket_ref).ok();

    match loop_runner::run_loop(&cli, token, &running).await {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => {
            tracing::error!("loop failed: {}", e);
            ExitCode::FAILURE
        }
    }
}
