//! ZeroGravity — Rust edition v3 (stealth hardened).
//!
//! Single-binary replacement for server.py. BoringSSL TLS impersonation,
//! byte-exact protobuf encoding, Chrome header fingerprinting, cascade
//! session management, warmup + heartbeat lifecycle mimicry.

mod api;
mod backend;
mod constants;
mod mitm;
mod platform;
mod proto;
mod quota;
mod session;
mod standalone;
mod trace;
mod warmup;

use api::AppState;
use backend::Backend;
use clap::Parser;
use session::SessionManager;
use std::sync::Arc;
use tracing::{info, warn};

use mitm::store::MitmStore;

#[derive(Parser)]
#[command(name = "zerogravity", about = "ZeroGravity — stealth LLM proxy")]
struct Cli {
    /// Port to listen on
    #[arg(long, default_value_t = 8741)]
    port: u16,

    /// Enable info-level logging (-v)
    #[arg(short, long)]
    verbose: bool,

    /// Enable debug-level logging (-d)
    #[arg(short, long)]
    debug: bool,

    /// Disable the MITM proxy (no API interception)
    #[arg(long)]
    no_mitm: bool,

    /// MITM proxy port (default: 8742, matches wrapper script)
    #[arg(long, default_value_t = 8742)]
    mitm_port: u16,

    /// Disable standalone LS — attach to the real running LS instead
    #[arg(long)]
    no_standalone: bool,

    /// Headless mode (DEFAULT) — no running Antigravity app required.
    /// Generates its own CSRF, disables extension server, uses HTTPS_PROXY for MITM.
    /// Use --no-headless or --classic to attach to a running Antigravity instance instead.
    #[arg(long, default_value_t = true)]
    headless: bool,

    /// Classic mode — requires a running Antigravity app. Alias for --no-headless.
    #[arg(long, conflicts_with = "headless")]
    classic: bool,

    /// Disable per-call debug traces (on by default, writes JSON to ~/.config/zerogravity/traces/)
    #[arg(long)]
    no_trace: bool,
}

