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

use std::collections::HashMap;
#[cfg(feature = "environment-vars")]
use std::env;
#[cfg(feature = "file-system")]
use std::{
    fs,
    path::{Path, PathBuf},
};

use anyhow::{Context, Result};
use indexmap::IndexMap;
use tracing::trace;

use crate::value::ConfigValue;

/// Source of a configuration layer for debugging and precedence tracking
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigLayerSource {
    /// Default configuration baked into the binary
    Default,
    /// Basic environment variables (PATH, HOME, etc.)
    #[cfg(feature = "environment-vars")]
    EnvironmentBasic,
    /// User configuration file
    #[cfg(feature = "file-system")]
    User(PathBuf),
    /// Project configuration file (e.g., .rialoconfig.toml at project root)
    #[cfg(feature = "file-system")]
    Project(PathBuf),
    /// Active environment configuration file
    #[cfg(feature = "file-system")]
    ActiveEnvironment(PathBuf),
    /// Environment variable overrides (RIALO_* variables)
    #[cfg(feature = "environment-vars")]
    EnvironmentOverrides,
    /// Command-line arguments
    CommandLineArgs,
    /// Custom source (with name)
    Custom(String),
}

impl ConfigLayerSource {
    /// Returns true if this layer source represents a writable file
    pub fn is_writable_file(&self) -> bool {
        #[cfg(feature = "file-system")]
        {
            matches!(
                self,
                ConfigLayerSource::User(_)
                    | ConfigLayerSource::Project(_)
                    | ConfigLayerSource::ActiveEnvironment(_)
            )
        }
        #[cfg(not(feature = "file-system"))]
        {
            false
        }
    }

    /// Get the file path if this is a file-based source
    #[cfg(feature = "file-system")]
    pub fn file_path(&self) -> Option<&PathBuf> {
        match self {
            ConfigLayerSource::User(path) => Some(path),
            ConfigLayerSource::Project(path) => Some(path),
            ConfigLayerSource::ActiveEnvironment(path) => Some(path),
            _ => None,
        }
    }

    /// Get the file path if this is a file-based source (always None without file-system feature)
    #[cfg(not(feature = "file-system"))]
    pub fn file_path(&self) -> Option<&std::path::PathBuf> {
        None
    }

    /// Check if this source matches another source type (ignoring paths for file-based sources)
    pub fn matches_type(&self, other: &ConfigLayerSource) -> bool {
        std::mem::discriminant(self) == std::mem::discriminant(other)
    }
}

/// A configuration layer containing values from a specific source
#[derive(Debug, Clone)]
pub struct ConfigLayer {
    source: ConfigLayerSource,
    values: IndexMap<String, ConfigValue>,
    weight: Option<i32>,
}

impl ConfigLayer {
    /// Create a new configuration layer
    pub fn new(source: ConfigLayerSource, values: IndexMap<String, ConfigValue>) -> Self {
        Self {
            source,
            values,
            weight: None,
        }
    }

    /// Create a new configuration layer with explicit weight
    pub fn new_with_weight(
        source: ConfigLayerSource,
        values: IndexMap<String, ConfigValue>,
        weight: i32,
    ) -> Self {
        Self {
            source,
            values,
            weight: Some(weight),
        }
    }

    /// Get the source of this configuration layer
    pub fn source(&self) -> &ConfigLayerSource {
        &self.source
    }

    /// Get all values from this layer
    pub fn values(&self) -> &IndexMap<String, ConfigValue> {
        &self.values
    }

    /// Get the explicit weight of this layer (if any)
    pub fn weight(&self) -> Option<i32> {
        self.weight
    }

    /// Get the file path for this layer (if it's file-based)
    #[cfg(feature = "file-system")]
    pub fn file_path(&self) -> Option<&PathBuf> {
        self.source.file_path()
    }

    /// Get the file path for this layer (always None without file-system feature)
    #[cfg(not(feature = "file-system"))]
    pub fn file_path(&self) -> Option<&std::path::PathBuf> {
        None
    }

    /// Check if this layer can be written to
    pub fn is_writable(&self) -> bool {
        self.source.is_writable_file()
    }

    /// Get a specific value from this layer
    pub fn get_value(&self, key: &str) -> Option<&ConfigValue> {
        self.get_nested_value(&self.values, key)
    }

