use clap::{CommandFactory, FromArgMatches};
use night_fury_daemon_core::cli::make_req;
use night_fury_daemon_core::client;
use serde_json::json;
use tail_fin_daemon::cli::{expand_home, Cli, Commands, DaemonCmd, SessionCmd};
use tail_fin_daemon::spawn::ensure_daemon_safe;

fn register_all_sites() {
    tail_fin_daemon::driver::register_sites(tail_fin_daemon::registry::all_site_entries().to_vec());
}

#[tokio::main]
async fn main() -> std::process::ExitCode {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_env("TFD_LOG")
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .with_writer(std::io::stderr)
        .init();

    // The binary owns the process exit code; `run` returns it (FAILURE on a
    // not-ok command response) or an `Err` for hard failures, which we print
    // once to stderr — no `process::exit` in the library (workspace convention).
    match run().await {
        Ok(code) => code,
        Err(e) => {
            eprintln!("Error: {e:#}");
            std::process::ExitCode::FAILURE
        }
    }
}

async fn run() -> anyhow::Result<std::process::ExitCode> {
    use std::process::ExitCode;

    // Hybrid clap: derive Cli for fixed commands (daemon/session), augmented
    // with generated subcommands for descriptor-backed sites (reddit, …).
    // Parse once.
    let mut command = Cli::command();
    for sub in tail_fin_daemon::dispatch::generated::generated_site_commands() {
        command = command.subcommand(sub);
    }
    let matches = command.get_matches();

    // Global args may be given at any level (top, after <site>, or after the
    // command), so scan the subcommand chain via `global_arg` (fix F1) rather
    // than reading only the top-level matches.
    use tail_fin_daemon::dispatch::generated::global_arg;
    let socket = expand_home(global_arg(&matches, "socket").unwrap_or("~/.tail-fin/daemon.sock"));
    let host = global_arg(&matches, "host")
        .unwrap_or(tail_fin_daemon::cli::DEFAULT_CDP_HOST)
        .to_string();
    let session = global_arg(&matches, "session").map(str::to_string);

    // Ensure the parent directory for the socket exists.
    if let Some(parent) = std::path::Path::new(&socket).parent() {
        let _ = std::fs::create_dir_all(parent);
    }

    // Route generated sites (not in the derive `Commands` enum) before
    // reconstructing the derive Cli, which would reject them.
    if let Some((name, site_matches)) = matches.subcommand() {
        if tail_fin_daemon::dispatch::generated::is_generated_site(name) {
            return tail_fin_daemon::dispatch::generated::run(
                name,
                site_matches,
                &socket,
                &host,
                session,
            )
            .await;
        }
    }

    let cli = Cli::from_arg_matches(&matches).map_err(|e| anyhow::anyhow!("{e}"))?;

    match cli.command {
        Commands::Daemon { cmd } => match cmd {
            DaemonCmd::Start {
                idle_timeout,
                daemon_idle,
                max_sessions,
                detach,
            } => {
                if detach {
                    tail_fin_daemon::spawn::start_detached_and_wait(
                        &socket,
                        idle_timeout,
                        daemon_idle,
                        max_sessions,
                    )
                    .await?;
                } else {
                    register_all_sites();
                    tail_fin_daemon::server::serve(
                        &socket,
                        idle_timeout,
                        daemon_idle,
                        max_sessions,
                    )
                    .await?;
                }
            }
            DaemonCmd::Stop => {
                let req = make_req("daemon.shutdown", None, json!({}));
                let resp = client::send(&socket, &req).await?;
                if !tail_fin_daemon::dispatch::print_and_ok(&resp) {
                    return Ok(ExitCode::FAILURE);
                }
            }
            DaemonCmd::Status => {
                ensure_daemon_safe(&socket).await?;
                let req = make_req("daemon.status", None, json!({}));
                let resp = client::send(&socket, &req).await?;
                if !tail_fin_daemon::dispatch::print_and_ok(&resp) {
                    return Ok(ExitCode::FAILURE);
                }
            }
        },

        Commands::Session { cmd } => match cmd {
            SessionCmd::Acquire { site, host } => {
                ensure_daemon_safe(&socket).await?;
                let req = make_req(
                    "session.acquire",
                    None,
                    json!({"site": site, "host": host, "mode": "acquired"}),
                );
                let resp = client::send(&socket, &req).await?;
                if !tail_fin_daemon::dispatch::print_and_ok(&resp) {
                    return Ok(ExitCode::FAILURE);
                }
            }
            SessionCmd::Release { session_id } => {
                ensure_daemon_safe(&socket).await?;
                let req = make_req("session.release", Some(&session_id), json!({}));
                let resp = client::send(&socket, &req).await?;
                if !tail_fin_daemon::dispatch::print_and_ok(&resp) {
                    return Ok(ExitCode::FAILURE);
                }
            }
            SessionCmd::List => {
                ensure_daemon_safe(&socket).await?;
                let req = make_req("session.list", None, json!({}));
                let resp = client::send(&socket, &req).await?;
                if !tail_fin_daemon::dispatch::print_and_ok(&resp) {
                    return Ok(ExitCode::FAILURE);
                }
            }
            SessionCmd::Destroy { session_id } => {
                ensure_daemon_safe(&socket).await?;
                let req = make_req("session.destroy", Some(&session_id), json!({}));
                let resp = client::send(&socket, &req).await?;
                if !tail_fin_daemon::dispatch::print_and_ok(&resp) {
                    return Ok(ExitCode::FAILURE);
                }
            }
        },

        Commands::Start {
            idle_timeout,
            daemon_idle,
            max_sessions,
            detach: _,
        } => {
            register_all_sites();
            tail_fin_daemon::server::serve(&socket, idle_timeout, daemon_idle, max_sessions)
                .await?;
        }
    }

    Ok(ExitCode::SUCCESS)
}
