use anyhow::{bail, Context, Result};
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};

const LLM_SECRETS_FILE: &str = "llm_secrets.json";

#[derive(Default, serde::Serialize, serde::Deserialize)]
struct SecretsStore {
    #[serde(default)]
    api_keys: BTreeMap<String, String>,
    #[serde(default)]
    tokens: BTreeMap<String, String>,
}

fn get_use_sdf_from_config(config_path: &Path) -> bool {
    let config_content = match fs::read_to_string(config_path) {
        Ok(c) => c,
        Err(_) => return false,
    };

    let config_value: toml::Value = match config_content.parse() {
        Ok(v) => v,
        Err(_) => return false,
    };

    config_value
        .get("vault")
        .and_then(|vault| vault.get("use_sdf"))
        .and_then(|v| v.as_bool())
        .unwrap_or(false)
}

pub fn save_llm_secret(config_path: &Path, env_name: &str, secret: &str) -> Result<()> {
    let secrets_path = llm_secrets_path(config_path);

    if let Some(parent) = secrets_path.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("failed to create secrets directory {}", parent.display()))?;
    }

    let use_sdf = get_use_sdf_from_config(config_path);

    let mut store = load_secrets_store(&secrets_path, use_sdf)?;
    store
        .api_keys
        .insert(env_name.to_string(), secret.to_string());

    save_encrypted_store(&secrets_path, &store, use_sdf)
}

pub fn save_token(config_path: &Path, token_name: &str, token: &str) -> Result<()> {
    let secrets_path = llm_secrets_path(config_path);
    if let Some(parent) = secrets_path.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("failed to create secrets directory {}", parent.display()))?;
    }

    let use_sdf = get_use_sdf_from_config(config_path);

    let mut store = load_secrets_store(&secrets_path, use_sdf)?;
    store
        .tokens
        .insert(token_name.to_string(), token.to_string());

    save_encrypted_store(&secrets_path, &store, use_sdf)
}

pub fn auto_save_from_env(config_path: &Path) -> Result<()> {
    let config_content = match fs::read_to_string(config_path) {
        Ok(c) => c,
        Err(_) => return Ok(()),
    };

    let config_value: toml::Value = match config_content.parse() {
        Ok(v) => v,
        Err(_) => return Ok(()),
    };

    let api_key_env = config_value
        .get("llm")
        .and_then(|llm| llm.get("api_key_env"))
        .and_then(|v| v.as_str())
        .filter(|s| !s.is_empty());

    let Some(env_name) = api_key_env else {
        return Ok(());
    };

    let api_key = match std::env::var(env_name) {
        Ok(v) if !v.trim().is_empty() => v,
        Ok(_) => return Ok(()),
        Err(_) => return Ok(()),
    };

    save_llm_secret(config_path, env_name, &api_key)?;

    tracing::info!(
        "auto saving API key from env {} to llm_secrets.json",
        env_name
    );

    Ok(())
}

fn load_secrets_store(path: &Path, use_sdf: bool) -> Result<SecretsStore> {
    if !path.exists() {
        return Ok(SecretsStore::default());
    }

    let bytes = fs::read(path)
        .with_context(|| format!("failed to read secrets file {}", path.display()))?;

    let decrypted = if use_sdf {
        use vault::sdf::{decrypt_secret, init_sdf_provider};
        if let Err(e) = init_sdf_provider("/usr/local/sdf/lib/libsdf.so") {
            anyhow::bail!("SDF 初始化失败: {}", e);
        }
        decrypt_secret(&bytes).map_err(|e| anyhow::anyhow!("SDF 解密失败: {}", e))?
    } else {
        decrypt_aes_gcm(&bytes)?
    };

    serde_json::from_slice(&decrypted)
        .with_context(|| format!("failed to parse secrets file {}", path.display()))
}

fn save_encrypted_store(path: &Path, store: &SecretsStore, use_sdf: bool) -> Result<()> {
    let json = serde_json::to_vec(store).context("failed to serialize secrets")?;

    let encrypted = if use_sdf {
        use vault::sdf::{encrypt_secret, init_sdf_provider};
        if let Err(e) = init_sdf_provider("/usr/local/sdf/lib/libsdf.so") {
            anyhow::bail!("SDF 初始化失败: {}", e);
        }
        encrypt_secret(json.as_ref()).map_err(|e| anyhow::anyhow!("SDF 加密失败: {}", e))?
    } else {
        encrypt_aes_gcm(&json)?
    };

    fs::write(path, encrypted)
        .with_context(|| format!("failed to write secrets file {}", path.display()))?;

    Ok(())
}

