// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::{collections::HashMap, fmt};

use anyhow::Result;
use indexmap::IndexMap;
use serde::{de::DeserializeOwned, Serialize};

/// Generic configuration value that can represent data from different formats
#[derive(Debug, Clone, PartialEq, Serialize)]
pub enum ConfigValue {
    /// Null or missing value
    Null,
    /// Boolean value
    Bool(bool),
    /// Integer value
    Integer(i64),
    /// Floating point value
    Float(f64),
    /// String value
    String(String),
    /// Array of values
    Array(Vec<ConfigValue>),
    /// Key-value mapping
    Object(IndexMap<String, ConfigValue>),
}

impl ConfigValue {
    /// Create a ConfigValue from any serializable type
    pub fn from_serializable<T: Serialize>(value: T) -> Result<Self> {
        let json_value = serde_json::to_value(value)?;
        Ok(Self::from_json_value(json_value))
    }

    /// Try to deserialize this value as a specific type
    pub fn try_into<T: DeserializeOwned>(self) -> Result<T> {
        let json_value = self.to_json_value();
        Ok(serde_json::from_value(json_value)?)
    }

    /// Check if this value is null/missing
    pub fn is_null(&self) -> bool {
        matches!(self, ConfigValue::Null)
    }

    /// Get a nested value using dot notation
    pub fn get_path(&self, path: &str) -> Option<&ConfigValue> {
        if path.is_empty() {
            return Some(self);
        }

        let parts: Vec<&str> = path.split('.').collect();
        let mut current = self;

        for part in parts {
            match current {
                ConfigValue::Object(map) => {
                    current = map.get(part)?;
                }
                _ => return None,
            }
        }

        Some(current)
    }

    /// Convert from serde_json::Value
    fn from_json_value(value: serde_json::Value) -> Self {
        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::String(n.to_string())
                }
            }
            serde_json::Value::String(s) => ConfigValue::String(s),
            serde_json::Value::Array(arr) => {
                let values = arr.into_iter().map(Self::from_json_value).collect();
                ConfigValue::Array(values)
            }
            serde_json::Value::Object(obj) => {
                let mut map = IndexMap::new();
                for (k, v) in obj {
                    map.insert(k, Self::from_json_value(v));
                }
                ConfigValue::Object(map)
            }
        }
    }

    /// Convert flat dot-notation keys to nested structure
    /// This takes a map like {"foo.bar": value1, "foo.baz": value2}
    /// and converts it to {"foo": {"bar": value1, "baz": value2}}
    pub fn from_flat_map(flat_map: IndexMap<String, ConfigValue>) -> ConfigValue {
        let mut result = IndexMap::new();

        for (key, value) in flat_map {
            Self::set_nested_path(&mut result, &key, value);
        }

        ConfigValue::Object(result)
    }

    /// Set a nested value using dot notation path
    fn set_nested_path(map: &mut IndexMap<String, ConfigValue>, path: &str, value: ConfigValue) {
        let parts: Vec<&str> = path.split('.').collect();

        if parts.len() == 1 {
            map.insert(parts[0].to_string(), value);
            return;
        }

        let key = parts[0];
        let remaining_path = parts[1..].join(".");

        if !map.contains_key(key) {
            map.insert(key.to_string(), ConfigValue::Object(IndexMap::new()));
        }

        if let Some(ConfigValue::Object(nested_map)) = map.get_mut(key) {
            Self::set_nested_path(nested_map, &remaining_path, value);
        }
    }

    /// Convert to serde_json::Value for serialization
    pub fn to_json_value(self) -> serde_json::Value {
        match self {
            ConfigValue::Null => serde_json::Value::Null,
            ConfigValue::Bool(b) => serde_json::Value::Bool(b),
            ConfigValue::Integer(i) => serde_json::Value::Number(i.into()),
            ConfigValue::Float(f) => serde_json::Value::Number(
                serde_json::Number::from_f64(f).unwrap_or_else(|| 0.into()),
            ),
            ConfigValue::String(s) => serde_json::Value::String(s),
            ConfigValue::Array(arr) => {
                let values = arr.into_iter().map(|v| v.to_json_value()).collect();
                serde_json::Value::Array(values)
            }
            ConfigValue::Object(obj) => {
                let mut map = serde_json::Map::new();
                for (k, v) in obj {
                    map.insert(k, v.to_json_value());
                }
                serde_json::Value::Object(map)
            }
        }
    }

    /// Convert to toml::Value for TOML serialization
    #[cfg(feature = "file-system")]
    pub fn to_toml_value(self) -> Result<toml::Value> {
        match self {
            ConfigValue::Null => Ok(toml::Value::String("null".to_string())),
            ConfigValue::Bool(b) => Ok(toml::Value::Boolean(b)),
            ConfigValue::Integer(i) => Ok(toml::Value::Integer(i)),
            ConfigValue::Float(f) => Ok(toml::Value::Float(f)),
            ConfigValue::String(s) => Ok(toml::Value::String(s)),
            ConfigValue::Array(arr) => {
                let values: Result<Vec<_>, _> =
                    arr.into_iter().map(|v| v.to_toml_value()).collect();
                Ok(toml::Value::Array(values?))
            }
            ConfigValue::Object(obj) => {
                let mut map = toml::map::Map::new();
                for (k, v) in obj {
                    map.insert(k, v.to_toml_value()?);
                }
                Ok(toml::Value::Table(map))
            }
        }
    }

    /// Create from toml::Value
    #[cfg(feature = "file-system")]
    pub fn from_toml_value(value: toml::Value) -> Self {
        match value {
            toml::Value::String(s) => ConfigValue::String(s),
            toml::Value::Integer(i) => ConfigValue::Integer(i),
            toml::Value::Float(f) => ConfigValue::Float(f),
            toml::Value::Boolean(b) => ConfigValue::Bool(b),
            toml::Value::Datetime(dt) => ConfigValue::String(dt.to_string()),
            toml::Value::Array(arr) => {
                let values = arr.into_iter().map(Self::from_toml_value).collect();
                ConfigValue::Array(values)
            }
            toml::Value::Table(table) => {
                let mut map = IndexMap::new();
                for (k, v) in table {
                    map.insert(k, Self::from_toml_value(v));
                }
                ConfigValue::Object(map)
            }
        }
    }
}

