//! Structured config file parsing for deep inspection.
//!
//! Provides unified parsing for YAML, JSON, and TOML config files,
//! enabling path-aware security checks on nested structures.
//!
//! # Example
//!
//! ```ignore
//! use aphoria::extractors::config_parser::{ConfigValue, parse_config};
//! use aphoria::types::Language;
//!
//! let yaml = r#"
//! server:
//!   tls:
//!     verify: false
//! "#;
//!
//! let config = parse_config(yaml, Language::Yaml)?;
//! // Walk tree, find "server.tls.verify" = false
//! ```

use std::collections::HashMap;

use crate::types::Language;

/// A unified configuration value that can represent any config format.
///
/// This enum provides a common representation for YAML, JSON, and TOML values,
/// enabling format-agnostic traversal and inspection.
#[derive(Debug, Clone, PartialEq)]
pub enum ConfigValue {
    /// Null/None value
    Null,
    /// Boolean value
    Bool(bool),
    /// Integer value (stored as i64 for maximum range)
    Integer(i64),
    /// Floating point value
    Float(f64),
    /// String value
    String(String),
    /// Array of values
    Array(Vec<ConfigValue>),
    /// Object/Map of key-value pairs
    Object(HashMap<String, ConfigValue>),
}

impl ConfigValue {
    /// Check if this value is a boolean `false`.
    pub fn is_false(&self) -> bool {
        matches!(self, ConfigValue::Bool(false))
    }

    /// Check if this value is a boolean `true`.
    pub fn is_true(&self) -> bool {
        matches!(self, ConfigValue::Bool(true))
    }

    /// Try to get this value as a boolean.
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            ConfigValue::Bool(b) => Some(*b),
            _ => None,
        }
    }

    /// Try to get this value as an integer.
    pub fn as_integer(&self) -> Option<i64> {
        match self {
            ConfigValue::Integer(i) => Some(*i),
            _ => None,
        }
    }

    /// Try to get this value as a string.
    pub fn as_str(&self) -> Option<&str> {
        match self {
            ConfigValue::String(s) => Some(s),
            _ => None,
        }
    }

    /// Try to get this value as an object.
    pub fn as_object(&self) -> Option<&HashMap<String, ConfigValue>> {
        match self {
            ConfigValue::Object(obj) => Some(obj),
            _ => None,
        }
    }

    /// Get a nested value by dot-separated path.
    ///
    /// # Example
    ///
    /// ```ignore
    /// let val = config.get_path("server.tls.verify");
    /// ```
    pub fn get_path(&self, path: &str) -> Option<&ConfigValue> {
        let parts: Vec<&str> = path.split('.').collect();
        self.get_path_parts(&parts)
    }

    fn get_path_parts(&self, parts: &[&str]) -> Option<&ConfigValue> {
        if parts.is_empty() {
            return Some(self);
        }

        match self {
            ConfigValue::Object(obj) => {
                obj.get(parts[0]).and_then(|v| v.get_path_parts(&parts[1..]))
            }
            _ => None,
        }
    }

    /// Return a human-readable type name for error messages.
    pub fn type_name(&self) -> &'static str {
        match self {
            ConfigValue::Null => "null",
            ConfigValue::Bool(_) => "boolean",
            ConfigValue::Integer(_) => "integer",
            ConfigValue::Float(_) => "float",
            ConfigValue::String(_) => "string",
            ConfigValue::Array(_) => "array",
            ConfigValue::Object(_) => "object",
        }
    }
}

/// A visitor callback for walking config trees.
///
/// The path is a dot-separated string like "server.tls.verify".
pub type ConfigVisitor<'a> = &'a mut dyn FnMut(&str, &ConfigValue);

/// Walk a config tree depth-first, calling the visitor at each leaf.
///
/// The visitor receives the full dot-path and value at each node.
pub fn walk_config(config: &ConfigValue, visitor: ConfigVisitor<'_>) {
    walk_config_inner(config, "", visitor);
}

