//! Reads/writes the "Bring Your Own Key" (BYOK) section of the V4 Operating
//! Agent's local config file (`~/.config/vigthoria/config.yaml`).
//!
//! This file is owned by `/var/www/V4-Operating-Agent/config.py`
//! (`LocalConfigStore`) and is also read directly by `main_agent.py`'s
//! `_build_provider()` to fall back to saved BYOK values when
//! `vigthoria v4 --non-interactive --provider byok` is launched without
//! explicit `--api-key`/`--base-url`/`--model` flags. To stay compatible we
//! mirror its exact YAML schema and field names, and we ONLY ever touch the
//! `byok` section - `cloud`/`v3_local`/`version`/`default_agent`/
//! `default_v4_provider` are always preserved untouched, so this can never
//! overwrite the Vigthoria-hosted Cloud/V3-local provider defaults or any
//! server-side Model Router/Gateway credentials (which live entirely
//! server-side and are never read from this file at all).
//!
//! The plaintext API key is never sent back to the frontend/webview once
//! saved - only a boolean `has_api_key` flag is exposed - mirroring the
//! Python side's own `redact()` behavior ("<set>" instead of the real value).

use std::fs;
use std::io::Write;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderDefaults {
    #[serde(default)]
    pub base_url: String,
    #[serde(default)]
    pub model: String,
}

impl Default for ProviderDefaults {
    fn default() -> Self {
        Self {
            base_url: String::new(),
            model: String::new(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ByokConfig {
    #[serde(default)]
    pub api_key: String,
    #[serde(default)]
    pub base_url: String,
    #[serde(default)]
    pub model: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct V4Config {
    #[serde(default = "default_version")]
    pub version: i64,
    #[serde(default = "default_agent")]
    pub default_agent: String,
    #[serde(default = "default_v4_provider")]
    pub default_v4_provider: String,
    #[serde(default = "default_cloud")]
    pub cloud: ProviderDefaults,
    #[serde(default = "default_v3_local")]
    pub v3_local: ProviderDefaults,
    #[serde(default)]
    pub byok: ByokConfig,
}

fn default_version() -> i64 {
    1
}
fn default_agent() -> String {
    "v3".to_string()
}
fn default_v4_provider() -> String {
    "cloud".to_string()
}
fn default_cloud() -> ProviderDefaults {
    ProviderDefaults {
        base_url: "https://gateway.vigthoria.io".to_string(),
        model: "vigthoria-cloud-default".to_string(),
    }
}
fn default_v3_local() -> ProviderDefaults {
    ProviderDefaults {
        base_url: "http://10.0.0.2:8018".to_string(),
        model: "vigthoria-v3-code-35b".to_string(),
    }
}

impl Default for V4Config {
    fn default() -> Self {
        Self {
            version: default_version(),
            default_agent: default_agent(),
            default_v4_provider: default_v4_provider(),
            cloud: default_cloud(),
            v3_local: default_v3_local(),
            byok: ByokConfig::default(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ByokStatus {
    pub has_api_key: bool,
    pub base_url: String,
    pub model: String,
    pub config_path: String,
}

/// Matches Python's `Path.home() / ".config" / "vigthoria" / "config.yaml"`.
/// Deliberately uses `dirs::home_dir()`, NOT `dirs::config_dir()` - the
/// Python side hardcodes `.config` under the home directory on every OS
/// (it does not use platform-specific config dirs), so using
/// `dirs::config_dir()` here would diverge on Windows/macOS and the
/// Workbench and V4 agent would silently read/write different files.
fn v4_config_path() -> Result<PathBuf, String> {
    let home = dirs::home_dir().ok_or_else(|| "Could not resolve home directory".to_string())?;
    Ok(home.join(".config").join("vigthoria").join("config.yaml"))
}

fn read_v4_config(path: &PathBuf) -> V4Config {
    let Ok(raw) = fs::read_to_string(path) else {
        return V4Config::default();
    };
    if raw.trim().is_empty() {
        return V4Config::default();
    }
    serde_yaml::from_str(&raw).unwrap_or_default()
}

/// Atomic write (temp file + rename) matching the Python store's approach,
/// with the same 0600/0700 permission hardening on Unix (API key is a
/// secret; keep it out of reach of other local users).
fn write_v4_config_atomic(path: &PathBuf, cfg: &V4Config) -> Result<(), String> {
    let dir = path
        .parent()
        .ok_or_else(|| "Invalid config path".to_string())?;
    fs::create_dir_all(dir).map_err(|e| format!("Failed to create config directory: {e}"))?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let _ = fs::set_permissions(dir, fs::Permissions::from_mode(0o700));
    }

    let yaml = serde_yaml::to_string(cfg).map_err(|e| format!("Failed to serialize config: {e}"))?;

    let tmp_path = dir.join(format!(".config.{}.yaml.tmp", std::process::id()));
    {
        let mut file =
            fs::File::create(&tmp_path).map_err(|e| format!("Failed to create temp config file: {e}"))?;
        file.write_all(yaml.as_bytes())
            .map_err(|e| format!("Failed to write temp config file: {e}"))?;
    }

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let _ = fs::set_permissions(&tmp_path, fs::Permissions::from_mode(0o600));
    }

    fs::rename(&tmp_path, path).map_err(|e| format!("Failed to save config file: {e}"))?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o600));
    }

    Ok(())
}

fn to_status(path: &PathBuf, cfg: &V4Config) -> ByokStatus {
    ByokStatus {
        has_api_key: !cfg.byok.api_key.trim().is_empty(),
        base_url: cfg.byok.base_url.clone(),
        model: cfg.byok.model.clone(),
        config_path: path.to_string_lossy().to_string(),
    }
}

/// Read-only status for the Settings UI. Never returns the raw key.
pub fn byok_status() -> Result<ByokStatus, String> {
    let path = v4_config_path()?;
    let cfg = read_v4_config(&path);
    Ok(to_status(&path, &cfg))
}

/// Save BYOK settings, touching ONLY the `byok` section of the shared
/// config file - `cloud`/`v3_local`/other fields are loaded and re-written
/// unchanged. `api_key` is only overwritten when `Some(non-empty)`; passing
/// `None`/empty keeps the currently-stored key (mirrors the CLI's own
/// `v4-menu` "existing value" convention), so the field can be left blank in
/// the UI to just update `base_url`/`model`. Pass `clear_api_key: true` to
/// explicitly remove a stored key.
pub fn save_byok_config(
    api_key: Option<String>,
    base_url: String,
    model: String,
    clear_api_key: bool,
) -> Result<ByokStatus, String> {
    let path = v4_config_path()?;
    let mut cfg = read_v4_config(&path);

    if clear_api_key {
        cfg.byok.api_key = String::new();
    } else if let Some(key) = api_key {
        let trimmed = key.trim();
        if !trimmed.is_empty() {
            cfg.byok.api_key = trimmed.to_string();
        }
    }
    cfg.byok.base_url = base_url.trim().to_string();
    cfg.byok.model = model.trim().to_string();

    write_v4_config_atomic(&path, &cfg)?;
    Ok(to_status(&path, &cfg))
}