impl fmt::Display for ConfigValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ConfigValue::Null => write!(f, "null"),
            ConfigValue::Bool(b) => write!(f, "{}", b),
            ConfigValue::Integer(i) => write!(f, "{}", i),
            ConfigValue::Float(fl) => write!(f, "{}", fl),
            ConfigValue::String(s) => write!(f, "{}", s),
            ConfigValue::Array(arr) => {
                write!(f, "[")?;
                for (i, item) in arr.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}", item)?;
                }
                write!(f, "]")
            }
            ConfigValue::Object(_) => write!(f, "{{...}}"),
        }
    }
}

// Convenience From implementations for common types
impl From<bool> for ConfigValue {
    fn from(value: bool) -> Self {
        ConfigValue::Bool(value)
    }
}

impl From<i8> for ConfigValue {
    fn from(value: i8) -> Self {
        ConfigValue::Integer(value as i64)
    }
}

impl From<i16> for ConfigValue {
    fn from(value: i16) -> Self {
        ConfigValue::Integer(value as i64)
    }
}

impl From<i32> for ConfigValue {
    fn from(value: i32) -> Self {
        ConfigValue::Integer(value as i64)
    }
}

impl From<i64> for ConfigValue {
    fn from(value: i64) -> Self {
        ConfigValue::Integer(value)
    }
}

impl From<u8> for ConfigValue {
    fn from(value: u8) -> Self {
        ConfigValue::Integer(value as i64)
    }
}

impl From<u16> for ConfigValue {
    fn from(value: u16) -> Self {
        ConfigValue::Integer(value as i64)
    }
}

impl From<u32> for ConfigValue {
    fn from(value: u32) -> Self {
        ConfigValue::Integer(value as i64)
    }
}

impl From<f32> for ConfigValue {
    fn from(value: f32) -> Self {
        ConfigValue::Float(value as f64)
    }
}

impl From<f64> for ConfigValue {
    fn from(value: f64) -> Self {
        ConfigValue::Float(value)
    }
}

impl From<String> for ConfigValue {
    fn from(value: String) -> Self {
        ConfigValue::String(value)
    }
}

impl From<&str> for ConfigValue {
    fn from(value: &str) -> Self {
        ConfigValue::String(value.to_string())
    }
}

impl From<Vec<ConfigValue>> for ConfigValue {
    fn from(value: Vec<ConfigValue>) -> Self {
        ConfigValue::Array(value)
    }
}

impl From<IndexMap<String, ConfigValue>> for ConfigValue {
    fn from(value: IndexMap<String, ConfigValue>) -> Self {
        ConfigValue::Object(value)
    }
}

impl From<HashMap<String, ConfigValue>> for ConfigValue {
    fn from(value: HashMap<String, ConfigValue>) -> Self {
        ConfigValue::Object(value.into_iter().collect())
    }
}

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

    #[test]
    fn test_value_conversions() {
        let val = ConfigValue::from(42);
        assert_eq!(val, ConfigValue::Integer(42));

        let val = ConfigValue::from("hello");
        assert_eq!(val, ConfigValue::String("hello".to_string()));

        let val = ConfigValue::from(true);
        assert_eq!(val, ConfigValue::Bool(true));
    }

    #[test]
    fn test_path_access() {
        let mut obj = IndexMap::new();
        obj.insert("timeout".to_string(), ConfigValue::Integer(30));

        let mut root = IndexMap::new();
        root.insert("network".to_string(), ConfigValue::Object(obj));

        let config = ConfigValue::Object(root);

        let timeout = config.get_path("network.timeout").unwrap();
        assert_eq!(timeout, &ConfigValue::Integer(30));

        assert!(config.get_path("network.nonexistent").is_none());
    }

    #[test]
    fn test_try_into() {
        let val = ConfigValue::Integer(42);
        let result: i64 = val.try_into().unwrap();
        assert_eq!(result, 42);

        let val = ConfigValue::String("hello".to_string());
        let result: String = val.try_into().unwrap();
        assert_eq!(result, "hello");
    }
}