#[tokio::main]
async fn main() {
    // Ignore SIGPIPE — prevents instant death when piped through tee/grep
    #[cfg(unix)]
    {
        use tokio::signal::unix::{signal, SignalKind};
        let mut sigpipe = signal(SignalKind::pipe()).expect("failed to install SIGPIPE handler");
        tokio::spawn(async move {
            loop {
                sigpipe.recv().await;
                // Silently ignore SIGPIPE
            }
        });
    }

    // Install rustls CryptoProvider early — prevents panic under concurrent load
    let _ = rustls::crypto::ring::default_provider().install_default();

    let cli = Cli::parse();
    let headless = cli.headless && !cli.classic;

    // Flag > env var > default (warn)
    let log_level = if cli.debug {
        "debug"
    } else if cli.verbose {
        "info"
    } else {
        // Fall back to RUST_LOG env, or warn-only
        ""
    };

    let filter = if log_level.is_empty() {
        tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "warn".into())
    } else {
        tracing_subscriber::EnvFilter::new(log_level)
    };

    tracing_subscriber::fmt().with_env_filter(filter).init();

    // ── Step 1: Bind main port (auto-kill stale process if needed) ─────────────
    let addr = format!("127.0.0.1:{}", cli.port);
    let listener = match tokio::net::TcpListener::bind(&addr).await {
        Ok(l) => l,
        Err(_) => {
            // Port in use — try to kill whatever's holding it
            eprintln!("  Port {} in use, killing stale process...", cli.port);
            let _ = std::process::Command::new("sh")
                .args([
                    "-c",
                    &format!("kill $(lsof -ti:{}) 2>/dev/null; sleep 0.3", cli.port),
                ])
                .status();
            // Also kill any leftover standalone LS processes
            let _ = std::process::Command::new("pkill")
                .args(["-f", "language_server.*antigravity-standalone"])
                .status();
            // Retry once
            match tokio::net::TcpListener::bind(&addr).await {
                Ok(l) => l,
                Err(e) => {
                    eprintln!("Fatal: cannot bind to {addr} even after kill: {e}");
                    std::process::exit(1);
                }
            }
        }
    };

    // ── Step 2: MITM proxy (must be running BEFORE LS spawn in headless) ──────
    // In headless mode, the LS is configured with HTTPS_PROXY pointing at our
    // MITM proxy, so it must be listening before the LS tries to connect.
    let mitm_store = MitmStore::new();
    let (mitm_port_actual, mitm_handle) = if !cli.no_mitm {
        let data_dir = dirs_data_dir();
        match mitm::ca::MitmCa::load_or_generate(&data_dir) {
            Ok(ca) => {
                let ca = Arc::new(ca);
                let ca_pem = ca.ca_pem_path.display().to_string();
                let config = mitm::proxy::MitmConfig {
                    port: cli.mitm_port,
                    modify_requests: true,
                };
                match mitm::proxy::run(ca, mitm_store.clone(), config).await {
                    Ok((port, handle)) => {
                        info!(port, ca = %ca_pem, "MITM proxy started");
                        // Write actual port to file for wrapper script discovery
                        let port_file = data_dir.join("mitm-port");
                        if let Err(e) = std::fs::write(&port_file, port.to_string()) {
                            warn!("Failed to write MITM port file: {e}");
                        }
                        (Some((port, ca_pem)), Some(handle))
                    }
                    Err(e) => {
                        warn!("MITM proxy failed to start: {e}");
                        (None, None)
                    }
                }
            }
            Err(e) => {
                warn!("MITM CA generation failed: {e}");
                (None, None)
            }
        }
    } else {
        info!("MITM proxy disabled (--no-mitm)");
        (None, None)
    };

    // ── Step 3: Backend discovery (or standalone LS spawn) ─────────────────────
    // --headless implies standalone mode
    let standalone_ls = if headless || !cli.no_standalone {
        // Get LS config: headless generates its own, normal steals from running LS
        let main_config = if headless {
            info!("Headless mode: generating self-contained config");
            standalone::generate_standalone_config()
        } else {
            match standalone::discover_main_ls_config() {
                Ok(c) => c,
                Err(e) => {
                    eprintln!("Fatal: {e}");
                    eprintln!(
                        "Hint: start Antigravity first, or remove --classic to use headless mode"
                    );
                    std::process::exit(1);
                }
            }
        };
        // Build MITM config using the actual MITM port (not just the CLI default)
        let mitm_cfg = if let Some((mitm_port, _)) = &mitm_port_actual {
            let ca_path = dirs_data_dir()
                .join("mitm-ca.pem")
                .to_string_lossy()
                .to_string();
            Some(standalone::StandaloneMitmConfig {
                proxy_addr: format!("http://127.0.0.1:{}", mitm_port),
                ca_cert_path: ca_path,
            })
        } else {
            None
        };

        let mut ls =
            match standalone::StandaloneLS::spawn(&main_config, mitm_cfg.as_ref(), headless) {
                Ok(ls) => ls,
                Err(e) => {
                    eprintln!("Fatal: failed to spawn standalone LS: {e}");
                    std::process::exit(1);
                }
            };
        // Wait for it to be ready
        let rt_ls_port = ls.port;
        let rt_ls_csrf = ls.csrf.clone();
        tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current().block_on(async {
                if let Err(e) = ls.wait_ready(10).await {
                    eprintln!("Fatal: {e}");
                    std::process::exit(1);
                }
            });
        });
        info!(port = rt_ls_port, "Standalone LS ready");
        Some((ls, rt_ls_port, rt_ls_csrf))
    } else {
        None
    };

    let backend = Arc::new(if let Some((_, port, ref csrf)) = standalone_ls {
        // Build backend pointing at standalone LS
        let oauth = std::env::var("ZEROGRAVITY_TOKEN")
            .ok()
            .filter(|s| !s.is_empty())
            .or_else(|| {
                let path = crate::constants::token_file_path();
                std::fs::read_to_string(&path)
                    .ok()
                    .map(|s| s.trim().to_string())
                    .filter(|s| !s.is_empty())
            })
            .unwrap_or_default();
        match Backend::new_with_config(port, csrf.clone(), oauth) {
            Ok(b) => b,
            Err(e) => {
                eprintln!("Fatal: {e}");
                std::process::exit(1);
            }
        }
    } else {
        // Normal mode: discover existing LS
        match Backend::new() {
            Ok(b) => b,
            Err(e) => {
                eprintln!("Fatal: {e}");
                std::process::exit(1);
            }
        }
    });

    let (pid, https_port, csrf, token) = backend.info().await;

    // ── Step 4: Warmup + heartbeat ────────────────────────────────────────────
    warmup::warmup_sequence(&backend, headless).await;
    let heartbeat_handle = warmup::start_heartbeat(Arc::clone(&backend));

    // ── Step 4b: Quota monitor ────────────────────────────────────────────────
    let quota_store = quota::QuotaStore::new();
    quota_store.clone().start_polling(Arc::clone(&backend));
    info!("Quota monitor started (polling every 60s)");

    // ── Step 4c: Debug trace collector ────────────────────────────────────────
    let trace_enabled = !cli.no_trace;
    let trace_collector = trace::TraceCollector::new(trace_enabled);
    if trace_enabled {
        trace_collector.cleanup_old_traces(7);
        info!("Debug tracing enabled → ~/.config/zerogravity/traces/");
    }

    let state = Arc::new(AppState {
        backend,
        sessions: SessionManager::new(),
        mitm_store,
        quota_store,
        mitm_enabled: mitm_handle.is_some(),
        trace: trace_collector,
    });

    // Periodic backend refresh — keeps LS connection details fresh
    // (skip in standalone mode — the port is fixed and discover() would overwrite it)
    let is_standalone = !cli.no_standalone;
    let refresh_backend = Arc::clone(&state.backend);
    let refresh_handle = tokio::spawn(async move {
        if is_standalone {
            // In standalone mode, the backend config is fixed — no refresh needed
            std::future::pending::<()>().await;
            return;
        }
        loop {
            tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
            if let Err(e) = refresh_backend.refresh().await {
                warn!("Periodic refresh failed: {e}");
            }
        }
    });

    // ── Step 5: Start serving ─────────────────────────────────────────────────
    let app = api::router(state.clone());

    print_banner(
        cli.port,
        &pid,
        &https_port,
        &csrf,
        &token,
        &mitm_port_actual,
        is_standalone,
    );
    info!("Listening on http://{addr}");

    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .expect("server error");

    // ── Cleanup: abort all background tasks ───────────────────────────────────
    heartbeat_handle.abort();
    refresh_handle.abort();
    if let Some(h) = mitm_handle {
        h.abort();
    }
    // Kill standalone LS if we spawned one
    if let Some((mut ls, _, _)) = standalone_ls {
        ls.kill();
    }
    // Remove stale MITM port file
    let _ = std::fs::remove_file(dirs_data_dir().join("mitm-port"));
    eprintln!("  \x1b[1;32m✓ Server shutdown complete\x1b[0m\n");
    info!("Server shutdown complete");
}

