mod auth;
mod daemon;
mod protocol;
mod session;

use std::path::PathBuf;

use tokio::signal::unix::{signal, SignalKind};
use tokio::sync::broadcast;

use auth::AuthToken;
use daemon::Daemon;

const VERSION: &str = "0.1.0";

// ---------------------------------------------------------------------------
// CLI argument parsing — no clap needed for 3 flags.
// ---------------------------------------------------------------------------

struct Cli {
    socket_dir: Option<PathBuf>,
    default_shell: Option<String>,
    verbose: bool,
}

impl Cli {
    fn parse() -> Result<Self, String> {
        let mut args = std::env::args().skip(1).peekable();
        let mut socket_dir = None;
        let mut default_shell = None;
        let mut verbose = false;

        while let Some(arg) = args.next() {
            match arg.as_str() {
                "--socket-dir" => {
                    socket_dir = Some(PathBuf::from(
                        args.next().ok_or("--socket-dir requires a value")?,
                    ));
                }
                "--shell" => {
                    default_shell = Some(
                        args.next().ok_or("--shell requires a value")?,
                    );
                }
                "--verbose" | "-v" => {
                    verbose = true;
                }
                "--help" | "-h" => {
                    print_usage();
                    std::process::exit(0);
                }
                other => {
                    return Err(format!("unknown argument: {other}"));
                }
            }
        }

        Ok(Self {
            socket_dir,
            default_shell,
            verbose,
        })
    }
}

fn print_usage() {
    eprintln!(
        "USAGE: agor-ptyd [OPTIONS]\n\
         \n\
         OPTIONS:\n\
           --socket-dir <PATH>   Socket directory\n\
                                 (default: /run/user/$UID/agor or ~/.local/share/agor/run)\n\
           --shell <PATH>        Default shell (default: $SHELL or /bin/bash)\n\
           --verbose             Enable debug logging\n\
           --help                Show this message"
    );
}

// ---------------------------------------------------------------------------
// Socket directory resolution
// ---------------------------------------------------------------------------

fn resolve_socket_dir(override_path: Option<PathBuf>) -> Result<PathBuf, String> {
    if let Some(p) = override_path {
        return Ok(p);
    }

    // Prefer XDG runtime dir.
    if let Ok(uid_str) = std::env::var("UID").or_else(|_| {
        // UID is not always exported; fall back to getuid().
        Ok::<_, std::env::VarError>(unsafe { libc_getuid() }.to_string())
    }) {
        let xdg = PathBuf::from(format!("/run/user/{uid_str}/agor"));
        if xdg.parent().map(|p| p.exists()).unwrap_or(false) {
            return Ok(xdg);
        }
    }

    // Fallback: ~/.local/share/agor/run
    let home = std::env::var("HOME").map_err(|_| "HOME not set".to_string())?;
    Ok(PathBuf::from(home).join(".local/share/agor/run"))
}

#[cfg(target_os = "linux")]
unsafe fn libc_getuid() -> u32 {
    // Safety: getuid() is always safe.
    extern "C" {
        fn getuid() -> u32;
    }
    getuid()
}

#[cfg(not(target_os = "linux"))]
unsafe fn libc_getuid() -> u32 {
    0
}

// ---------------------------------------------------------------------------
// Default shell resolution
// ---------------------------------------------------------------------------

fn resolve_shell(override_shell: Option<String>) -> String {
    override_shell
        .or_else(|| std::env::var("SHELL").ok())
        .filter(|s| !s.is_empty())
        .unwrap_or_else(|| "/bin/bash".into())
}

// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------

#[tokio::main]
async fn main() {
    let cli = match Cli::parse() {
        Ok(c) => c,
        Err(e) => {
            eprintln!("error: {e}");
            print_usage();
            std::process::exit(1);
        }
    };

    // Initialise logging.
    let log_level = if cli.verbose { "debug" } else { "info" };
    env_logger::Builder::from_env(
        env_logger::Env::default().default_filter_or(log_level),
    )
    .init();

    log::info!("agor-ptyd v{VERSION} starting");

    let socket_dir = match resolve_socket_dir(cli.socket_dir) {
        Ok(d) => d,
        Err(e) => {
            log::error!("cannot resolve socket directory: {e}");
            std::process::exit(1);
        }
    };

    // Ensure the directory exists.
    if let Err(e) = std::fs::create_dir_all(&socket_dir) {
        log::error!("cannot create socket directory {socket_dir:?}: {e}");
        std::process::exit(1);
    }

    let socket_path = socket_dir.join("ptyd.sock");
    let shell = resolve_shell(cli.default_shell);

    // Generate and persist auth token.
    let token = match AuthToken::generate_and_persist(&socket_dir) {
        Ok(t) => t,
        Err(e) => {
            log::error!("token generation failed: {e}");
            std::process::exit(1);
        }
    };

    // Shutdown broadcast channel — one sender, N receivers.
    let (shutdown_tx, shutdown_rx) = broadcast::channel::<()>(1);

    // Signal handlers for SIGTERM and SIGINT.
    let shutdown_tx_sigterm = shutdown_tx.clone();
    let shutdown_tx_sigint = shutdown_tx.clone();

    tokio::spawn(async move {
        let mut sigterm = signal(SignalKind::terminate()).expect("SIGTERM handler");
        sigterm.recv().await;
        log::info!("SIGTERM received");
        let _ = shutdown_tx_sigterm.send(());
    });

    tokio::spawn(async move {
        let mut sigint = signal(SignalKind::interrupt()).expect("SIGINT handler");
        sigint.recv().await;
        log::info!("SIGINT received");
        let _ = shutdown_tx_sigint.send(());
    });

    let daemon = Daemon::new(socket_path, token, shell);
    if let Err(e) = daemon.run(shutdown_rx).await {
        log::error!("daemon exited with error: {e}");
        std::process::exit(1);
    }

    log::info!("agor-ptyd shut down cleanly");
}