    /// Check if this layer is marked as final (blocks inheritance from lower-precedence layers)
    pub fn is_final(&self) -> bool {
        self.get_value("final")
            .and_then(|value| {
                if let ConfigValue::Bool(is_final) = value {
                    Some(*is_final)
                } else {
                    None
                }
            })
            .unwrap_or(false)
    }

    /// Get a nested value using dot notation (e.g., "network.timeout")
    fn get_nested_value<'a>(
        &self,
        values: &'a IndexMap<String, ConfigValue>,
        key: &str,
    ) -> Option<&'a ConfigValue> {
        // First try to get the key as-is (for flat keys like CLI args)
        if let Some(value) = values.get(key) {
            return Some(value);
        }

        // Then try nested lookup using ConfigValue's path access
        let parts: Vec<&str> = key.split('.').collect();

        if parts.len() == 1 {
            return values.get(key);
        }

        let mut current = values.get(parts[0])?;

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

        Some(current)
    }
}

// ============================================================================
// Layer Builder Types
// ============================================================================

/// Supported configuration file formats
#[cfg(feature = "file-system")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileFormat {
    /// TOML format
    Toml,
    /// JSON format
    Json,
    /// YAML format
    Yaml,
}

#[cfg(feature = "file-system")]
impl FileFormat {
    /// Detect format from file extension
    pub fn from_extension(path: &Path) -> Option<Self> {
        path.extension()?
            .to_str()
            .and_then(|ext| match ext.to_lowercase().as_str() {
                "toml" => Some(FileFormat::Toml),
                "json" => Some(FileFormat::Json),
                "yaml" | "yml" => Some(FileFormat::Yaml),
                _ => None,
            })
    }
}

/// Builder for creating configuration layers from files (TOML, JSON, YAML)
#[cfg(feature = "file-system")]
#[derive(Debug)]
pub struct FileLayer {
    path: PathBuf,
    source: ConfigLayerSource,
    required: bool,
    format: Option<FileFormat>,
    weight: Option<i32>,
}

#[cfg(feature = "file-system")]
impl FileLayer {
    /// Helper to embed the file path in file-based config layer sources
    fn embed_path_in_source(&self, source: &ConfigLayerSource) -> ConfigLayerSource {
        match source {
            ConfigLayerSource::User(_) => ConfigLayerSource::User(self.path.clone()),
            ConfigLayerSource::Project(_) => ConfigLayerSource::Project(self.path.clone()),
            ConfigLayerSource::ActiveEnvironment(_) => {
                ConfigLayerSource::ActiveEnvironment(self.path.clone())
            }
            _ => source.clone(),
        }
    }

    /// Helper to create a ConfigLayer with optional weight
    fn create_layer(
        &self,
        source: ConfigLayerSource,
        values: IndexMap<String, ConfigValue>,
    ) -> ConfigLayer {
        match self.weight {
            Some(weight) => ConfigLayer::new_with_weight(source, values, weight),
            None => ConfigLayer::new(source, values),
        }
    }
    /// Create a new file layer builder (format auto-detected from extension)
    pub fn new<P: Into<PathBuf>>(path: P) -> Self {
        let path = path.into();
        Self {
            path,
            source: ConfigLayerSource::User(PathBuf::new()), // Will be replaced in build()
            required: true,
            format: None,
            weight: None,
        }
    }

    /// Set the source type for this layer
    pub fn with_source(mut self, source: ConfigLayerSource) -> Self {
        self.source = self.embed_path_in_source(&source);
        self
    }

    /// Set whether this file is required to exist
    pub fn required(mut self, required: bool) -> Self {
        self.required = required;
        self
    }

    /// Explicitly set the file format (overrides auto-detection)
    pub fn with_format(mut self, format: FileFormat) -> Self {
        self.format = Some(format);
        self
    }

    /// Set an explicit weight for this layer (higher = higher precedence)
    pub fn with_weight(mut self, weight: i32) -> Self {
        self.weight = Some(weight);
        self
    }