/// Wait for SIGINT (Ctrl+C) or SIGTERM for graceful shutdown.
async fn shutdown_signal() {
    let ctrl_c = async {
        tokio::signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
            .expect("failed to install SIGTERM handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {
            eprintln!("\n  \x1b[1;33m⚡ Shutting down gracefully...\x1b[0m");
            info!("Received SIGINT, shutting down...");
        },
        _ = terminate => {
            eprintln!("\n  \x1b[1;33m⚡ Received SIGTERM, shutting down...\x1b[0m");
            info!("Received SIGTERM, shutting down...");
        },
    }
}

fn print_banner(
    port: u16,
    pid: &str,
    https_port: &str,
    csrf: &str,
    token: &str,
    mitm: &Option<(u16, String)>,
    is_standalone: bool,
) {
    let chrome_major = &*constants::CHROME_MAJOR;
    let ver = crate::constants::antigravity_version();

    println!();
    println!("  \x1b[1;35m>> zerogravity\x1b[0m \x1b[2mv{ver}\x1b[0m");
    println!("  \x1b[2m────────────────────────────────────────────────\x1b[0m");
    println!();
    println!("  \x1b[1mcore\x1b[0m");
    println!("  \x1b[36m  tls\x1b[0m        BoringSSL (Chrome {chrome_major})");
    println!("  \x1b[36m  listen\x1b[0m     http://127.0.0.1:{port}");
    println!("  \x1b[36m  ls pid\x1b[0m     {pid}");
    println!("  \x1b[36m  https\x1b[0m      :{https_port}");
    println!("  \x1b[36m  csrf\x1b[0m       {csrf}");
    println!("  \x1b[36m  oauth\x1b[0m      {token}");
    println!();

    // MITM section
    if let Some((mitm_port, ca_path)) = mitm {
        println!("  \x1b[1mmitm\x1b[0m");
        println!("  \x1b[36m  proxy\x1b[0m      127.0.0.1:{mitm_port}");
        println!("  \x1b[36m  ca cert\x1b[0m    {ca_path}");

        // Check if wrapper is installed
        if is_standalone {
            println!("  \x1b[36m  wrapper\x1b[0m    \x1b[32miptables (standalone)\x1b[0m");
        } else if check_wrapper_installed() {
            println!("  \x1b[36m  wrapper\x1b[0m    \x1b[32minstalled\x1b[0m");
        } else {
            println!("  \x1b[36m  wrapper\x1b[0m    \x1b[33mnot installed\x1b[0m");
        }
        println!();
    } else {
        println!("  \x1b[1mmitm\x1b[0m        \x1b[33mdisabled\x1b[0m");
        println!();
    }

    // Routes
    println!("  \x1b[1mroutes\x1b[0m");
    println!("  \x1b[33m  POST\x1b[0m  /v1/responses");
    println!("  \x1b[33m  POST\x1b[0m  /v1/chat/completions");
    println!("  \x1b[33m  POST\x1b[0m  /v1beta/models/:model:generateContent");
    println!("  \x1b[33m  POST\x1b[0m  /v1beta/models/:model:streamGenerateContent");
    println!("  \x1b[32m  GET \x1b[0m  /v1/models");
    println!("  \x1b[32m  GET \x1b[0m  /v1/sessions");
    println!("  \x1b[31m  DEL \x1b[0m  /v1/sessions/:id");
    println!("  \x1b[33m  POST\x1b[0m  /v1/token");
    println!("  \x1b[32m  GET \x1b[0m  /v1/usage");
    println!("  \x1b[32m  GET \x1b[0m  /v1/quota");
    println!("  \x1b[32m  GET \x1b[0m  /health");
    println!();

    // Status line
    let mitm_tag = if mitm.is_some() {
        "\x1b[32mmitm\x1b[0m"
    } else {
        "\x1b[31mmitm\x1b[0m"
    };
    println!("  \x1b[2mstealth:\x1b[0m \x1b[32mwarmup\x1b[0m \x1b[32mheartbeat\x1b[0m \x1b[32mjitter\x1b[0m {mitm_tag}");
    println!();

    // Setup hints
    if let Some((mitm_port, ca_path)) = mitm {
        if is_standalone {
            // Standalone mode uses iptables UID isolation — no wrapper needed
        } else if !check_wrapper_installed() {
            println!("  \x1b[1;33m[!]\x1b[0m mitm wrapper not installed");
            println!("      \x1b[2mrun:\x1b[0m  ./scripts/mitm-wrapper.sh install");
            println!("      \x1b[2mor:\x1b[0m   HTTPS_PROXY=http://127.0.0.1:{mitm_port}");
            println!("             NODE_EXTRA_CA_CERTS={ca_path}");
            println!();
        }
    }

    if token == "NOT SET" {
        println!("  \x1b[1;33m[!]\x1b[0m no oauth token");
        println!("      export ZEROGRAVITY_TOKEN=ya29.xxx");
        println!(
            "      curl -X POST http://127.0.0.1:{port}/v1/token -d '{{\"token\":\"ya29.xxx\"}}'"
        );
        println!("      echo 'ya29.xxx' > ~/.config/zerogravity/token");
        println!();
    }
}

