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

#[cfg(feature = "schema")]
use anyhow::anyhow;
use indexmap::IndexMap;
use serde::de::DeserializeOwned;
use tracing::debug;

use crate::{
    layers::{ConfigLayer, ConfigLayerSource},
    value::ConfigValue,
};

/// Configuration resolver that handles layer precedence and value resolution
#[derive(Debug)]
pub struct ConfigResolver {
    /// Layers sorted by precedence (later = higher precedence)
    sorted_layers: Vec<ConfigLayer>,
    /// Optional JSON Schema for validation (stored as JSON for cloning)
    #[cfg(feature = "schema")]
    schema_json: Option<serde_json::Value>,
    /// Compiled validator (lazily created)
    #[cfg(feature = "schema")]
    validator: Option<jsonschema::Validator>,
}

impl ConfigResolver {
    /// Create a new configuration resolver with sorted layers
    pub fn new(layers: Vec<ConfigLayer>) -> Self {
        let mut indexed_layers: Vec<(usize, ConfigLayer)> =
            layers.into_iter().enumerate().collect();

        // Sort by precedence: use explicit weight if provided, otherwise use original index
        indexed_layers.sort_by_key(|(index, layer)| layer.weight().unwrap_or(*index as i32));

        let sorted_layers = indexed_layers.into_iter().map(|(_, layer)| layer).collect();
        Self {
            sorted_layers,
            #[cfg(feature = "schema")]
            schema_json: None,
            #[cfg(feature = "schema")]
            validator: None,
        }
    }

    /// Create an empty configuration resolver (for backwards compatibility)
    pub fn empty() -> Self {
        Self {
            sorted_layers: Vec::new(),
            #[cfg(feature = "schema")]
            schema_json: None,
            #[cfg(feature = "schema")]
            validator: None,
        }
    }

    /// Resolve a specific configuration value across all layers
    pub fn resolve_value<T>(&self, key: &str) -> Option<T>
    where
        T: DeserializeOwned,
    {
        let resolved_value = self.get_highest_precedence_value(key)?;

        match resolved_value.clone().try_into() {
            Ok(value) => Some(value),
            Err(e) => {
                debug!(
                    "Failed to deserialize config value for key '{}': {}",
                    key, e
                );
                None
            }
        }
    }

    /// Resolve all configuration values into a single merged map using canonical flat keys
    pub fn resolve_all_values(&self) -> IndexMap<String, ConfigValue> {
        let mut resolved = IndexMap::new();
        let search_layers = self.get_search_layers();

        // Collect all keys in flattened form from all layers
        let mut all_keys = std::collections::HashSet::new();
        for layer in search_layers {
            // Add direct keys from layer
            for key in layer.values().keys() {
                all_keys.insert(key.clone());
            }

            // Add flattened keys for nested values
            for (key, _) in flatten_layer_values(layer) {
                all_keys.insert(key);
            }
        }

        // Resolve each key using the precedence system
        for key in all_keys {
            if let Some(value) = self.get_highest_precedence_value(&key) {
                // Only store non-object values (leaf values)
                // This ensures we only store the canonical flat keys, not nested structures
                if !matches!(value, ConfigValue::Object(_)) {
                    resolved.insert(key, value.clone());
                }
            }
        }

        resolved
    }

    /// Get the value with highest precedence for a given key
    /// Respects "final" layers that block inheritance from lower-precedence layers
    fn get_highest_precedence_value(&self, key: &str) -> Option<&ConfigValue> {
        let search_layers = self.get_search_layers();

        for layer in search_layers.iter().rev() {
            if let Some(value) = layer.get_value(key) {
                return Some(value);
            }
        }
        None
    }

    /// Find the index of the highest precedence "final" layer
    fn find_highest_final_layer_index(&self) -> Option<usize> {
        self.sorted_layers
            .iter()
            .enumerate()
            .rev() // Search from highest to lowest precedence
            .find(|(_, layer)| layer.is_final())
            .map(|(index, _)| index)
    }

    /// Get the layers that should be searched, respecting final layer boundaries
    fn get_search_layers(&self) -> &[ConfigLayer] {
        let final_layer_index = self.find_highest_final_layer_index();
        match final_layer_index {
            Some(final_idx) => &self.sorted_layers[final_idx..],
            None => &self.sorted_layers[..],
        }
    }

