/// Config — layered configuration with typed access.
///
/// Sources are stacked in priority order. The first source to provide a value
/// for a key wins. Resolution order:
///   1. Environment variables (highest)
///   2. .env file (dev only)
///   3. el.toml [env.<current>] section
///   4. el.toml [config] section (base)
///   5. Defaults defined in code (lowest)

use std::collections::HashMap;
use crate::error::ConfigError;
use crate::source::{ConfigSource, FromConfigStr, MapSource};
use crate::env::Environment;

/// The main configuration object.
///
/// Holds a stack of sources and resolves keys through them in order.
pub struct Config {
    /// Sources in descending priority order (index 0 = highest priority).
    sources: Vec<Box<dyn ConfigSource>>,
    /// Environment in effect.
    pub environment: Environment,
}

impl Config {
    /// Create an empty config with no sources.
    pub fn new(env: Environment) -> Self {
        Self {
            sources: Vec::new(),
            environment: env,
        }
    }

    /// Build with the default source stack for an application:
    /// env vars > defaults map.
    pub fn default_stack() -> Self {
        use crate::source::EnvVarSource;
        let env = Environment::current();
        let mut config = Self::new(env);
        config.push_source(Box::new(EnvVarSource::new()));
        config
    }

    /// Add a source at the lowest priority (end of the stack).
    pub fn push_source(&mut self, source: Box<dyn ConfigSource>) {
        self.sources.push(source);
    }

    /// Add a source at the highest priority (beginning of the stack).
    pub fn prepend_source(&mut self, source: Box<dyn ConfigSource>) {
        self.sources.insert(0, source);
    }

    /// Add defaults as the lowest-priority source.
    pub fn set_defaults(&mut self, defaults: HashMap<String, String>) {
        let src = MapSource::from_map("defaults", defaults);
        self.sources.push(Box::new(src));
    }

    /// Get a raw string value for a key.
    pub fn get_raw(&self, key: &str) -> Option<String> {
        for source in &self.sources {
            if let Some(val) = source.get_raw(key) {
                return Some(val);
            }
        }
        None
    }

    /// Get a typed value for a key.
    ///
    /// Returns an error if the key is not found or can't be parsed.
    pub fn get<T: FromConfigStr>(&self, key: &str) -> Result<T, ConfigError> {
        let raw = self.get_raw(key).ok_or_else(|| ConfigError::NotFound {
            key: key.to_string(),
        })?;

        T::from_config_str(&raw).map_err(|e| {
            // Inject the key into TypeMismatch errors
            match e {
                ConfigError::TypeMismatch { expected, got, .. } => {
                    ConfigError::TypeMismatch {
                        key: key.to_string(),
                        expected,
                        got,
                    }
                }
                other => other,
            }
        })
    }

    /// Get a typed value with a fallback default.
    pub fn get_or<T: FromConfigStr>(&self, key: &str, default: T) -> T {
        self.get(key).unwrap_or(default)
    }

    /// Get an optional typed value. Returns None if not set (not an error).
    pub fn get_opt<T: FromConfigStr>(&self, key: &str) -> Result<Option<T>, ConfigError> {
        match self.get_raw(key) {
            None => Ok(None),
            Some(raw) => T::from_config_str(&raw)
                .map(Some)
                .map_err(|e| match e {
                    ConfigError::TypeMismatch { expected, got, .. } => {
                        ConfigError::TypeMismatch {
                            key: key.to_string(),
                            expected,
                            got,
                        }
                    }
                    other => other,
                }),
        }
    }

    /// All key→value pairs from all sources (merged, highest-priority wins).
    pub fn all(&self) -> HashMap<String, String> {
        let mut result = HashMap::new();
        // Iterate in reverse order (lowest priority first) so higher-priority
        // sources overwrite lower-priority ones.
        for source in self.sources.iter().rev() {
            for (k, v) in source.all() {
                result.insert(k, v);
            }
        }
        result
    }
}

/// Load config from an `el.toml` string.
///
/// Reads `[config]` as the base, then overlays `[env.<environment>]`.
pub fn load_from_toml(toml_str: &str, env: &Environment) -> Result<MapSource, ConfigError> {
    let value: toml::Value = toml::from_str(toml_str)
        .map_err(|e| ConfigError::ParseError(e.to_string()))?;

    let mut map = HashMap::new();

    // Load base [config] section
    if let Some(config_section) = value.get("config") {
        if let Some(table) = config_section.as_table() {
            flatten_toml_table(table, "", &mut map);
        }
    }

    // Overlay [env.<name>] section
    let env_key = env.name();
    if let Some(env_sections) = value.get("env") {
        if let Some(env_table) = env_sections.get(env_key) {
            if let Some(table) = env_table.as_table() {
                flatten_toml_table(table, "", &mut map);
            }
        }
    }

    Ok(MapSource::from_map("el.toml", map))
}