/// Check if the MITM wrapper is installed by looking for the .real backup file
/// next to the LS binary. Uses /proc to find the real LS path dynamically.
fn check_wrapper_installed() -> bool {
    // Find the LS binary path from known PID or by scanning /proc
    if let Some(ls_path) = find_ls_binary_path() {
        let real_path = format!("{ls_path}.real");
        return std::path::Path::new(&real_path).exists();
    }
    false
}

/// Find the LS binary path by reading /proc/<pid>/exe for known language server processes.
#[cfg(target_os = "linux")]
fn find_ls_binary_path() -> Option<String> {
    // Try all running processes, look for ones that look like the LS
    let proc = std::path::Path::new("/proc");
    if !proc.exists() {
        return None;
    }

    if let Ok(entries) = std::fs::read_dir(proc) {
        for entry in entries.flatten() {
            let name = entry.file_name();
            let name_str = name.to_string_lossy();
            // Only look at numeric dirs (PIDs)
            if !name_str.chars().all(|c| c.is_ascii_digit()) {
                continue;
            }
            let exe_link = entry.path().join("exe");
            if let Ok(target) = std::fs::read_link(&exe_link) {
                let target_str = target.to_string_lossy();
                // Strip " (deleted)" suffix from unlinked binaries
                let target_clean = target_str.trim_end_matches(" (deleted)");
                // Match any binary that looks like the Antigravity LS
                if target_clean.contains("language_server_linux")
                    || target_clean.contains("language_server_darwin")
                    || target_clean.contains("language_server_windows")
                    || target_clean.contains("antigravity-language-server")
                {
                    // Strip .real suffix — if the wrapper exec'd the backup, we want the base name
                    let path = target_clean.trim_end_matches(".real");
                    return Some(path.to_string());
                }
            }
        }
    }
    None
}

#[cfg(not(target_os = "linux"))]
fn find_ls_binary_path() -> Option<String> {
    None
}

/// Get the data directory for storing MITM CA cert/key.
fn dirs_data_dir() -> std::path::PathBuf {
    crate::platform::Platform::detect().config_dir
}