    /// Build the ConfigLayer, loading from file
    pub fn build(self) -> Result<ConfigLayer> {
        if !self.path.exists() {
            if self.required {
                return Err(anyhow::anyhow!(
                    "Required config file does not exist: {}",
                    self.path.display()
                ));
            } else {
                // Even if file doesn't exist, preserve the file path so it can be written to later
                let source_with_path = self.embed_path_in_source(&self.source);
                return Ok(self.create_layer(source_with_path, IndexMap::new()));
            }
        }

        trace!("Loading config layer from file: {}", self.path.display());

        let content = fs::read_to_string(&self.path)
            .with_context(|| format!("Failed to read config file: {}", self.path.display()))?;

        // Determine format
        let format = self
            .format
            .or_else(|| FileFormat::from_extension(&self.path))
            .ok_or_else(|| {
                anyhow::anyhow!(
                    "Could not determine file format for: {}",
                    self.path.display()
                )
            })?;

        let values = self.parse_content(&content, format)?;
        let source_with_path = self.embed_path_in_source(&self.source);
        Ok(self.create_layer(source_with_path, values))
    }

    /// Try to build the layer, returning None if file doesn't exist (instead of error)
    pub fn try_build(self) -> Result<Option<ConfigLayer>> {
        if !self.path.exists() {
            return Ok(None);
        }
        Ok(Some(self.build()?))
    }

    /// Parse content based on format
    fn parse_content(
        &self,
        content: &str,
        format: FileFormat,
    ) -> Result<IndexMap<String, ConfigValue>> {
        match format {
            FileFormat::Toml => {
                let toml_value: toml::Value = toml::from_str(content).with_context(|| {
                    format!("Failed to parse TOML config file: {}", self.path.display())
                })?;
                let config_value = ConfigValue::from_toml_value(toml_value);
                if let ConfigValue::Object(map) = config_value {
                    Ok(map)
                } else {
                    Err(anyhow::anyhow!("TOML root must be a table/object"))
                }
            }
            FileFormat::Json => {
                let json_value: serde_json::Value =
                    serde_json::from_str(content).with_context(|| {
                        format!("Failed to parse JSON config file: {}", self.path.display())
                    })?;
                let config_value = ConfigValue::from_serializable(json_value)?;
                if let ConfigValue::Object(map) = config_value {
                    Ok(map)
                } else {
                    Err(anyhow::anyhow!("JSON root must be an object"))
                }
            }
            FileFormat::Yaml => {
                let yaml_value: serde_yaml::Value =
                    serde_yaml::from_str(content).with_context(|| {
                        format!("Failed to parse YAML config file: {}", self.path.display())
                    })?;
                let config_value = ConfigValue::from_serializable(yaml_value)?;
                if let ConfigValue::Object(map) = config_value {
                    Ok(map)
                } else {
                    Err(anyhow::anyhow!("YAML root must be an object/mapping"))
                }
            }
        }
    }
}

/// Builder for creating configuration layers from environment variables
#[cfg(feature = "environment-vars")]
#[derive(Debug)]
pub struct EnvironmentLayer {
    prefix: Option<String>,
    source: ConfigLayerSource,
    transform_keys: bool,
    weight: Option<i32>,
}

#[cfg(feature = "environment-vars")]
impl EnvironmentLayer {
    /// Create a new environment variable layer builder
    pub fn new() -> Self {
        Self {
            prefix: None,
            source: ConfigLayerSource::EnvironmentOverrides,
            transform_keys: true,
            weight: None,
        }
    }

    /// Only include environment variables with this prefix
    pub fn with_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
        self.prefix = Some(prefix.into());
        self
    }

    /// Set the source type for this layer
    pub fn with_source(mut self, source: ConfigLayerSource) -> Self {
        self.source = source;
        self
    }

    /// Set whether to transform keys (remove prefix, lowercase, underscore to dot)
    pub fn transform_keys(mut self, transform: bool) -> Self {
        self.transform_keys = transform;
        self
    }

    /// Set an explicit weight for this layer (higher = higher precedence)
    pub fn with_weight(mut self, weight: i32) -> Self {
        self.weight = Some(weight);
        self
    }

    /// Build the ConfigLayer from environment variables
    pub fn build(self) -> ConfigLayer {
        let mut values = IndexMap::new();

        for (key, value) in env::vars() {
            // Check if key matches prefix requirement
            let config_key = if let Some(ref prefix) = self.prefix {
                if key.starts_with(prefix) {
                    if self.transform_keys {
                        key[prefix.len()..]
                            .trim_start_matches('_')
                            .to_lowercase()
                            .replace('_', ".")
                    } else {
                        key
                    }
                } else {
                    continue;
                }
            } else if self.transform_keys {
                key.to_lowercase().replace('_', ".")
            } else {
                key
            };

            // Try to parse as different types
            let config_value = if let Ok(int_val) = value.parse::<i64>() {
                ConfigValue::Integer(int_val)
            } else if let Ok(float_val) = value.parse::<f64>() {
                ConfigValue::Float(float_val)
            } else if let Ok(bool_val) = value.parse::<bool>() {
                ConfigValue::Bool(bool_val)
            } else {
                ConfigValue::String(value)
            };

            values.insert(config_key, config_value);
        }

        match self.weight {
            Some(weight) => ConfigLayer::new_with_weight(self.source, values, weight),
            None => ConfigLayer::new(self.source, values),
        }
    }
}