fn walk_config_inner(value: &ConfigValue, path: &str, visitor: ConfigVisitor<'_>) {
    match value {
        ConfigValue::Object(obj) => {
            for (key, val) in obj {
                let new_path =
                    if path.is_empty() { key.clone() } else { format!("{}.{}", path, key) };
                // Visit the object node itself
                visitor(&new_path, val);
                // Recurse into children
                walk_config_inner(val, &new_path, visitor);
            }
        }
        ConfigValue::Array(arr) => {
            for (idx, val) in arr.iter().enumerate() {
                let new_path = format!("{}[{}]", path, idx);
                visitor(&new_path, val);
                walk_config_inner(val, &new_path, visitor);
            }
        }
        // Leaf nodes are already visited by the parent
        _ => {}
    }
}

/// Error type for config parsing failures.
#[derive(Debug, Clone)]
pub struct ConfigParseError {
    /// Human-readable error message describing the parse failure.
    pub message: String,
}

impl std::fmt::Display for ConfigParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Config parse error: {}", self.message)
    }
}

impl std::error::Error for ConfigParseError {}

/// Parse a config file into a unified ConfigValue.
///
/// The language determines which parser to use.
pub fn parse_config(content: &str, language: Language) -> Result<ConfigValue, ConfigParseError> {
    match language {
        Language::Yaml => parse_yaml(content),
        Language::Json => parse_json(content),
        Language::Toml => parse_toml(content),
        _ => Err(ConfigParseError {
            message: format!("Unsupported config language: {:?}", language),
        }),
    }
}

/// Parse YAML content into ConfigValue.
fn parse_yaml(content: &str) -> Result<ConfigValue, ConfigParseError> {
    let yaml_value: serde_yaml::Value = serde_yaml::from_str(content)
        .map_err(|e| ConfigParseError { message: format!("YAML parse error: {}", e) })?;
    Ok(yaml_to_config(yaml_value))
}

/// Parse JSON content into ConfigValue.
fn parse_json(content: &str) -> Result<ConfigValue, ConfigParseError> {
    let json_value: serde_json::Value = serde_json::from_str(content)
        .map_err(|e| ConfigParseError { message: format!("JSON parse error: {}", e) })?;
    Ok(json_to_config(json_value))
}

/// Parse TOML content into ConfigValue.
fn parse_toml(content: &str) -> Result<ConfigValue, ConfigParseError> {
    let toml_value: toml::Value = content
        .parse()
        .map_err(|e| ConfigParseError { message: format!("TOML parse error: {}", e) })?;
    Ok(toml_to_config(toml_value))
}

/// Convert serde_yaml::Value to ConfigValue.
fn yaml_to_config(value: serde_yaml::Value) -> ConfigValue {
    match value {
        serde_yaml::Value::Null => ConfigValue::Null,
        serde_yaml::Value::Bool(b) => ConfigValue::Bool(b),
        serde_yaml::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                ConfigValue::Integer(i)
            } else if let Some(f) = n.as_f64() {
                ConfigValue::Float(f)
            } else {
                ConfigValue::Null
            }
        }
        serde_yaml::Value::String(s) => ConfigValue::String(s),
        serde_yaml::Value::Sequence(seq) => {
            ConfigValue::Array(seq.into_iter().map(yaml_to_config).collect())
        }
        serde_yaml::Value::Mapping(map) => {
            let mut obj = HashMap::new();
            for (k, v) in map {
                if let serde_yaml::Value::String(key) = k {
                    obj.insert(key, yaml_to_config(v));
                }
            }
            ConfigValue::Object(obj)
        }
        serde_yaml::Value::Tagged(tagged) => yaml_to_config(tagged.value),
    }
}

/// Convert serde_json::Value to ConfigValue.
fn json_to_config(value: serde_json::Value) -> ConfigValue {
    match value {
        serde_json::Value::Null => ConfigValue::Null,
        serde_json::Value::Bool(b) => ConfigValue::Bool(b),
        serde_json::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                ConfigValue::Integer(i)
            } else if let Some(f) = n.as_f64() {
                ConfigValue::Float(f)
            } else {
                ConfigValue::Null
            }
        }
        serde_json::Value::String(s) => ConfigValue::String(s),
        serde_json::Value::Array(arr) => {
            ConfigValue::Array(arr.into_iter().map(json_to_config).collect())
        }
        serde_json::Value::Object(map) => {
            let mut obj = HashMap::new();
            for (k, v) in map {
                obj.insert(k, json_to_config(v));
            }
            ConfigValue::Object(obj)
        }
    }
}

