/// ConfigSource trait and implementations.
///
/// Each source provides key→value pairs. Sources are stacked in priority order;
/// the Config struct resolves by asking each source in turn.

use std::collections::HashMap;
use crate::error::ConfigError;

/// A source of configuration values.
pub trait ConfigSource: Send + Sync {
    /// The name of this source (for debugging/error messages).
    fn name(&self) -> &str;

    /// Get a raw string value for a key.
    /// Returns None if this source doesn't have the key.
    fn get_raw(&self, key: &str) -> Option<String>;

    /// All key→value pairs from this source.
    fn all(&self) -> HashMap<String, String>;
}

/// Reads from environment variables.
///
/// Keys are mapped: `app.name` → `EL_APP_NAME` (uppercased, dots → underscores).
pub struct EnvVarSource {
    /// Optional prefix. Default: "EL".
    prefix: String,
}

impl EnvVarSource {
    pub fn new() -> Self {
        Self { prefix: "EL".to_string() }
    }

    pub fn with_prefix(prefix: impl Into<String>) -> Self {
        Self { prefix: prefix.into() }
    }

    fn env_key(&self, key: &str) -> String {
        let normalized = key.replace('.', "_").replace('-', "_").to_uppercase();
        format!("{}_{}", self.prefix, normalized)
    }
}

impl Default for EnvVarSource {
    fn default() -> Self {
        Self::new()
    }
}

impl ConfigSource for EnvVarSource {
    fn name(&self) -> &str {
        "environment"
    }

    fn get_raw(&self, key: &str) -> Option<String> {
        std::env::var(self.env_key(key)).ok()
    }

    fn all(&self) -> HashMap<String, String> {
        let prefix = format!("{}_", self.prefix);
        std::env::vars()
            .filter(|(k, _)| k.starts_with(&prefix))
            .map(|(k, v)| {
                let stripped = k.strip_prefix(&prefix).unwrap_or(&k);
                let config_key = stripped.to_lowercase().replace('_', ".");
                (config_key, v)
            })
            .collect()
    }
}

/// Holds an in-memory map of config values.
///
/// Used for defaults defined in code, or for config loaded from a parsed
/// TOML/JSON file section.
pub struct MapSource {
    name: String,
    values: HashMap<String, String>,
}

impl MapSource {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            values: HashMap::new(),
        }
    }

    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.values.insert(key.into(), value.into());
    }

    pub fn from_map(name: impl Into<String>, map: HashMap<String, String>) -> Self {
        Self {
            name: name.into(),
            values: map,
        }
    }
}

impl ConfigSource for MapSource {
    fn name(&self) -> &str {
        &self.name
    }

    fn get_raw(&self, key: &str) -> Option<String> {
        self.values.get(key).cloned()
    }

    fn all(&self) -> HashMap<String, String> {
        self.values.clone()
    }
}

/// Typed config value extractor.
pub trait FromConfigStr: Sized {
    fn from_config_str(s: &str) -> Result<Self, ConfigError>;
}

impl FromConfigStr for String {
    fn from_config_str(s: &str) -> Result<Self, ConfigError> {
        Ok(s.to_string())
    }
}

impl FromConfigStr for u32 {
    fn from_config_str(s: &str) -> Result<Self, ConfigError> {
        s.parse().map_err(|_| ConfigError::TypeMismatch {
            key: String::new(),
            expected: "u32".to_string(),
            got: s.to_string(),
        })
    }
}

impl FromConfigStr for u64 {
    fn from_config_str(s: &str) -> Result<Self, ConfigError> {
        s.parse().map_err(|_| ConfigError::TypeMismatch {
            key: String::new(),
            expected: "u64".to_string(),
            got: s.to_string(),
        })
    }
}

impl FromConfigStr for i32 {
    fn from_config_str(s: &str) -> Result<Self, ConfigError> {
        s.parse().map_err(|_| ConfigError::TypeMismatch {
            key: String::new(),
            expected: "i32".to_string(),
            got: s.to_string(),
        })
    }
}

impl FromConfigStr for i64 {
    fn from_config_str(s: &str) -> Result<Self, ConfigError> {
        s.parse().map_err(|_| ConfigError::TypeMismatch {
            key: String::new(),
            expected: "i64".to_string(),
            got: s.to_string(),
        })
    }
}

impl FromConfigStr for f32 {
    fn from_config_str(s: &str) -> Result<Self, ConfigError> {
        s.parse().map_err(|_| ConfigError::TypeMismatch {
            key: String::new(),
            expected: "f32".to_string(),
            got: s.to_string(),
        })
    }
}

impl FromConfigStr for f64 {
    fn from_config_str(s: &str) -> Result<Self, ConfigError> {
        s.parse().map_err(|_| ConfigError::TypeMismatch {
            key: String::new(),
            expected: "f64".to_string(),
            got: s.to_string(),
        })
    }
}

impl FromConfigStr for bool {
    fn from_config_str(s: &str) -> Result<Self, ConfigError> {
        match s.to_lowercase().as_str() {
            "true" | "1" | "yes" | "on" => Ok(true),
            "false" | "0" | "no" | "off" => Ok(false),
            _ => Err(ConfigError::TypeMismatch {
                key: String::new(),
                expected: "bool".to_string(),
                got: s.to_string(),
            }),
        }
    }
}

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

    #[test]
    fn map_source_get() {
        let mut src = MapSource::new("test");
        src.insert("app.name", "TestApp");
        assert_eq!(src.get_raw("app.name"), Some("TestApp".to_string()));
    }

    #[test]
    fn map_source_missing() {
        let src = MapSource::new("test");
        assert_eq!(src.get_raw("no.key"), None);
    }

    #[test]
    fn bool_from_config_str() {
        assert_eq!(bool::from_config_str("true").unwrap(), true);
        assert_eq!(bool::from_config_str("1").unwrap(), true);
        assert_eq!(bool::from_config_str("false").unwrap(), false);
        assert_eq!(bool::from_config_str("0").unwrap(), false);
    }

    #[test]
    fn u32_from_config_str() {
        assert_eq!(u32::from_config_str("42").unwrap(), 42u32);
    }

    #[test]
    fn u32_type_mismatch() {
        assert!(u32::from_config_str("not-a-number").is_err());
    }

    #[test]
    fn env_key_mapping() {
        let src = EnvVarSource::new();
        // app.name → EL_APP_NAME
        assert_eq!(src.env_key("app.name"), "EL_APP_NAME");
    }

    #[test]
    fn env_source_name() {
        let src = EnvVarSource::new();
        assert_eq!(src.name(), "environment");
    }
}