    /// Debug: Show resolution path for a specific key
    pub fn debug_resolution_path(
        &self,
        key: &str,
    ) -> Vec<(ConfigLayerSource, Option<ConfigValue>, i32)> {
        let mut path = Vec::new();

        for layer in &self.sorted_layers {
            let value = layer.get_value(key).cloned();
            // We can show the actual computed precedence from the weight
            let precedence = layer.weight().unwrap_or(0);
            path.push((layer.source().clone(), value, precedence));
        }

        path
    }

    /// Get access to the sorted layers (for debugging or introspection)
    pub fn layers(&self) -> &[ConfigLayer] {
        &self.sorted_layers
    }

    /// Get all writable layers sorted by precedence (highest first)
    pub fn writable_layers(&self) -> Vec<&ConfigLayer> {
        self.sorted_layers
            .iter()
            .rev() // Highest precedence first
            .filter(|layer| layer.is_writable())
            .collect()
    }

    /// Find the highest precedence writable layer for saving values
    pub fn preferred_writable_layer(&self) -> Option<&ConfigLayer> {
        self.sorted_layers
            .iter()
            .rev() // Highest precedence first
            .find(|layer| layer.is_writable())
    }

    /// Find a writable layer by source type
    pub fn writable_layer_by_source(&self, source: &ConfigLayerSource) -> Option<&ConfigLayer> {
        self.sorted_layers
            .iter()
            .find(|layer| layer.source().matches_type(source) && layer.is_writable())
    }

    /// Find the first writable User layer
    #[cfg(feature = "file-system")]
    pub fn writable_user_layer(&self) -> Option<&ConfigLayer> {
        self.sorted_layers
            .iter()
            .rev() // Highest precedence first
            .find(|layer| {
                matches!(layer.source(), ConfigLayerSource::User(_)) && layer.is_writable()
            })
    }

    /// Find the first writable User layer (always returns None without file-system feature)
    #[cfg(not(feature = "file-system"))]
    pub fn writable_user_layer(&self) -> Option<&ConfigLayer> {
        None
    }

    /// Find the first writable Project layer
    #[cfg(feature = "file-system")]
    pub fn writable_project_layer(&self) -> Option<&ConfigLayer> {
        self.sorted_layers
            .iter()
            .rev() // Highest precedence first
            .find(|layer| {
                matches!(layer.source(), ConfigLayerSource::Project(_)) && layer.is_writable()
            })
    }

    /// Find the first writable Project layer (always returns None without file-system feature)
    #[cfg(not(feature = "file-system"))]
    pub fn writable_project_layer(&self) -> Option<&ConfigLayer> {
        None
    }

    /// Find the first writable ActiveEnvironment layer
    #[cfg(feature = "file-system")]
    pub fn writable_active_environment_layer(&self) -> Option<&ConfigLayer> {
        self.sorted_layers
            .iter()
            .rev() // Highest precedence first
            .find(|layer| {
                matches!(layer.source(), ConfigLayerSource::ActiveEnvironment(_))
                    && layer.is_writable()
            })
    }

    /// Find the first writable ActiveEnvironment layer (always returns None without file-system feature)
    #[cfg(not(feature = "file-system"))]
    pub fn writable_active_environment_layer(&self) -> Option<&ConfigLayer> {
        None
    }

    /// Set the JSON Schema for validation (requires "schema" feature)
    /// On native platforms: Full validation with external reference support
    /// On WASM: Basic validation without external references
    #[cfg(all(feature = "schema", not(target_arch = "wasm32")))]
    pub fn with_schema(mut self, schema_json: serde_json::Value) -> anyhow::Result<Self> {
        let validator = jsonschema::validator_for(&schema_json)
            .map_err(|e| anyhow!("Failed to compile JSON Schema: {}", e))?;
        self.schema_json = Some(schema_json);
        self.validator = Some(validator);

        // Full validation with external reference support on native platforms
        self.validate_resolved_config()?;
        Ok(self)
    }