/// Convert toml::Value to ConfigValue.
fn toml_to_config(value: toml::Value) -> ConfigValue {
    match value {
        toml::Value::Boolean(b) => ConfigValue::Bool(b),
        toml::Value::Integer(i) => ConfigValue::Integer(i),
        toml::Value::Float(f) => ConfigValue::Float(f),
        toml::Value::String(s) => ConfigValue::String(s),
        toml::Value::Datetime(dt) => ConfigValue::String(dt.to_string()),
        toml::Value::Array(arr) => {
            ConfigValue::Array(arr.into_iter().map(toml_to_config).collect())
        }
        toml::Value::Table(table) => {
            let mut obj = HashMap::new();
            for (k, v) in table {
                obj.insert(k, toml_to_config(v));
            }
            ConfigValue::Object(obj)
        }
    }
}

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

    #[test]
    fn test_parse_yaml_simple() {
        let yaml = r#"
server:
  port: 8080
  debug: true
"#;
        let config = parse_config(yaml, Language::Yaml).expect("parse failed");

        assert!(matches!(config, ConfigValue::Object(_)));
        assert_eq!(config.get_path("server.port"), Some(&ConfigValue::Integer(8080)));
        assert_eq!(config.get_path("server.debug"), Some(&ConfigValue::Bool(true)));
    }

    #[test]
    fn test_parse_yaml_nested() {
        let yaml = r#"
server:
  security:
    tls:
      verify: false
      min_version: "1.2"
"#;
        let config = parse_config(yaml, Language::Yaml).expect("parse failed");

        assert_eq!(config.get_path("server.security.tls.verify"), Some(&ConfigValue::Bool(false)));
        assert_eq!(
            config.get_path("server.security.tls.min_version"),
            Some(&ConfigValue::String("1.2".to_string()))
        );
    }

    #[test]
    fn test_parse_json() {
        let json = r#"{"server": {"tls_verify": false, "port": 443}}"#;
        let config = parse_config(json, Language::Json).expect("parse failed");

        assert_eq!(config.get_path("server.tls_verify"), Some(&ConfigValue::Bool(false)));
        assert_eq!(config.get_path("server.port"), Some(&ConfigValue::Integer(443)));
    }

    #[test]
    fn test_parse_toml() {
        let toml_content = r#"
[server]
debug = true
port = 8080

[server.tls]
verify = false
"#;
        let config = parse_config(toml_content, Language::Toml).expect("parse failed");

        assert_eq!(config.get_path("server.debug"), Some(&ConfigValue::Bool(true)));
        assert_eq!(config.get_path("server.tls.verify"), Some(&ConfigValue::Bool(false)));
    }

    #[test]
    fn test_walk_config() {
        let yaml = r#"
server:
  tls:
    verify: false
  debug: true
"#;
        let config = parse_config(yaml, Language::Yaml).expect("parse failed");

        let mut paths = Vec::new();
        walk_config(&config, &mut |path, value| {
            if let ConfigValue::Bool(b) = value {
                paths.push((path.to_string(), *b));
            }
        });

        assert!(paths.contains(&("server.tls.verify".to_string(), false)));
        assert!(paths.contains(&("server.debug".to_string(), true)));
    }

    #[test]
    fn test_config_value_helpers() {
        let val_false = ConfigValue::Bool(false);
        let val_true = ConfigValue::Bool(true);
        let val_int = ConfigValue::Integer(42);
        let val_str = ConfigValue::String("hello".to_string());

        assert!(val_false.is_false());
        assert!(!val_true.is_false());
        assert!(val_true.is_true());

        assert_eq!(val_false.as_bool(), Some(false));
        assert_eq!(val_int.as_integer(), Some(42));
        assert_eq!(val_str.as_str(), Some("hello"));
    }

    #[test]
    fn test_array_walk() {
        let yaml = r#"
servers:
  - name: server1
    enabled: false
  - name: server2
    enabled: true
"#;
        let config = parse_config(yaml, Language::Yaml).expect("parse failed");

        let mut found = Vec::new();
        walk_config(&config, &mut |path, value| {
            if path.contains("enabled") {
                if let ConfigValue::Bool(b) = value {
                    found.push((path.to_string(), *b));
                }
            }
        });

        assert_eq!(found.len(), 2);
    }

    #[test]
    fn test_unsupported_language() {
        let result = parse_config("content", Language::Rust);
        assert!(result.is_err());
    }
}
