//! Shared constants — auto-detected from the installed Antigravity binary at startup.
//!
//! On first access, we locate the Antigravity installation (via the running
//! language server PID or well-known paths), parse `product.json` for version
//! strings, and extract Chrome/Electron versions from the binary. If detection
//! fails, we fall back to hardcoded values.

use std::fs;
use std::process::Command;
use std::sync::LazyLock;

/// Auto-detected version info from the installed Antigravity app.
struct DetectedVersions {
    antigravity: String,
    chrome: String,
    electron: String,
    client: String,
}

/// Locate the Antigravity install directory by tracing the language server PID
/// back to its binary, then walking up to the app root. Falls back to
/// well-known install paths.
fn find_install_dir() -> Option<String> {
    let p = crate::platform::Platform::detect();

    // 1. Check if platform-detected app_root exists
    let app_root_parent = std::path::Path::new(&p.app_root)
        .parent()
        .and_then(|p| p.parent())
        .map(|p| p.to_string_lossy().to_string());
    if let Some(ref dir) = app_root_parent {
        if fs::metadata(format!("{dir}/resources/app/product.json")).is_ok() {
            return Some(dir.clone());
        }
    }

    // 2. Try tracing the running language server via /proc (Linux only)
    #[cfg(target_os = "linux")]
    {
        if let Ok(output) = Command::new("sh")
            .args(["-c", "pgrep -f language_server | head -1"])
            .output()
        {
            let pid = String::from_utf8_lossy(&output.stdout).trim().to_string();
            if !pid.is_empty() {
                if let Ok(exe) = fs::read_link(format!("/proc/{pid}/exe")) {
                    let exe_str = exe.to_string_lossy().to_string();
                    if let Some(idx) = exe_str.find("/resources/") {
                        return Some(exe_str[..idx].to_string());
                    }
                }
            }
        }
    }

    // 3. Fall back to well-known install paths
    #[cfg(target_os = "linux")]
    let candidates = ["/usr/share/antigravity", "/opt/Antigravity"];
    #[cfg(target_os = "macos")]
    let candidates = [
        "/Applications/Antigravity.app/Contents",
        &format!(
            "{}/Applications/Antigravity.app/Contents",
            std::env::var("HOME").unwrap_or_default()
        ),
    ];
    #[cfg(target_os = "windows")]
    let candidates = [&format!(
        "{}\\Programs\\Antigravity",
        std::env::var("LOCALAPPDATA").unwrap_or_default()
    )];
    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
    let candidates: [&str; 0] = [];

    for path in &candidates {
        if fs::metadata(format!("{path}/resources/app/product.json")).is_ok() {
            return Some(path.to_string());
        }
    }

    None
}

/// Read `product.json` from the install dir and extract version fields.
fn read_product_json(install_dir: &str) -> (Option<String>, Option<String>) {
    let path = format!("{install_dir}/resources/app/product.json");
    let Ok(contents) = fs::read_to_string(&path) else {
        return (None, None);
    };
    let Ok(json) = serde_json::from_str::<serde_json::Value>(&contents) else {
        return (None, None);
    };

    let version = json["version"].as_str().map(|s| s.to_string());
    let ide_version = json["ideVersion"].as_str().map(|s| s.to_string());
    (version, ide_version)
}

/// Extract Chrome and Electron versions from the main binary via `strings`.
/// Pattern: "Chrome/142.0.7444.175", "Electron/39.2.3".
fn extract_binary_versions(install_dir: &str) -> (Option<String>, Option<String>) {
    let binary = format!("{install_dir}/antigravity");
    if fs::metadata(&binary).is_err() {
        return (None, None);
    }

    // Use grep -oP on the binary to avoid loading the whole thing into memory
    let chrome = Command::new("sh")
        .args([
            "-c",
            &format!(
                "strings '{}' | grep -oP 'Chrome/[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' | head -1",
                binary
            ),
        ])
        .output()
        .ok()
        .and_then(|o| {
            let s = String::from_utf8_lossy(&o.stdout).trim().to_string();
            s.strip_prefix("Chrome/").map(|v| v.to_string())
        });

    let electron = Command::new("sh")
        .args([
            "-c",
            &format!(
                "strings '{}' | grep -oP 'Electron/[0-9]+\\.[0-9]+\\.[0-9]+' | head -1",
                binary
            ),
        ])
        .output()
        .ok()
        .and_then(|o| {
            let s = String::from_utf8_lossy(&o.stdout).trim().to_string();
            s.strip_prefix("Electron/").map(|v| v.to_string())
        });

    (chrome, electron)
}