    /// Set the JSON Schema for validation (WASM-compatible version)
    #[cfg(all(feature = "schema", target_arch = "wasm32"))]
    pub fn with_schema(mut self, schema_json: serde_json::Value) -> anyhow::Result<Self> {
        let validator = jsonschema::validator_for(&schema_json)
            .map_err(|e| anyhow!("Failed to compile JSON Schema: {}", e))?;
        self.schema_json = Some(schema_json);
        self.validator = Some(validator);

        // Basic validation on WASM (external references not supported)
        tracing::info!(
            "Schema validation enabled for WASM - external references are not supported"
        );
        self.validate_resolved_config()?;
        Ok(self)
    }

    /// Set the JSON Schema for validation (no-op when "schema" feature is disabled)
    #[cfg(not(feature = "schema"))]
    pub fn with_schema(self, _schema_json: serde_json::Value) -> anyhow::Result<Self> {
        tracing::warn!("Schema validation is not available when 'schema' feature is disabled");
        Ok(self)
    }

    /// Copy the schema from another resolver (requires "schema" feature)
    #[cfg(feature = "schema")]
    pub fn with_schema_from(mut self, other: &ConfigResolver) -> Self {
        if let Some(ref schema_json) = other.schema_json {
            self.schema_json = Some(schema_json.clone());
            // Recompile the validator
            if let Ok(validator) = jsonschema::validator_for(schema_json) {
                self.validator = Some(validator);
            }
        }
        self
    }

    /// Copy the schema from another resolver (no-op when "schema" feature is disabled)
    #[cfg(not(feature = "schema"))]
    pub fn with_schema_from(self, _other: &ConfigResolver) -> Self {
        tracing::warn!("Schema validation is not available when 'schema' feature is disabled");
        self
    }

    /// Validate the final resolved configuration against the schema
    #[cfg(feature = "schema")]
    pub fn validate_resolved_config(&self) -> anyhow::Result<()> {
        if let Some(ref validator) = self.validator {
            let resolved_values = self.resolve_all_values();
            // Convert flat keys to nested structure for schema validation
            let config_value = ConfigValue::from_flat_map(resolved_values);
            let json_value = config_value.to_json_value();

            if let Err(validation_error) = validator.validate(&json_value) {
                let path_str = validation_error.instance_path.to_string();
                let path = if path_str.is_empty() || path_str == "/" {
                    "<root>".to_string()
                } else {
                    path_str
                };

                // Find which layers contribute to this configuration path
                let contributing_layers = self.find_contributing_layers(&path);
                let layer_info = if contributing_layers.is_empty() {
                    "  (No contributing layers found)".to_string()
                } else {
                    format!(
                        "  Contributing layers: {}",
                        contributing_layers
                            .iter()
                            .map(|source| format!("{:?}", source))
                            .collect::<Vec<_>>()
                            .join(", ")
                    )
                };

                return Err(anyhow!(
                    "Schema validation failed for resolved configuration:\n  - {}: {}\n{}",
                    path,
                    validation_error,
                    layer_info
                ));
            }
        }
        Ok(())
    }

    /// Validate the final resolved configuration against the schema (no-op when "schema" feature is disabled)
    #[cfg(not(feature = "schema"))]
    pub fn validate_resolved_config(&self) -> anyhow::Result<()> {
        // No validation when schema feature is disabled
        Ok(())
    }

    /// Find which layers contribute values to a specific configuration path
    #[cfg(feature = "schema")]
    fn find_contributing_layers(&self, path: &str) -> Vec<ConfigLayerSource> {
        let mut contributing = Vec::new();
        let search_layers = self.get_search_layers();

        // Convert path like "/database/host" to "database.host"
        let config_key = if path == "<root>" {
            return search_layers
                .iter()
                .filter(|layer| !layer.values().is_empty())
                .map(|layer| layer.source().clone())
                .collect();
        } else {
            path.trim_start_matches('/').replace('/', ".")
        };

        // Check each layer for this specific key or parent keys
        for layer in search_layers.iter().rev() {
            // Highest precedence first
            if self.layer_contributes_to_path(layer, &config_key) {
                contributing.push(layer.source().clone());
            }
        }

        contributing
    }

