use std::path::{Path, PathBuf};

use super::error::{ClosureError, Result};
use super::provider::Provider;

pub trait Agent {
    fn apply(&self, provider: &Provider) -> Result<()>;
}

pub struct ClaudeCodeAdapter {
    config_path: PathBuf,
}

impl ClaudeCodeAdapter {
    pub fn new(config_path: &Path) -> Self {
        Self {
            config_path: config_path.to_path_buf(),
        }
    }
}

impl Agent for ClaudeCodeAdapter {
    fn apply(&self, provider: &Provider) -> Result<()> {
        let Provider::ClaudeCode { model, env, .. } = provider else {
            return Err(ClosureError::Crypto(
                "provider target mismatch".to_string()
            ));
        };

        let mut config: serde_json::Value = if self.config_path.exists() {
            let contents = std::fs::read_to_string(&self.config_path)?;
            serde_json::from_str(&contents)?
        } else {
            serde_json::json!({})
        };

        if let Some(model) = model {
            config["model"] = serde_json::Value::String(model.clone());
        }

        let env_map = env.iter()
            .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
            .collect::<serde_json::Map<String, serde_json::Value>>();
        config["env"] = serde_json::Value::Object(env_map);

        let output = serde_json::to_string_pretty(&config)?;
        std::fs::write(&self.config_path, output)?;

        Ok(())
    }
}

pub struct CodexAdapter {
    config_path: PathBuf,
    auth_path: PathBuf,
}

impl CodexAdapter {
    pub fn new(config_path: &Path, auth_path: &Path) -> Self {
        Self {
            config_path: config_path.to_path_buf(),
            auth_path: auth_path.to_path_buf(),
        }
    }
}

impl Agent for CodexAdapter {
    fn apply(&self, provider: &Provider) -> Result<()> {
        let Provider::Codex {
            api_key,
            base_url,
            model,
            model_provider,
            review_model,
            ..
        } = provider
        else {
            return Err(ClosureError::Crypto(
                "provider target mismatch".to_string()
            ));
        };

        let mut config: toml::Table = if self.config_path.exists() {
            let contents = std::fs::read_to_string(&self.config_path)?;
            contents.parse().unwrap_or_default()
        } else {
            toml::Table::new()
        };

        if let Some(model) = model {
            config.insert("model".to_string(), toml::Value::String(model.clone()));
        }
        if let Some(model_provider) = model_provider {
            config.insert(
                "model_provider".to_string(),
                toml::Value::String(model_provider.clone()),
            );
        }
        if let Some(review_model) = review_model {
            config.insert(
                "review_model".to_string(),
                toml::Value::String(review_model.clone()),
            );
        }
        if let (Some(base_url), Some(model_provider)) = (base_url, model_provider) {
            let providers = config
                .entry("model_providers")
                .or_insert_with(|| toml::Value::Table(toml::Table::new()))
                .as_table_mut()
                .ok_or_else(|| ClosureError::Crypto("invalid model_providers".to_string()))?;
            let provider_entry = providers
                .entry(model_provider.clone())
                .or_insert_with(|| toml::Value::Table(toml::Table::new()))
                .as_table_mut()
                .ok_or_else(|| ClosureError::Crypto("invalid provider entry".to_string()))?;
            provider_entry.insert(
                "base_url".to_string(),
                toml::Value::String(base_url.clone()),
            );
        }

        let config_output = toml::to_string_pretty(&config)?;
        std::fs::write(&self.config_path, config_output)?;

        let mut auth = serde_json::json!({});
        if let Some(api_key) = api_key {
            auth["OPENAI_API_KEY"] = serde_json::Value::String(api_key.clone());
        }
        let auth_output = serde_json::to_string_pretty(&auth)?;
        std::fs::write(&self.auth_path, auth_output)?;

        Ok(())
    }
}
