//! Standalone Language Server — spawn and lifecycle management.
//!
//! Launches an isolated LS instance as a child process that the proxy fully owns.
//! In **headless** mode, the LS runs completely independently — no running
//! Antigravity app required. Extension server is disabled (`port=0`), CSRF is
//! self-generated, and MITM uses `HTTPS_PROXY` instead of iptables.

mod discovery;
mod spawn;
mod stub;

use crate::platform::{self, Platform};
use std::process::Command;
use tracing::info;
use uuid::Uuid;

// Re-export public API
pub use spawn::StandaloneLS;

/// Source for the DNS redirect preload library (compiled at runtime, Linux only).
const DNS_REDIRECT_C_SOURCE: &str = include_str!("../mitm/dns_redirect.c");

/// Get platform-resolved paths. Convenience accessor.
pub(crate) fn paths() -> Platform {
    Platform::detect()
}

/// Config needed to bootstrap the standalone LS.
///
/// In normal mode, discovered from the running main LS.
/// In headless mode, generated entirely by the proxy.
pub struct MainLSConfig {
    pub extension_server_port: String,
    pub csrf: String,
}

/// Optional MITM proxy config for the standalone LS.
pub struct StandaloneMitmConfig {
    pub proxy_addr: String,   // Full URL with scheme, e.g. "http://127.0.0.1:8742"
    pub ca_cert_path: String, // path to MITM CA .pem
}

/// Generate a fully self-contained config for headless mode.
///
/// No running Antigravity instance needed — extension server is disabled
/// and CSRF is a random UUID.
pub fn generate_standalone_config() -> MainLSConfig {
    let csrf = Uuid::new_v4().to_string();
    info!(
        csrf_len = csrf.len(),
        "Generated standalone config (headless)"
    );
    MainLSConfig {
        extension_server_port: "0".to_string(), // disables extension server
        csrf,
    }
}

/// Discover only the extension_server_port and csrf_token from the running main LS.
///
/// This does NOT discover the HTTPS port — we don't need to talk to the real LS,
/// only steal its extension server connection info.
pub fn discover_main_ls_config() -> Result<MainLSConfig, String> {
    discovery::discover_main_ls_config()
}

/// Build the dns_redirect.so preload library if it doesn't already exist.
///
/// Linux only — hooks `getaddrinfo()` via LD_PRELOAD to redirect Google API
/// domain lookups to 127.0.0.1.
///
/// Returns the path to the .so on success, None on failure.
fn build_dns_redirect_so() -> Option<String> {
    if !platform::supports_ld_preload() {
        return None;
    }

    let p = paths();
    let so_path = &p.dns_redirect_so_path;

    // Skip rebuild if already exists
    if std::path::Path::new(so_path).exists() {
        return Some(so_path.to_string());
    }

    // Write C source to a temp file
    let c_path = format!("{so_path}.c");
    if let Err(e) = std::fs::write(&c_path, DNS_REDIRECT_C_SOURCE) {
        tracing::warn!("Failed to write dns_redirect.c: {e}");
        return None;
    }

    // Compile: gcc -shared -fPIC -o dns_redirect.so dns_redirect.c -ldl
    let output = Command::new("gcc")
        .args(["-shared", "-fPIC", "-o", so_path.as_str(), &c_path, "-ldl"])
        .output();

    match output {
        Ok(out) if out.status.success() => {
            info!("Built dns_redirect.so at {so_path}");
            let _ = std::fs::remove_file(&c_path);
            Some(so_path.to_string())
        }
        Ok(out) => {
            let stderr = String::from_utf8_lossy(&out.stderr);
            tracing::warn!("Failed to compile dns_redirect.so: {stderr}");
            None
        }
        Err(e) => {
            tracing::warn!("gcc not found, cannot build dns_redirect.so: {e}");
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use super::discovery::find_free_port;
    use std::net::TcpListener;

    #[test]
    fn test_find_free_port() {
        let port = find_free_port().unwrap();
        assert!(port > 0);
        // Port should be available — try binding to it
        let listener = TcpListener::bind(format!("127.0.0.1:{port}"));
        assert!(listener.is_ok(), "Port {port} should be free");
    }
}