#[cfg(feature = "environment-vars")]
impl Default for EnvironmentLayer {
    fn default() -> Self {
        Self::new()
    }
}

/// Builder for creating in-memory configuration layers
#[derive(Debug)]
pub struct InMemoryLayer {
    source: ConfigLayerSource,
    values: IndexMap<String, ConfigValue>,
    weight: Option<i32>,
}

impl InMemoryLayer {
    /// Create a new in-memory layer builder
    pub fn new(source: ConfigLayerSource) -> Self {
        Self {
            source,
            values: IndexMap::new(),
            weight: None,
        }
    }

    /// Add a key-value pair
    pub fn with_value<K: Into<String>, V: Into<ConfigValue>>(mut self, key: K, value: V) -> Self {
        self.values.insert(key.into(), value.into());
        self
    }

    /// Add multiple key-value pairs from a HashMap
    pub fn with_values<K, V>(mut self, values: HashMap<K, V>) -> Self
    where
        K: Into<String>,
        V: Into<String>,
    {
        for (k, v) in values {
            self.values.insert(k.into(), ConfigValue::String(v.into()));
        }
        self
    }

    /// Add values from ConfigValue map
    pub fn with_config_values(mut self, values: IndexMap<String, ConfigValue>) -> Self {
        self.values.extend(values);
        self
    }

    /// Set an explicit weight for this layer (higher = higher precedence)
    pub fn with_weight(mut self, weight: i32) -> Self {
        self.weight = Some(weight);
        self
    }

    /// Build the ConfigLayer
    pub fn build(self) -> ConfigLayer {
        match self.weight {
            Some(weight) => ConfigLayer::new_with_weight(self.source, self.values, weight),
            None => ConfigLayer::new(self.source, self.values),
        }
    }
}

/// Builder for creating configuration layers from command-line arguments
#[derive(Debug)]
pub struct CliArgsLayer {
    source: ConfigLayerSource,
    values: IndexMap<String, ConfigValue>,
    weight: Option<i32>,
}

impl CliArgsLayer {
    /// Create a new CLI args layer builder
    pub fn new() -> Self {
        Self {
            source: ConfigLayerSource::CommandLineArgs,
            values: IndexMap::new(),
            weight: None,
        }
    }

    /// Set the source type for this layer
    pub fn with_source(mut self, source: ConfigLayerSource) -> Self {
        self.source = source;
        self
    }

    /// Add arguments from a HashMap
    pub fn with_args(mut self, args: HashMap<String, String>) -> Self {
        for (key, value) in args {
            self = self.with_arg(key, value);
        }
        self
    }

    /// Add a single argument
    pub fn with_arg<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
        let value_str = value.into();
        let config_value = if let Ok(int_val) = value_str.parse::<i64>() {
            ConfigValue::Integer(int_val)
        } else if let Ok(float_val) = value_str.parse::<f64>() {
            ConfigValue::Float(float_val)
        } else if let Ok(bool_val) = value_str.parse::<bool>() {
            ConfigValue::Bool(bool_val)
        } else {
            ConfigValue::String(value_str)
        };

        self.values.insert(key.into(), config_value);
        self
    }

    /// Set an explicit weight for this layer (higher = higher precedence)
    pub fn with_weight(mut self, weight: i32) -> Self {
        self.weight = Some(weight);
        self
    }

    /// Build the ConfigLayer
    pub fn build(self) -> ConfigLayer {
        match self.weight {
            Some(weight) => ConfigLayer::new_with_weight(self.source, self.values, weight),
            None => ConfigLayer::new(self.source, self.values),
        }
    }
}

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