/// Detect all versions from the installed Antigravity app.
fn detect_versions() -> DetectedVersions {
    // Hardcoded fallbacks — last known good values
    const FALLBACK_ANTIGRAVITY: &str = "1.107.0";
    const FALLBACK_CHROME: &str = "142.0.7444.175";
    const FALLBACK_ELECTRON: &str = "39.2.3";
    const FALLBACK_CLIENT: &str = "1.16.5";

    let Some(install_dir) = find_install_dir() else {
        tracing::warn!("Could not find Antigravity install — using fallback versions");
        return DetectedVersions {
            antigravity: FALLBACK_ANTIGRAVITY.to_string(),
            chrome: FALLBACK_CHROME.to_string(),
            electron: FALLBACK_ELECTRON.to_string(),
            client: FALLBACK_CLIENT.to_string(),
        };
    };

    // product.json → antigravity version + client/IDE version
    let (ag_ver, client_ver) = read_product_json(&install_dir);

    // Binary → Chrome + Electron versions
    let (chrome_ver, electron_ver) = extract_binary_versions(&install_dir);

    let versions = DetectedVersions {
        antigravity: ag_ver.unwrap_or_else(|| FALLBACK_ANTIGRAVITY.to_string()),
        chrome: chrome_ver.unwrap_or_else(|| FALLBACK_CHROME.to_string()),
        electron: electron_ver.unwrap_or_else(|| FALLBACK_ELECTRON.to_string()),
        client: client_ver.unwrap_or_else(|| FALLBACK_CLIENT.to_string()),
    };

    tracing::info!(
        antigravity = %versions.antigravity,
        chrome = %versions.chrome,
        electron = %versions.electron,
        client = %versions.client,
        "Detected app versions"
    );

    versions
}

// ─── Public API ──────────────────────────────────────────────────────────────

/// All detected versions — computed once on first access.
static VERSIONS: LazyLock<DetectedVersions> = LazyLock::new(detect_versions);

/// Antigravity app version (e.g. "1.107.0").
pub fn antigravity_version() -> &'static str {
    &VERSIONS.antigravity
}

/// Chrome version bundled with Electron (e.g. "142.0.7444.175").
pub fn chrome_version() -> &'static str {
    &VERSIONS.chrome
}

/// Electron version (e.g. "39.2.3").
pub fn electron_version() -> &'static str {
    &VERSIONS.electron
}

/// Client/IDE version from product.json (e.g. "1.16.5").
pub fn client_version() -> &'static str {
    &VERSIONS.client
}

pub const CLIENT_NAME: &str = "antigravity";
pub const LS_SERVICE: &str = "exa.language_server_pb.LanguageServerService";

/// Log base directory for Antigravity.
pub fn log_base() -> String {
    let p = crate::platform::Platform::detect();
    // Antigravity logs live next to its state DB
    let state_parent = std::path::Path::new(&p.state_db_path)
        .parent()
        .and_then(|p| p.parent())
        .and_then(|p| p.parent())
        .map(|p| p.to_string_lossy().to_string())
        .unwrap_or_else(|| {
            let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
            format!("{home}/.config/Antigravity")
        });
    format!("{state_parent}/logs")
}

/// Token file path.
pub fn token_file_path() -> String {
    crate::platform::Platform::detect()
        .token_path
        .to_string_lossy()
        .to_string()
}

/// User-Agent string matching the Electron webview — computed once.
pub static USER_AGENT: LazyLock<String> = LazyLock::new(|| {
    format!(
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \
         (KHTML, like Gecko) Antigravity/{} \
         Chrome/{} Electron/{} Safari/537.36",
        antigravity_version(),
        chrome_version(),
        electron_version()
    )
});

/// Chrome major version for sec-ch-ua header — computed once.
pub static CHROME_MAJOR: LazyLock<String> = LazyLock::new(|| {
    chrome_version()
        .split('.')
        .next()
        .unwrap_or("142")
        .to_string()
});

/// Safely truncate a string to at most `max` characters (not bytes).
pub fn safe_truncate(s: &str, max: usize) -> String {
    match s.char_indices().nth(max) {
        None => s.to_string(),
        Some((idx, _)) => format!("{}...", &s[..idx]),
    }
}
