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

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

/// Applies a provider configuration to the agent's configuration files.
pub trait Agent {
    fn apply(&self, provider: &Provider) -> Result<()>;
}

/// An adapter that writes `ClaudeCode` provider settings to the Claude Code configuration file.
pub struct ClaudeCodeAdapter {
    config_path: PathBuf,
}

impl ClaudeCodeAdapter {
    /// Creates a new `ClaudeCodeAdapter` that writes to the given config path.
    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::Validation(
                "provider target mismatch".to_string(),
            ));
        };

        ensure_parent_dir(&self.config_path)?;
        let mut config: serde_json::Value = if self.config_path.exists() {
            let contents = fs::read_to_string(&self.config_path)?;
            serde_json::from_str(&contents).map_err(|err| {
                ClosureError::Validation(format!(
                    "invalid Claude config {}: {err}",
                    self.config_path.display()
                ))
            })?
        } else {
            serde_json::json!({})
        };

        if !config.is_object() {
            return Err(ClosureError::Validation(format!(
                "Claude config root must be an object: {}",
                self.config_path.display()
            )));
        }

        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_vec_pretty(&config)?;
        atomic_write(&self.config_path, &output)?;
        Ok(())
    }
}

/// An adapter that writes `Codex` provider settings to the Codex configuration and auth files.
pub struct CodexAdapter {
    config_path: PathBuf,
    auth_path: PathBuf,
}

impl CodexAdapter {
    /// Creates a new `CodexAdapter` that writes to the given config and auth paths.
    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::Validation(
                "provider target mismatch".to_string(),
            ));
        };

        ensure_parent_dir(&self.config_path)?;
        ensure_parent_dir(&self.auth_path)?;

        let mut config: toml::Table = if self.config_path.exists() {
            let contents = fs::read_to_string(&self.config_path)?;
            contents.parse().map_err(|err| {
                ClosureError::Validation(format!(
                    "invalid Codex config {}: {err}",
                    self.config_path.display()
                ))
            })?
        } 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::Validation("invalid model_providers section".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::Validation("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)?;
        atomic_write(&self.config_path, config_output.as_bytes())?;

        let auth_value = if self.auth_path.exists() {
            let contents = fs::read_to_string(&self.auth_path)?;
            if contents.trim().is_empty() {
                serde_json::json!({})
            } else {
                serde_json::from_str::<serde_json::Value>(&contents).map_err(|err| {
                    ClosureError::Validation(format!(
                        "invalid Codex auth {}: {err}",
                        self.auth_path.display()
                    ))
                })?
            }
        } else {
            serde_json::json!({})
        };

        let mut auth = auth_value.as_object().cloned().ok_or_else(|| {
            ClosureError::Validation("Codex auth root must be an object".to_string())
        })?;

        if let Some(api_key) = api_key {
            auth.insert(
                "OPENAI_API_KEY".to_string(),
                serde_json::Value::String(api_key.clone()),
            );
        }
        let auth_output = serde_json::to_vec_pretty(&serde_json::Value::Object(auth))?;
        atomic_write(&self.auth_path, &auth_output)?;

        Ok(())
    }
}
