//! Platform detection and path resolution.
//!
//! All platform-specific paths are resolved here. Every path can be overridden
//! via environment variables, falling back to OS-specific defaults.
//!
//! # Env Var Overrides
//!
//! | Variable | Description |
//! |----------|-------------|
//! | `ZEROGRAVITY_LS_PATH` | Path to the Language Server binary |
//! | `ZEROGRAVITY_APP_ROOT` | Antigravity app root directory |
//! | `ZEROGRAVITY_DATA_DIR` | Standalone LS data directory |
//! | `ZEROGRAVITY_CONFIG_DIR` | ZeroGravity config directory |
//! | `ZEROGRAVITY_LS_USER` | System user for LS isolation (Linux only) |
//! | `ZEROGRAVITY_STATE_DB` | Path to Antigravity's state.vscdb |
//! | `SSL_CERT_FILE` | System CA certificate bundle |

use std::path::PathBuf;

/// All platform-specific paths, resolved once at startup.
#[derive(Debug, Clone)]
pub struct Platform {
    /// Path to the Language Server binary.
    pub ls_binary_path: String,
    /// Antigravity app root (for ANTIGRAVITY_EDITOR_APP_ROOT).
    pub app_root: String,
    /// Data directory for standalone LS runtime files.
    pub data_dir: String,
    /// Config directory (~/.config/zerogravity or platform equivalent).
    pub config_dir: PathBuf,
    /// System CA certificate bundle path.
    pub ca_cert_path: String,
    /// System user for UID-scoped isolation (Linux only).
    pub ls_user: String,
    /// Path to Antigravity's state.vscdb.
    pub state_db_path: String,
    /// Token file path.
    pub token_path: PathBuf,
    /// Traces directory.
    pub traces_dir: PathBuf,
    /// DNS redirect shared library path (Linux only).
    pub dns_redirect_so_path: String,
    /// OS display name for sec-ch-ua-platform header ("Linux", "macOS", "Windows").
    pub os_name: &'static str,
}

impl Platform {
    /// Detect platform and resolve all paths.
    ///
    /// Environment variables override platform defaults.
    pub fn detect() -> Self {
        let home = home_dir();
        let config_dir = env_or("ZEROGRAVITY_CONFIG_DIR", || default_config_dir(&home));

        let ls_binary_path = env_or("ZEROGRAVITY_LS_PATH", default_ls_binary_path);
        let app_root = env_or("ZEROGRAVITY_APP_ROOT", default_app_root);
        let data_dir = env_or("ZEROGRAVITY_DATA_DIR", default_data_dir);
        let ca_cert_path = env_or("SSL_CERT_FILE", default_ca_cert_path);
        let ls_user = env_or("ZEROGRAVITY_LS_USER", || "zerogravity-ls".into());
        let state_db_path = env_or("ZEROGRAVITY_STATE_DB", || default_state_db_path(&home));
        let dns_redirect_so_path = format!("{}/dns-redirect.so", &data_dir);

        let config_dir = PathBuf::from(&config_dir);
        let token_path = config_dir.join("token");
        let traces_dir = config_dir.join("traces");

        Self {
            ls_binary_path,
            app_root,
            data_dir,
            config_dir,
            ca_cert_path,
            ls_user,
            state_db_path,
            token_path,
            traces_dir,
            dns_redirect_so_path,
            os_name: default_os_name(),
        }
    }
}

// ── Helpers ──

fn home_dir() -> String {
    std::env::var("HOME")
        .or_else(|_| std::env::var("USERPROFILE")) // Windows
        .unwrap_or_else(|_| "/tmp".into())
}

fn env_or(var: &str, default: impl FnOnce() -> String) -> String {
    std::env::var(var).unwrap_or_else(|_| default())
}

// ── Platform defaults ──

#[cfg(target_os = "linux")]
fn default_ls_binary_path() -> String {
    "/usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64"
        .into()
}

#[cfg(target_os = "macos")]
fn default_ls_binary_path() -> String {
    let home = home_dir();
    // Check both /Applications and ~/Applications
    for base in &[
        "/Applications/Antigravity.app",
        &format!("{home}/Applications/Antigravity.app"),
    ] {
        let path = format!(
            "{base}/Contents/Resources/app/extensions/antigravity/bin/language_server_darwin_arm64"
        );
        if std::path::Path::new(&path).exists() {
            return path;
        }
    }
    "/Applications/Antigravity.app/Contents/Resources/app/extensions/antigravity/bin/language_server_darwin_arm64".into()
}

#[cfg(target_os = "windows")]
fn default_ls_binary_path() -> String {
    let local = std::env::var("LOCALAPPDATA")
        .unwrap_or_else(|_| "C:\\Users\\Default\\AppData\\Local".into());
    format!("{local}\\Programs\\Antigravity\\resources\\app\\extensions\\antigravity\\bin\\language_server_windows_x64.exe")
}

#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
fn default_ls_binary_path() -> String {
    "language_server".into()
}

// ── App root ──