fn flatten_toml_table(
    table: &toml::value::Table,
    prefix: &str,
    out: &mut HashMap<String, String>,
) {
    for (key, value) in table {
        let full_key = if prefix.is_empty() {
            key.clone()
        } else {
            format!("{}.{}", prefix, key)
        };

        match value {
            toml::Value::String(s) => {
                out.insert(full_key, s.clone());
            }
            toml::Value::Integer(i) => {
                out.insert(full_key, i.to_string());
            }
            toml::Value::Float(f) => {
                out.insert(full_key, f.to_string());
            }
            toml::Value::Boolean(b) => {
                out.insert(full_key, b.to_string());
            }
            toml::Value::Table(t) => {
                flatten_toml_table(t, &full_key, out);
            }
            _ => {} // Arrays, datetimes: skip for now
        }
    }
}

/// Macro for typed config access on a global/injected Config.
///
/// ```ignore
/// let name = config!(cfg, "app.name", String);
/// let port = config!(cfg, "server.port", u32, 8080);
/// ```
#[macro_export]
macro_rules! config {
    ($cfg:expr, $key:expr, $type:ty) => {
        $cfg.get::<$type>($key)
    };
    ($cfg:expr, $key:expr, $type:ty, $default:expr) => {
        $cfg.get_or::<$type>($key, $default)
    };
}

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

    fn make_config(pairs: &[(&str, &str)]) -> Config {
        let mut src = MapSource::new("test");
        for (k, v) in pairs {
            src.insert(*k, *v);
        }
        let mut cfg = Config::new(Environment::Development);
        cfg.push_source(Box::new(src));
        cfg
    }

    #[test]
    fn get_string() {
        let cfg = make_config(&[("app.name", "TestApp")]);
        assert_eq!(cfg.get::<String>("app.name").unwrap(), "TestApp");
    }

    #[test]
    fn get_u32() {
        let cfg = make_config(&[("server.port", "8080")]);
        assert_eq!(cfg.get::<u32>("server.port").unwrap(), 8080u32);
    }

    #[test]
    fn get_bool() {
        let cfg = make_config(&[("feature.enabled", "true")]);
        assert_eq!(cfg.get::<bool>("feature.enabled").unwrap(), true);
    }

    #[test]
    fn get_missing_returns_error() {
        let cfg = Config::new(Environment::Development);
        assert!(cfg.get::<String>("missing.key").is_err());
    }

    #[test]
    fn get_or_default() {
        let cfg = Config::new(Environment::Development);
        assert_eq!(cfg.get_or("timeout", 30u32), 30u32);
    }

    #[test]
    fn get_or_prefers_source() {
        let cfg = make_config(&[("timeout", "60")]);
        assert_eq!(cfg.get_or("timeout", 30u32), 60u32);
    }

    #[test]
    fn higher_priority_source_wins() {
        let mut cfg = Config::new(Environment::Development);
        let mut low = MapSource::new("low");
        low.insert("key", "low-value");
        let mut high = MapSource::new("high");
        high.insert("key", "high-value");
        cfg.push_source(Box::new(high));
        cfg.push_source(Box::new(low));
        // First source (index 0) is highest priority
        assert_eq!(cfg.get::<String>("key").unwrap(), "high-value");
    }

    #[test]
    fn get_opt_missing_is_none() {
        let cfg = Config::new(Environment::Development);
        assert_eq!(cfg.get_opt::<String>("missing").unwrap(), None);
    }

    #[test]
    fn get_opt_present_is_some() {
        let cfg = make_config(&[("key", "value")]);
        assert_eq!(
            cfg.get_opt::<String>("key").unwrap(),
            Some("value".to_string())
        );
    }

    #[test]
    fn load_from_toml_base() {
        let toml = r#"
[config]
app.name = "MyApp"
app.version = "1.0.0"
"#;
        let src = load_from_toml(toml, &Environment::Development).unwrap();
        assert_eq!(src.get_raw("app.name"), Some("MyApp".to_string()));
    }

    #[test]
    fn load_from_toml_env_overlay() {
        let toml = r#"
[config]
api.base_url = "https://api.example.com"

[env.development]
api.base_url = "http://localhost:8080"
"#;
        let src = load_from_toml(toml, &Environment::Development).unwrap();
        assert_eq!(
            src.get_raw("api.base_url"),
            Some("http://localhost:8080".to_string())
        );
    }

    #[test]
    fn load_from_toml_env_does_not_override_in_prod() {
        let toml = r#"
[config]
api.base_url = "https://api.example.com"

[env.development]
api.base_url = "http://localhost:8080"
"#;
        let src = load_from_toml(toml, &Environment::Production).unwrap();
        assert_eq!(
            src.get_raw("api.base_url"),
            Some("https://api.example.com".to_string())
        );
    }

    #[test]
    fn load_from_toml_invalid() {
        let result = load_from_toml("not valid toml %%%", &Environment::Development);
        assert!(result.is_err());
    }
}