fn encrypt_aes_gcm(plaintext: &[u8]) -> Result<Vec<u8>> {
    use aes_gcm::{
        aead::{Aead, KeyInit},
        Aes256Gcm, Nonce,
    };
    use rand::RngCore;
    use vault::WhiteBoxKeyProvider;

    let key_provider = WhiteBoxKeyProvider::new("default");
    let master_key = key_provider
        .get_key()
        .context("failed to get key material")?;

    let cipher = Aes256Gcm::new_from_slice(&master_key)
        .map_err(|_| anyhow::anyhow!("invalid master key"))?;

    let mut nonce_bytes = [0u8; 12];
    rand::thread_rng().fill_bytes(&mut nonce_bytes);
    let nonce = Nonce::from_slice(&nonce_bytes);

    let ciphertext = cipher
        .encrypt(nonce, plaintext)
        .map_err(|_| anyhow::anyhow!("encryption failed"))?;

    let mut result = vec![1u8];
    result.extend(&nonce_bytes);
    result.extend(&ciphertext);
    Ok(result)
}

fn decrypt_aes_gcm(encrypted: &[u8]) -> Result<Vec<u8>> {
    use aes_gcm::{
        aead::{Aead, KeyInit},
        Aes256Gcm, Nonce,
    };
    use vault::WhiteBoxKeyProvider;

    if encrypted.len() < 13 {
        bail!("encrypted data too short");
    }

    let version = encrypted[0];
    if version != 1 {
        bail!("unsupported encryption version: {}", version);
    }

    let key_provider = WhiteBoxKeyProvider::new("default");
    let master_key = key_provider
        .get_key()
        .context("failed to get key material")?;

    let cipher = Aes256Gcm::new_from_slice(&master_key)
        .map_err(|_| anyhow::anyhow!("invalid master key"))?;

    let nonce = Nonce::from_slice(&encrypted[1..13]);
    let ciphertext = &encrypted[13..];

    cipher
        .decrypt(nonce, ciphertext)
        .map_err(|_| anyhow::anyhow!("decryption failed"))
}

pub fn load_llm_secrets_to_memory(config_path: &Path) -> Result<()> {
    let use_sdf = get_use_sdf_from_config(config_path);

    let secrets_path = llm_secrets_path(config_path);

    let bytes = fs::read(&secrets_path)
        .with_context(|| format!("failed to read secrets file {}", secrets_path.display()))?;

    if bytes.is_empty() {
        let _ = fs::remove_file(&secrets_path);
        return Ok(());
    }

    if bytes.len() < 2 {
        let _ = fs::remove_file(&secrets_path);
        return Ok(());
    }

    let decrypted = if use_sdf {
        use vault::sdf::{decrypt_secret, init_sdf_provider};
        if let Err(e) = init_sdf_provider("/usr/local/sdf/lib/libsdf.so") {
            anyhow::bail!("SDF 初始化失败: {}", e);
        }
        decrypt_secret(&bytes).map_err(|e| anyhow::anyhow!("SDF 解密失败: {}", e))?
    } else {
        decrypt_aes_gcm(&bytes)?
    };

    let _store: SecretsStore =
        serde_json::from_slice(&decrypted).with_context(|| "failed to parse decrypted secrets")?;

    tracing::info!("successfully verified secrets decryption");
    Ok(())
}

pub fn llm_secrets_path(config_path: &Path) -> PathBuf {
    config_path
        .parent()
        .unwrap_or_else(|| Path::new("."))
        .join(LLM_SECRETS_FILE)
}

pub fn init_on_demand_secret_provider(config_path: &Path) -> Result<()> {
    let use_sdf = get_use_sdf_from_config(config_path);
    let secrets_path = llm_secrets_path(config_path);

    crate::gateway::init_secret_provider(secrets_path, use_sdf);
    tracing::info!(
        "on-demand secret provider initialized (use_sdf={})",
        use_sdf
    );
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{init_on_demand_secret_provider, save_llm_secret};

    #[test]
    fn saved_secret_can_be_retrieved_on_demand() {
        let temp_dir = tempfile::TempDir::new().expect("create temp dir");
        let config_path = temp_dir.path().join("config.toml");
        let env_name = "XIAOO_TEST_DEEPSEEK_API_KEY";

        std::env::remove_var(env_name);
        std::env::set_var("USE_SDF", "false");
        save_llm_secret(&config_path, env_name, "test-secret").expect("save secret");

        init_on_demand_secret_provider(&config_path).expect("init provider");

        let retrieved = crate::gateway::get_decrypted_api_key(env_name);
        assert_eq!(retrieved, Some("test-secret".to_string()));

        std::env::remove_var("USE_SDF");
    }
}