#[cfg(target_os = "linux")]
fn default_app_root() -> String {
    "/usr/share/antigravity/resources/app".into()
}

#[cfg(target_os = "macos")]
fn default_app_root() -> String {
    "/Applications/Antigravity.app/Contents/Resources/app".into()
}

#[cfg(target_os = "windows")]
fn default_app_root() -> String {
    let local = std::env::var("LOCALAPPDATA")
        .unwrap_or_else(|_| "C:\\Users\\Default\\AppData\\Local".into());
    format!("{local}\\Programs\\Antigravity\\resources\\app")
}

#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
fn default_app_root() -> String {
    ".".into()
}

// ── Data dir ──

fn default_data_dir() -> String {
    #[cfg(target_os = "windows")]
    {
        let temp = std::env::var("TEMP").unwrap_or_else(|_| "C:\\Temp".into());
        format!("{temp}\\zerogravity-standalone")
    }
    #[cfg(not(target_os = "windows"))]
    {
        "/tmp/zerogravity-standalone".into()
    }
}

// ── Config dir ──

fn default_config_dir(home: &str) -> String {
    #[cfg(target_os = "macos")]
    {
        format!("{home}/Library/Application Support/zerogravity")
    }
    #[cfg(target_os = "windows")]
    {
        let appdata =
            std::env::var("APPDATA").unwrap_or_else(|_| format!("{home}\\AppData\\Roaming"));
        format!("{appdata}\\zerogravity")
    }
    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
    {
        format!("{home}/.config/zerogravity")
    }
}

// ── CA certs ──

fn default_ca_cert_path() -> String {
    #[cfg(target_os = "macos")]
    {
        "/etc/ssl/cert.pem".into()
    }
    #[cfg(target_os = "windows")]
    {
        // Windows uses native cert store, this is a fallback
        String::new()
    }
    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
    {
        // Try common Linux paths
        for path in &[
            "/etc/ssl/certs/ca-certificates.crt",
            "/etc/pki/tls/certs/ca-bundle.crt",
            "/etc/ssl/ca-bundle.pem",
        ] {
            if std::path::Path::new(path).exists() {
                return path.to_string();
            }
        }
        "/etc/ssl/certs/ca-certificates.crt".into()
    }
}

// ── State DB ──

fn default_state_db_path(home: &str) -> String {
    #[cfg(target_os = "macos")]
    {
        format!("{home}/Library/Application Support/Antigravity/User/globalStorage/state.vscdb")
    }
    #[cfg(target_os = "windows")]
    {
        let appdata =
            std::env::var("APPDATA").unwrap_or_else(|_| format!("{home}\\AppData\\Roaming"));
        format!("{appdata}\\Antigravity\\User\\globalStorage\\state.vscdb")
    }
    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
    {
        format!("{home}/.config/Antigravity/User/globalStorage/state.vscdb")
    }
}

// ── OS name ──

fn default_os_name() -> &'static str {
    #[cfg(target_os = "linux")]
    {
        "Linux"
    }
    #[cfg(target_os = "macos")]
    {
        "macOS"
    }
    #[cfg(target_os = "windows")]
    {
        "Windows"
    }
    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
    {
        "Unknown"
    }
}

// ── Platform queries ──

/// Returns true if running on Linux.
pub fn is_linux() -> bool {
    cfg!(target_os = "linux")
}

/// Returns true if running on macOS.
#[allow(dead_code)]
pub fn is_macos() -> bool {
    cfg!(target_os = "macos")
}

/// Returns true if running on Windows.
#[allow(dead_code)]
pub fn is_windows() -> bool {
    cfg!(target_os = "windows")
}

/// Returns true if UID isolation (iptables + dedicated user) is available.
///
/// Only supported on Linux with the zerogravity-ls system user.
pub fn supports_uid_isolation() -> bool {
    #[cfg(target_os = "linux")]
    {
        std::process::Command::new("id")
            .args(["-u", "zerogravity-ls"])
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status()
            .map(|s| s.success())
            .unwrap_or(false)
    }
    #[cfg(not(target_os = "linux"))]
    {
        false
    }
}

/// Returns true if LD_PRELOAD DNS redirect is supported (Linux only).
pub fn supports_ld_preload() -> bool {
    cfg!(target_os = "linux")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_detect_doesnt_panic() {
        let p = Platform::detect();
        assert!(!p.ls_binary_path.is_empty());
        assert!(!p.app_root.is_empty());
        assert!(!p.data_dir.is_empty());
    }

    #[test]
    fn test_env_override() {
        std::env::set_var("ZEROGRAVITY_LS_PATH", "/custom/ls");
        let p = Platform::detect();
        assert_eq!(p.ls_binary_path, "/custom/ls");
        std::env::remove_var("ZEROGRAVITY_LS_PATH");
    }

    #[test]
    fn test_config_dir_has_token_and_traces() {
        let p = Platform::detect();
        assert!(p.token_path.ends_with("token"));
        assert!(p.traces_dir.ends_with("traces"));
    }
}
