use std::path::Path;

use serde::de::DeserializeOwned;
use serde_json::Value;

use crate::error::{ConfigError, Result};
use crate::hierarchy;
use crate::nickel;

/// Carga un archivo de configuración (NCL o TOML) como JSON Value
///
/// # Arguments
/// - `path`: Ruta al archivo de configuración (.ncl o .toml)
///
/// # Returns
/// `serde_json::Value` con el contenido parseado
///
/// # Errors
/// - `InvalidFormat` si la extensión no es .ncl o .toml
/// - `NickelExportFailed` si el export de NCL falla
/// - `TomlParseFailed` o `JsonParseFailed` si el parsing falla
pub fn load_config<P: AsRef<Path>>(path: P) -> Result<Value> {
    load_config_with_overrides(path, &serde_json::Value::Null)
}

/// Carga un archivo de configuración mergeando overrides dinámicos.
/// Para archivos NCL, los overrides se pasan a Nickel que aplica contracts
/// al resultado mergeado. Para TOML, se hace deep-merge en memoria.
pub fn load_config_with_overrides<P: AsRef<Path>>(
    path: P,
    overrides: &serde_json::Value,
) -> Result<Value> {
    let path = path.as_ref();

    match path.extension().and_then(|s| s.to_str()) {
        Some("ncl") => load_ncl_with_overrides(path, overrides),
        Some("toml") => {
            let base = load_toml_as_json(path)?;
            let has_overrides =
                matches!(overrides, serde_json::Value::Object(m) if !m.is_empty());
            if has_overrides {
                Ok(deep_merge_json(base, overrides.clone()))
            } else {
                Ok(base)
            }
        }
        _ => {
            tracing::error!("Invalid config format: {:?}", path);
            Err(ConfigError::invalid_format(path))
        }
    }
}

/// Carga un archivo NCL con overrides, validando contracts en el merge
fn load_ncl_with_overrides(path: &Path, overrides: &serde_json::Value) -> Result<Value> {
    let json_string = nickel::export_with_overrides(path, overrides)?;

    serde_json::from_str::<Value>(&json_string)
        .map_err(|e| ConfigError::json_failed(format!("Failed to parse NCL export as JSON: {}", e)))
}

/// Carga un archivo TOML y lo convierte a JSON para consistencia
fn load_toml_as_json<P: AsRef<Path>>(path: P) -> Result<Value> {
    let path = path.as_ref();

    let content = std::fs::read_to_string(path).map_err(|e| {
        ConfigError::io_error(format!("Failed to read TOML file {:?}: {}", path, e))
    })?;

    let value: toml::Value = toml::from_str(&content).map_err(|e| {
        ConfigError::toml_failed(format!("Failed to parse TOML file {:?}: {}", path, e))
    })?;

    serde_json::to_value(value)
        .map_err(|e| ConfigError::json_failed(format!("Failed to convert TOML to JSON: {}", e)))
}

/// Deep merge de dos JSON Values. `overlay` sobreescribe campos de `base`.
/// Para objetos, mergea recursivamente. Para otros tipos, overlay gana.
fn deep_merge_json(base: Value, overlay: Value) -> Value {
    match (base, overlay) {
        (Value::Object(mut base_map), Value::Object(overlay_map)) => {
            for (key, overlay_val) in overlay_map {
                let merged = match base_map.remove(&key) {
                    Some(base_val) => deep_merge_json(base_val, overlay_val),
                    None => overlay_val,
                };
                base_map.insert(key, merged);
            }
            Value::Object(base_map)
        }
        (_, overlay) => overlay,
    }
}

/// Trait que deben implementar todas las estructuras de configuración.
///
/// El flujo de carga es:
/// 1. `collect_env_overrides()` recoge env vars como JSON Value
/// 2. El archivo NCL base se mergea con los overrides
/// 3. Nickel aplica contracts al resultado mergeado (validación)
/// 4. Si pasa, serde deserializa el JSON validado al struct
///
/// Los overrides nunca bypasean los contracts de Nickel.
pub trait ConfigLoader: DeserializeOwned + Sized + Default {
    /// Retorna el nombre del servicio (ej: "orchestrator", "mcp-server")
    fn service_name() -> &'static str;

    /// Recoge env var overrides como un JSON Value (estructura anidada).
    /// Los servicios sobreescriben este método para mapear sus env vars
    /// específicas al shape del config NCL.
    ///
    /// Retorna un objeto JSON vacío si no hay overrides.
    fn collect_env_overrides() -> serde_json::Value {
        serde_json::Value::Object(serde_json::Map::new())
    }

    /// Carga la configuración usando la jerarquía estándar.
    /// Los env overrides pasan por Nickel para validación de contracts.
    fn load() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        Self::load_from_hierarchy()
    }

    /// Carga desde la jerarquía de configuración (archivo + env overrides
    /// validados por Nickel → defaults)
    fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
    {
        let service = Self::service_name();
        let overrides = Self::collect_env_overrides();

        if let Some(path) = hierarchy::resolve_config_path(service) {
            let json_value = load_config_with_overrides(&path, &overrides)?;
            return serde_json::from_value(json_value).map_err(|e| {
                Box::new(ConfigError::deserialization_failed(format!(
                    "Failed to deserialize {} config: {}",
                    service, e
                ))) as Box<dyn std::error::Error + Send + Sync>
            });
        }

        tracing::debug!("No config file found for {} - using defaults", service);
        Ok(Self::default())
    }

    /// Carga desde un archivo específico (sin env overrides)
    fn from_path<P: AsRef<Path>>(
        path: P,
    ) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let path = path.as_ref();
        let json_value = load_config(path)?;

        serde_json::from_value(json_value).map_err(|e| {
            Box::new(ConfigError::deserialization_failed(format!(
                "Failed to deserialize config from {:?}: {}",
                path, e
            ))) as Box<dyn std::error::Error + Send + Sync>
        })
    }

    /// Carga desde un archivo con overrides validados por Nickel
    fn from_path_with_overrides<P: AsRef<Path>>(
        path: P,
        overrides: &serde_json::Value,
    ) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let path = path.as_ref();
        let json_value = load_config_with_overrides(path, overrides)?;

        serde_json::from_value(json_value).map_err(|e| {
            Box::new(ConfigError::deserialization_failed(format!(
                "Failed to deserialize config from {:?}: {}",
                path, e
            ))) as Box<dyn std::error::Error + Send + Sync>
        })
    }

    /// DEPRECATED: Mutates config after deserialization, bypassing Nickel contracts.
    /// Migrate to `collect_env_overrides()` instead. This method exists only for
    /// backward compatibility while services are migrated.
    #[deprecated(note = "Use collect_env_overrides() instead — overrides must pass through Nickel")]
    fn apply_env_overrides(
        &mut self,
    ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
        Ok(())
    }
}

/// Carga y deserializa una configuración de tipo genérico
/// Retorna un Box<dyn Error> como el trait ConfigLoader requiere
pub fn load_typed_config<T: ConfigLoader>(
) -> std::result::Result<T, Box<dyn std::error::Error + Send + Sync>> {
    T::load()
}

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

    #[test]
    fn test_load_config_invalid_extension() {
        let result = load_config("test.txt");
        assert!(matches!(result, Err(ConfigError::InvalidFormat { .. })));
    }
}