    /// Check if a layer contributes to a specific configuration path
    #[cfg(feature = "schema")]
    fn layer_contributes_to_path(&self, layer: &ConfigLayer, path: &str) -> bool {
        // Check if layer has the exact key
        if layer.get_value(path).is_some() {
            return true;
        }

        // Check if layer has any parent or child keys of this path
        let path_parts: Vec<&str> = path.split('.').collect();

        for key in layer.values().keys() {
            let key_parts: Vec<&str> = key.split('.').collect();

            // Check if this key is a parent of the path (e.g., "database" is parent of "database.host")
            if path_parts.len() > key_parts.len() && path_parts[..key_parts.len()] == key_parts {
                return true;
            }

            // Check if this key is a child of the path (e.g., "database.host" is child of "database")
            if key_parts.len() > path_parts.len() && key_parts[..path_parts.len()] == path_parts {
                return true;
            }

            // Check if this key is a sibling (shares same parent structure)
            // For validation errors, if we're validating database.port, we want to show
            // any layer that contributes to database.* since they're part of the same object
            if key_parts.len() == path_parts.len() && key_parts.len() > 1 {
                // Check if all parent parts match (e.g., "database" part matches for both "database.host" and "database.port")
                if key_parts[..key_parts.len() - 1] == path_parts[..path_parts.len() - 1] {
                    return true;
                }
            }
        }

        false
    }
}

/// Flatten a layer's values into dot-notation keys
fn flatten_layer_values(layer: &ConfigLayer) -> IndexMap<String, ConfigValue> {
    let mut flattened = IndexMap::new();
    for (key, value) in layer.values() {
        flatten_value_recursive(value, key.clone(), &mut flattened);
    }
    flattened
}

/// Recursively flatten a ConfigValue into dot-notation keys
fn flatten_value_recursive(
    value: &ConfigValue,
    prefix: String,
    result: &mut IndexMap<String, ConfigValue>,
) {
    match value {
        ConfigValue::Object(map) => {
            for (key, val) in map {
                let new_prefix = format!("{}.{}", prefix, key);
                flatten_value_recursive(val, new_prefix, result);
            }
        }
        _ => {
            result.insert(prefix, value.clone());
        }
    }
}

impl Default for ConfigResolver {
    fn default() -> Self {
        Self::empty()
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use super::*;
    use crate::layers::{CliArgsLayer, ConfigLayerSource, InMemoryLayer};

    #[test]
    fn test_precedence_resolution() {
        // Create layers with different values for the same key
        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("network.timeout_ms", ConfigValue::Integer(30000))
            .with_value("network.retry_count", ConfigValue::Integer(3))
            .build();

        let mut cli_args = HashMap::new();
        cli_args.insert("network.timeout_ms".to_string(), "60000".to_string());
        let cli_layer = CliArgsLayer::new().with_args(cli_args).build();

        let layers = vec![default_layer, cli_layer];
        let resolver = ConfigResolver::new(layers);

        // CLI should override default
        let timeout: i64 = resolver.resolve_value("network.timeout_ms").unwrap();
        assert_eq!(timeout, 60000);

        // Non-overridden values should come from default
        let retry_count: i64 = resolver.resolve_value("network.retry_count").unwrap();
        assert_eq!(retry_count, 3);
    }

    #[test]
    fn test_debug_resolution_path() {
        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("network.timeout_ms", ConfigValue::Integer(30000))
            .build();

        let mut cli_args = HashMap::new();
        cli_args.insert("network.timeout_ms".to_string(), "60000".to_string());
        let cli_layer = CliArgsLayer::new().with_args(cli_args).build();

        let layers = vec![default_layer, cli_layer];
        let resolver = ConfigResolver::new(layers);
        let path = resolver.debug_resolution_path("network.timeout_ms");

        // Should show both sources, with CLI having higher precedence
        assert_eq!(path.len(), 2);
    }

    #[test]
    fn test_weight_based_precedence() {
        // Create a layer that comes later in order but has lower weight
        let later_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("test_key", ConfigValue::String("later_value".to_string()))
            .with_weight(1) // Lower weight
            .build();

        // Create a layer that comes earlier in order but has higher weight
        let earlier_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("test_key", ConfigValue::String("earlier_value".to_string()))
            .with_weight(10) // Higher weight
            .build();

        // Add layers in order where later_layer comes after earlier_layer
        let layers = vec![earlier_layer, later_layer];
        let resolver = ConfigResolver::new(layers);

        // Despite being later in order, the higher weight should win
        let result: String = resolver.resolve_value("test_key").unwrap();
        assert_eq!(result, "earlier_value");
    }

    #[test]
    #[cfg(feature = "environment-vars")]
    fn test_order_based_precedence_without_weights() {
        // Create layers without explicit weights
        let first_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("test_key", ConfigValue::String("first_value".to_string()))
            .build();

        let second_layer = InMemoryLayer::new(ConfigLayerSource::EnvironmentOverrides)
            .with_value("test_key", ConfigValue::String("second_value".to_string()))
            .build();

        let layers = vec![first_layer, second_layer];
        let resolver = ConfigResolver::new(layers);

        // Later layer (higher index) should win when no weights are specified
        let result: String = resolver.resolve_value("test_key").unwrap();
        assert_eq!(result, "second_value");
    }

    #[test]
    #[cfg(feature = "environment-vars")]
    fn test_performance_optimization_layers_sorted_once() {
        // Create multiple layers with weights in mixed order
        let layer1 = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("key1", ConfigValue::String("default".to_string()))
            .with_weight(1)
            .build();

        let layer2 = InMemoryLayer::new(ConfigLayerSource::CommandLineArgs)
            .with_value("key1", ConfigValue::String("user".to_string()))
            .with_value("key2", ConfigValue::String("user_only".to_string()))
            .with_weight(5)
            .build();

        let layer3 = InMemoryLayer::new(ConfigLayerSource::EnvironmentOverrides)
            .with_value("key1", ConfigValue::String("env".to_string()))
            .with_weight(3)
            .build();

        // Add in order: 1, 2, 3 but weight order should be: 1(1), 3(3), 2(5)
        let layers = vec![layer1, layer2, layer3];
        let resolver = ConfigResolver::new(layers);

        // Verify the internal layers are sorted by weight
        let sorted_layers = resolver.layers();
        assert_eq!(sorted_layers.len(), 3);

        // Should resolve in weight order: layer2(5) > layer3(3) > layer1(1)
        let key1_value: String = resolver.resolve_value("key1").unwrap();
        assert_eq!(key1_value, "user"); // layer2 has highest weight

        // Key that only exists in one layer should resolve correctly
        let key2_value: String = resolver.resolve_value("key2").unwrap();
        assert_eq!(key2_value, "user_only");

        // Verify that resolve_all_values works efficiently
        let all_values = resolver.resolve_all_values();
        assert_eq!(all_values.len(), 2);
        assert_eq!(
            all_values.get("key1").unwrap(),
            &ConfigValue::String("user".to_string())
        );
        assert_eq!(
            all_values.get("key2").unwrap(),
            &ConfigValue::String("user_only".to_string())
        );
    }

    #[test]
    fn test_resolve_all_values() {
        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("network.timeout_ms", ConfigValue::Integer(30000))
            .with_value("network.retry_count", ConfigValue::Integer(3))
            .build();

        let mut cli_args = HashMap::new();
        cli_args.insert("network.timeout_ms".to_string(), "60000".to_string());
        let cli_layer = CliArgsLayer::new().with_args(cli_args).build();

        let layers = vec![default_layer, cli_layer];
        let resolver = ConfigResolver::new(layers);
        let all_values = resolver.resolve_all_values();

        // Should contain values from both layers
        assert!(all_values.contains_key("network.timeout_ms"));
        assert!(all_values.contains_key("network.retry_count"));

        // CLI value should override default
        if let Some(ConfigValue::Integer(timeout)) = all_values.get("network.timeout_ms") {
            assert_eq!(*timeout, 60000);
        } else {
            all_values
                .get("network.timeout_ms")
                .expect("Expected timeout to be an integer");
        }
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_final_layer_blocks_inheritance() {
        // Create lower precedence layers with sensitive data
        let user_layer = InMemoryLayer::new(ConfigLayerSource::User(std::path::PathBuf::from(
            "/home/user/.rialoconfig",
        )))
        .with_value(
            "api_key",
            ConfigValue::String("secret-user-key".to_string()),
        )
        .with_value(
            "database.url",
            ConfigValue::String("user-database-url".to_string()),
        )
        .with_weight(2)
        .build();

        // Create a final layer that should block inheritance
        let test_layer = InMemoryLayer::new(ConfigLayerSource::ActiveEnvironment(
            std::path::PathBuf::from("/tmp/test-env"),
        ))
        .with_value("final", ConfigValue::Bool(true))
        .with_value("test_mode", ConfigValue::Bool(true))
        .with_value(
            "database.url",
            ConfigValue::String("test-database-url".to_string()),
        )
        .with_weight(5)
        .build();

        let layers = vec![user_layer, test_layer];
        let resolver = ConfigResolver::new(layers);

        // Values from final layer should be accessible
        let test_mode: bool = resolver.resolve_value("test_mode").unwrap();
        assert!(test_mode);

        let db_url: String = resolver.resolve_value("database.url").unwrap();
        assert_eq!(db_url, "test-database-url");

        // Values from lower layers should be blocked (return None)
        let api_key: Option<String> = resolver.resolve_value("api_key");
        assert!(
            api_key.is_none(),
            "API key should be blocked by final layer"
        );

        // resolve_all_values should only show values from final layer and above
        let all_values = resolver.resolve_all_values();
        assert!(all_values.contains_key("test_mode"));
        assert!(all_values.contains_key("database.url"));
        assert!(
            !all_values.contains_key("api_key"),
            "API key should not be in resolved values"
        );
        assert_eq!(
            all_values.get("database.url").unwrap(),
            &ConfigValue::String("test-database-url".to_string())
        );
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_multiple_final_layers_highest_wins() {
        // Create base layer with data
        let base_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("base_value", ConfigValue::String("base".to_string()))
            .with_weight(1)
            .build();

        // Create first final layer (lower precedence)
        let first_final =
            InMemoryLayer::new(ConfigLayerSource::User(std::path::PathBuf::from("/user")))
                .with_value("final", ConfigValue::Bool(true))
                .with_value(
                    "first_final_value",
                    ConfigValue::String("first".to_string()),
                )
                .with_weight(3)
                .build();

        // Create second final layer (higher precedence)
        let second_final = InMemoryLayer::new(ConfigLayerSource::ActiveEnvironment(
            std::path::PathBuf::from("/env"),
        ))
        .with_value("final", ConfigValue::Bool(true))
        .with_value(
            "second_final_value",
            ConfigValue::String("second".to_string()),
        )
        .with_weight(5)
        .build();

        // Create layer above second final
        let top_layer = InMemoryLayer::new(ConfigLayerSource::CommandLineArgs)
            .with_value("top_value", ConfigValue::String("top".to_string()))
            .with_weight(7)
            .build();

        let layers = vec![base_layer, first_final, second_final, top_layer];
        let resolver = ConfigResolver::new(layers);

        // Only values from the highest final layer and above should be accessible
        let top_value: String = resolver.resolve_value("top_value").unwrap();
        assert_eq!(top_value, "top");

        let second_final_value: String = resolver.resolve_value("second_final_value").unwrap();
        assert_eq!(second_final_value, "second");

        // Values from lower layers (including first final) should be blocked
        let first_final_value: Option<String> = resolver.resolve_value("first_final_value");
        assert!(
            first_final_value.is_none(),
            "First final layer should be blocked by second final layer"
        );

        let base_value: Option<String> = resolver.resolve_value("base_value");
        assert!(
            base_value.is_none(),
            "Base value should be blocked by final layers"
        );

        // Check resolve_all_values respects the highest final layer
        let all_values = resolver.resolve_all_values();
        assert!(all_values.contains_key("top_value"));
        assert!(all_values.contains_key("second_final_value"));
        assert!(!all_values.contains_key("first_final_value"));
        assert!(!all_values.contains_key("base_value"));
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_no_final_layer_normal_precedence() {
        // Create layers without any final=true
        let base_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("shared_key", ConfigValue::String("base_value".to_string()))
            .with_value("base_only", ConfigValue::String("base".to_string()))
            .with_weight(1)
            .build();

        let user_layer =
            InMemoryLayer::new(ConfigLayerSource::User(std::path::PathBuf::from("/user")))
                .with_value("shared_key", ConfigValue::String("user_value".to_string()))
                .with_value("user_only", ConfigValue::String("user".to_string()))
                .with_weight(3)
                .build();

        let cli_layer = InMemoryLayer::new(ConfigLayerSource::CommandLineArgs)
            .with_value("shared_key", ConfigValue::String("cli_value".to_string()))
            .with_weight(5)
            .build();

        let layers = vec![base_layer, user_layer, cli_layer];
        let resolver = ConfigResolver::new(layers);

        // Normal precedence should work (highest wins)
        let shared_key: String = resolver.resolve_value("shared_key").unwrap();
        assert_eq!(shared_key, "cli_value");

        // All layer values should be accessible
        let base_only: String = resolver.resolve_value("base_only").unwrap();
        assert_eq!(base_only, "base");

        let user_only: String = resolver.resolve_value("user_only").unwrap();
        assert_eq!(user_only, "user");

        // resolve_all_values should include values from all layers
        let all_values = resolver.resolve_all_values();
        assert!(all_values.contains_key("shared_key"));
        assert!(all_values.contains_key("base_only"));
        assert!(all_values.contains_key("user_only"));
        assert_eq!(
            all_values.get("shared_key").unwrap(),
            &ConfigValue::String("cli_value".to_string())
        );
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_final_layer_security_isolation() {
        // Simulate a scenario where user config has sensitive data
        let user_layer = InMemoryLayer::new(ConfigLayerSource::User(std::path::PathBuf::from(
            "/home/user/.rialoconfig",
        )))
        .with_value(
            "auth.token",
            ConfigValue::String("user-secret-token".to_string()),
        )
        .with_value(
            "auth.username",
            ConfigValue::String("real-user".to_string()),
        )
        .with_value(
            "database.production_url",
            ConfigValue::String("prod-db-connection".to_string()),
        )
        .with_weight(2)
        .build();

        // Test environment that should be isolated from user secrets
        let test_env_layer = InMemoryLayer::new(ConfigLayerSource::ActiveEnvironment(
            std::path::PathBuf::from("/tmp/test-env"),
        ))
        .with_value("final", ConfigValue::Bool(true))
        .with_value("test.enabled", ConfigValue::Bool(true))
        .with_value(
            "database.url",
            ConfigValue::String("test-db-connection".to_string()),
        )
        .with_value(
            "auth.username",
            ConfigValue::String("test-user".to_string()),
        )
        .with_weight(4)
        .build();

        let layers = vec![user_layer, test_env_layer];
        let resolver = ConfigResolver::new(layers);

        // Test environment should have its own safe values
        let test_enabled: bool = resolver.resolve_value("test.enabled").unwrap();
        assert!(test_enabled);

        let db_url: String = resolver.resolve_value("database.url").unwrap();
        assert_eq!(db_url, "test-db-connection");

        let username: String = resolver.resolve_value("auth.username").unwrap();
        assert_eq!(username, "test-user");

        // Sensitive user data should be completely blocked
        let user_token: Option<String> = resolver.resolve_value("auth.token");
        assert!(
            user_token.is_none(),
            "User auth token should not leak to test environment"
        );

        let prod_url: Option<String> = resolver.resolve_value("database.production_url");
        assert!(
            prod_url.is_none(),
            "Production database URL should not leak to test environment"
        );

        // Verify no sensitive data appears in all resolved values
        let all_values = resolver.resolve_all_values();
        assert!(
            !all_values.contains_key("auth.token"),
            "Auth token should not be accessible"
        );
        assert!(
            !all_values.contains_key("database.production_url"),
            "Production URL should not be accessible"
        );

        // But test-specific values should be present
        assert!(all_values.contains_key("test.enabled"));
        assert!(all_values.contains_key("database.url"));
        assert_eq!(
            all_values.get("database.url").unwrap(),
            &ConfigValue::String("test-db-connection".to_string())
        );
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_final_layer_with_nested_values() {
        // Base layer with nested configuration
        let base_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("app.name", ConfigValue::String("myapp".to_string()))
            .with_value("app.version", ConfigValue::String("1.0.0".to_string()))
            .with_value(
                "secrets.api_key",
                ConfigValue::String("prod-api-key".to_string()),
            )
            .with_weight(1)
            .build();

        // Final layer that should block access to secrets
        let demo_layer = InMemoryLayer::new(ConfigLayerSource::ActiveEnvironment(
            std::path::PathBuf::from("/demo"),
        ))
        .with_value("final", ConfigValue::Bool(true))
        .with_value("app.name", ConfigValue::String("demo-app".to_string()))
        .with_value("demo.mode", ConfigValue::Bool(true))
        .with_weight(3)
        .build();

        let layers = vec![base_layer, demo_layer];
        let resolver = ConfigResolver::new(layers);

        // App name should be overridden by demo layer
        let app_name: String = resolver.resolve_value("app.name").unwrap();
        assert_eq!(app_name, "demo-app");

        // Demo-specific value should be accessible
        let demo_mode: bool = resolver.resolve_value("demo.mode").unwrap();
        assert!(demo_mode);

        // Values from base layer that aren't overridden should be blocked
        let app_version: Option<String> = resolver.resolve_value("app.version");
        assert!(
            app_version.is_none(),
            "App version should be blocked by final layer"
        );

        let api_key: Option<String> = resolver.resolve_value("secrets.api_key");
        assert!(
            api_key.is_none(),
            "API key should be blocked by final layer"
        );

        // Verify nested path access is also blocked
        let all_values = resolver.resolve_all_values();
        assert!(!all_values.contains_key("app.version"));
        assert!(!all_values.contains_key("secrets.api_key"));
        assert!(all_values.contains_key("app.name"));
        assert!(all_values.contains_key("demo.mode"));
    }

    #[test]
    fn test_dot_notation_override_with_nested_structure() {
        // This test reproduces the issue where flat dot-notation keys from CLI overrides
        // don't properly override values in nested structures from other layers

        // Create a layer with nested structure (like defaults from TOML)
        let mut nested_values = IndexMap::new();
        let mut cdk_config = IndexMap::new();
        cdk_config.insert("verbose_mode".to_string(), ConfigValue::Bool(false));
        cdk_config.insert(
            "network".to_string(),
            ConfigValue::String("localnet".to_string()),
        );
        nested_values.insert("cdk".to_string(), ConfigValue::Object(cdk_config));

        let default_layer = ConfigLayer::new(ConfigLayerSource::Default, nested_values);

        // Create a layer with flat dot-notation keys (like CLI args)
        let mut cli_values = IndexMap::new();
        cli_values.insert("cdk.verbose_mode".to_string(), ConfigValue::Bool(true));
        cli_values.insert(
            "cdk.network".to_string(),
            ConfigValue::String("testnet".to_string()),
        );

        let cli_layer =
            ConfigLayer::new_with_weight(ConfigLayerSource::CommandLineArgs, cli_values, 10); // Higher weight = higher precedence

        let layers = vec![default_layer, cli_layer];
        let resolver = ConfigResolver::new(layers);

        // Individual value resolution should work (CLI overrides default)
        let verbose_mode: bool = resolver.resolve_value("cdk.verbose_mode").unwrap();
        assert!(verbose_mode, "CLI override should set verbose_mode to true");

        let network: String = resolver.resolve_value("cdk.network").unwrap();
        assert_eq!(
            network, "testnet",
            "CLI override should set network to testnet"
        );

        // resolve_all_values should also properly handle the overrides
        let all_values = resolver.resolve_all_values();

        // The resolved values should contain the CLI overrides, not the defaults
        if let Some(ConfigValue::Bool(verbose)) = all_values.get("cdk.verbose_mode") {
            assert!(
                *verbose,
                "resolve_all_values should show CLI override verbose_mode=true"
            );
        } else {
            all_values
                .get("cdk.verbose_mode")
                .expect("cdk.verbose_mode should be present and be a boolean");
        }

        if let Some(ConfigValue::String(net)) = all_values.get("cdk.network") {
            assert_eq!(
                net, "testnet",
                "resolve_all_values should show CLI override network=testnet"
            );
        } else {
            all_values
                .get("cdk.network")
                .expect("cdk.network should be present and be a string");
        }
    }
}
