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

//! # Rialo Modular Configuration
//!
//! A flexible layered configuration system.
//! The crate provides building blocks for creating configuration systems with
//! multiple layers that override each other based on precedence.
//!
//! ## Features
//!
//! - **Composable layers**: Build configuration from multiple sources with custom precedence
//! - **TOML file support**: Load configuration from TOML files with nested structure support
//! - **Environment variables**: Extract configuration from environment variables with prefix filtering
//! - **CLI arguments**: Override configuration with command-line arguments
//! - **In-memory layers**: Define default values or temporary overrides programmatically
//! - **Type-safe resolution**: Automatically deserialize configuration values to desired types
//! - **Security isolation**: Final layers block inheritance to prevent data leaks
//!
//! ## Example Usage
//!
//! Basic configuration with in-memory and CLI layers:
//!
//! ```rust
//! use rialo_modular_config::*;
//! use std::collections::HashMap;
//!
//! // Create a default configuration layer
//! let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
//!     .with_value("database.host", "localhost")
//!     .with_value("database.port", 5432i64)
//!     .with_value("server.timeout", 30i64)
//!     .build();
//!
//! // Add CLI argument overrides
//! let mut cli_args = HashMap::new();
//! cli_args.insert("server.timeout".to_string(), "60".to_string());
//! let cli_layer = CliArgsLayer::new()
//!     .with_args(cli_args)
//!     .build();
//!
//! // Build the final layered configuration
//! let config = LayeredConfigBuilder::new()
//!     .with_layer(default_layer)      // Lowest precedence
//!     .with_layer(cli_layer)          // Highest precedence
//!     .build()
//!     .unwrap();
//!
//! // Access configuration values with automatic type conversion
//! let db_host: String = config.get("database.host").unwrap();
//! let db_port: i64 = config.get("database.port").unwrap();
//! let timeout: i64 = config.get("server.timeout").unwrap(); // Will be 60 from CLI args
//! ```
//!
//! ## Layer-Aware Configuration Saving
//!
//! The configuration system intelligently handles saving values to the appropriate layers:
//! - **Read-only layers**: Environment variables, CLI arguments, in-memory defaults
//! - **Writable layers**: User, Project, and ActiveEnvironment TOML/JSON/YAML configuration files
//! - **Smart defaults**: Values save to the highest precedence writable layer by default
//! - **Layer targeting**: Specify exactly which layer should receive the saved value
//!
//! ## Typical Layer Precedence Order
//!
//! 1. **Default** (lowest) - In-memory defaults baked into the binary
//! 2. **EnvironmentBasic** - System environment variables (PATH, HOME, etc.)
//! 3. **User** - User-specific configuration files (~/.config/app/config.toml)
//! 4. **Project** - Project-specific configuration files (.rialoconfig.toml)
//! 5. **ActiveEnvironment** - Environment-specific configuration (dev/staging/prod)
//! 6. **EnvironmentOverrides** - Prefixed environment variables (APP_*)
//! 7. **CommandLineArgs** (highest) - Command-line argument overrides
//!
//! ## Security with Final Layers
//!
//! The configuration system supports "final" layers for security isolation. When a layer
//! contains `final = true`, it blocks all inheritance from lower-precedence layers. This
//! prevents sensitive data (like production API keys or database URLs) from accidentally
//! leaking into test or demo environments.
//!
//! ```rust
//! use rialo_modular_config::*;
//!
//! // Production layer with sensitive data
//! let prod_layer = InMemoryLayer::new(ConfigLayerSource::Default)
//!     .with_value("api_key", "prod-secret-key")
//!     .with_value("database.url", "prod-database")
//!     .build();
//!
//! // Test environment that should be isolated
//! let test_layer = InMemoryLayer::new(ConfigLayerSource::Custom("test-env".to_string()))
//!     .with_value("final", true)  // This blocks inheritance
//!     .with_value("database.url", "test-database")
//!     .build();
//!
//! let config = LayeredConfigBuilder::new()
//!     .with_layer(prod_layer)     // Lower precedence
//!     .with_layer(test_layer)     // Final layer
//!     .build()
//!     .unwrap();
//!
//! // Only test layer values are accessible
//! let db_url: String = config.get("database.url").unwrap(); // "test-database"
//! let api_key: Option<String> = config.get("api_key");      // None - blocked!
//! ```

mod builder;
mod layers;
mod resolver;
mod value;

use anyhow::Result;
pub use builder::LayeredConfigBuilder;
use indexmap::IndexMap;
#[cfg(feature = "environment-vars")]
pub use layers::EnvironmentLayer;
pub use layers::{CliArgsLayer, ConfigLayer, ConfigLayerSource, InMemoryLayer};
#[cfg(feature = "file-system")]
pub use layers::{FileFormat, FileLayer};
pub use resolver::ConfigResolver;
use serde::{de::DeserializeOwned, Serialize};
pub use value::ConfigValue;

/// Layered configuration system that resolves values from multiple sources
/// with proper precedence handling
#[derive(Debug)]
pub struct LayeredConfig {
    resolver: ConfigResolver,
}

impl LayeredConfig {
    /// Create a new layered configuration system (private - use builder)
    pub(crate) fn new(resolver: ConfigResolver) -> Self {
        Self { resolver }
    }

    /// Resolve a configuration value across all layers
    pub fn get<T>(&self, key: &str) -> Option<T>
    where
        T: DeserializeOwned,
    {
        self.resolver.resolve_value(key)
    }

    /// Get all resolved configuration as a single value map
    pub fn get_resolved_values(&self) -> IndexMap<String, ConfigValue> {
        self.resolver.resolve_all_values()
    }

    /// Get the layers in precedence order
    pub fn layers(&self) -> &[ConfigLayer] {
        self.resolver.layers()
    }

    /// Get all writable layers (file-based layers that can be modified)
    pub fn writable_layers(&self) -> Vec<&ConfigLayer> {
        self.resolver.writable_layers()
    }

    /// Get the preferred writable layer (highest precedence file layer for saving defaults)
    pub fn preferred_writable_layer(&self) -> Option<&ConfigLayer> {
        self.resolver.preferred_writable_layer()
    }

    /// Save a configuration value to the preferred writable layer (highest precedence file layer)
    #[cfg(feature = "file-system")]
    pub fn set<T>(&self, key: &str, value: T) -> Result<()>
    where
        T: Serialize,
    {
        let layer = self
            .preferred_writable_layer()
            .ok_or_else(|| anyhow::anyhow!("No writable configuration layers available"))?;

        // Use set_in_layer which includes validation
        self.set_in_layer(key, value, layer)
    }

    /// Save a configuration value (not available without file-system feature)
    #[cfg(not(feature = "file-system"))]
    pub fn set<T>(&self, _key: &str, _value: T) -> Result<()>
    where
        T: Serialize,
    {
        Err(anyhow::anyhow!(
            "Saving configuration requires the 'file-system' feature"
        ))
    }

    /// Find the first User layer (highest precedence)
    pub fn find_user_layer(&self) -> Option<&ConfigLayer> {
        self.resolver.writable_user_layer()
    }

    /// Find the first Project layer (highest precedence)
    pub fn find_project_layer(&self) -> Option<&ConfigLayer> {
        self.resolver.writable_project_layer()
    }

    /// Find the first ActiveEnvironment layer (highest precedence)  
    pub fn find_active_environment_layer(&self) -> Option<&ConfigLayer> {
        self.resolver.writable_active_environment_layer()
    }

    /// Find a writable layer matching the given source type
    pub fn find_layer_by_source(&self, source: &ConfigLayerSource) -> Option<&ConfigLayer> {
        self.resolver.writable_layer_by_source(source)
    }

    /// Validate the resolved configuration against the schema (requires "schema" feature)
    #[cfg(feature = "schema")]
    pub fn validate(&self) -> Result<()> {
        self.resolver.validate_resolved_config()
    }

    /// Save a configuration value to a specific layer
    ///
    /// This method will fail if the layer is not writable (e.g., environment variables, CLI args)
    #[cfg(feature = "file-system")]
    pub fn set_in_layer<T>(&self, key: &str, value: T, layer: &ConfigLayer) -> Result<()>
    where
        T: Serialize,
    {
        if !layer.is_writable() {
            return Err(anyhow::anyhow!(
                "Cannot write to read-only layer: {:?}",
                layer.source()
            ));
        }

        let file_path = layer
            .file_path()
            .ok_or_else(|| anyhow::anyhow!("Writable layer has no associated file path"))?;

        // Load existing config from the layer's file and backup original content for rollback
        let original_content = if file_path.exists() {
            Some(std::fs::read_to_string(file_path)?)
        } else {
            // Create parent directories if they don't exist
            if let Some(parent) = file_path.parent() {
                std::fs::create_dir_all(parent)?;
            }
            None
        };

        let mut config_data: IndexMap<String, ConfigValue> =
            if let Some(ref content) = original_content {
                let toml_value: toml::Value = toml::from_str(content)?;
                let config_value = ConfigValue::from_toml_value(toml_value);
                if let ConfigValue::Object(map) = config_value {
                    map
                } else {
                    IndexMap::new()
                }
            } else {
                IndexMap::new()
            };

        // Set the value
        let config_value = ConfigValue::from_serializable(value)?;
        self.set_nested_value(&mut config_data, key, config_value)?;

        // Write back to file as TOML
        let root_value = ConfigValue::Object(config_data);
        let toml_value = root_value.to_toml_value()?;
        let new_content = toml::to_string_pretty(&toml_value)?;
        std::fs::write(file_path, &new_content)?;

        // Run schema validation if available by reading the updated file content directly
        #[cfg(feature = "schema")]
        {
            // Create a temporary file layer that reads the fresh file content
            let validation_result = match layer.source() {
                ConfigLayerSource::User(path)
                | ConfigLayerSource::Project(path)
                | ConfigLayerSource::ActiveEnvironment(path) => {
                    // Create a fresh file layer that reads the updated content
                    let fresh_layer = FileLayer::new(path.clone())
                        .with_source(layer.source().clone())
                        .build();

                    match fresh_layer {
                        Ok(fresh_layer) => {
                            // Build temporary resolver with fresh layer to validate
                            let mut temp_layers = Vec::new();
                            let mut found_layer = false;

                            for existing_layer in self.resolver.layers() {
                                if existing_layer.source() == layer.source() {
                                    temp_layers.push(fresh_layer.clone());
                                    found_layer = true;
                                } else {
                                    temp_layers.push(existing_layer.clone());
                                }
                            }

                            // If we didn't find the layer to replace, add the fresh one
                            if !found_layer {
                                temp_layers.push(fresh_layer);
                            }

                            // Create temporary resolver with schema and validate
                            let temp_resolver =
                                ConfigResolver::new(temp_layers).with_schema_from(&self.resolver);
                            temp_resolver.validate_resolved_config()
                        }
                        Err(e) => Err(e),
                    }
                }
                _ => {
                    // For non-file layers, use the existing validation
                    self.validate()
                }
            };

            if let Err(validation_error) = validation_result {
                // Rollback the file to its previous state
                match original_content {
                    Some(original) => {
                        // Restore original content
                        if let Err(rollback_error) = std::fs::write(file_path, original) {
                            return Err(anyhow::anyhow!(
                                "Configuration validation failed after setting '{}': {}. Additionally, rollback failed: {}",
                                key,
                                validation_error,
                                rollback_error
                            ));
                        }
                    }
                    None => {
                        // File didn't exist before, remove it
                        if let Err(rollback_error) = std::fs::remove_file(file_path) {
                            return Err(anyhow::anyhow!(
                                "Configuration validation failed after setting '{}': {}. Additionally, cleanup failed: {}",
                                key,
                                validation_error,
                                rollback_error
                            ));
                        }
                    }
                }

                return Err(anyhow::anyhow!(
                    "Configuration validation failed after setting '{}': {}. Changes have been rolled back.",
                    key,
                    validation_error
                ));
            }
        }

        Ok(())
    }

    /// Save a configuration value to a specific layer (not available without file-system feature)
    #[cfg(not(feature = "file-system"))]
    pub fn set_in_layer<T>(&self, _key: &str, _value: T, _layer: &ConfigLayer) -> Result<()>
    where
        T: Serialize,
    {
        Err(anyhow::anyhow!(
            "Layer-specific saving requires the 'file-system' feature"
        ))
    }

    #[cfg(feature = "file-system")]
    fn set_nested_value(
        &self,
        config: &mut IndexMap<String, ConfigValue>,
        key: &str,
        value: ConfigValue,
    ) -> Result<()> {
        let parts: Vec<&str> = key.split('.').collect();

        if parts.len() == 1 {
            // Simple key, no nesting
            config.insert(key.to_string(), value);
            return Ok(());
        }

        // Navigate/create nested structure
        let mut current = config;
        let last_key = parts[parts.len() - 1];

        for &part in &parts[..parts.len() - 1] {
            let entry = current
                .entry(part.to_string())
                .or_insert_with(|| ConfigValue::Object(IndexMap::new()));

            match entry {
                ConfigValue::Object(ref mut map) => {
                    current = map;
                }
                _ => {
                    return Err(anyhow::anyhow!(
                        "Cannot create nested path '{}': '{}' is not an object",
                        key,
                        part
                    ));
                }
            }
        }

        // Set the final value
        current.insert(last_key.to_string(), value);
        Ok(())
    }
}

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

    use tempfile::TempDir;

    use super::*;

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

        // Create CLI override layer
        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();

        // Build config with layers
        let config = LayeredConfigBuilder::new()
            .with_layer(default_layer)
            .with_layer(cli_layer)
            .build()
            .unwrap();

        // Test value resolution - CLI should override default
        let timeout: i64 = config.get("network.timeout_ms").unwrap();
        assert_eq!(timeout, 60000);

        // Default value should still be available
        let retry_count: i64 = config.get("network.retry_count").unwrap();
        assert_eq!(retry_count, 3);
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_config_set_to_layer() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("test.toml");

        // Create a config with a writable file layer
        let file_layer = FileLayer::new(config_path.clone())
            .with_source(ConfigLayerSource::User(config_path.clone()))
            .required(false) // File doesn't need to exist initially
            .build()
            .unwrap();

        let config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .build()
            .unwrap();

        // Set a simple value using set_in_layer
        let layer = config.find_user_layer().unwrap();
        config.set_in_layer("simple_value", 42i64, layer).unwrap();

        // Verify the file was created and contains the value
        let content = fs::read_to_string(&config_path).unwrap();
        assert!(content.contains("simple_value = 42"));

        // Create new config and load from file
        let file_layer = FileLayer::new(config_path).build().unwrap();
        let new_config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .build()
            .unwrap();

        let value: i64 = new_config.get("simple_value").unwrap();
        assert_eq!(value, 42);
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_config_set_nested_values() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("nested_test.toml");

        // Create a config with a writable file layer
        let file_layer = FileLayer::new(config_path.clone())
            .with_source(ConfigLayerSource::User(config_path.clone()))
            .required(false)
            .build()
            .unwrap();

        let config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .build()
            .unwrap();

        let layer = config.find_user_layer().unwrap();

        // Set nested values
        config
            .set_in_layer("database.host", "localhost", layer)
            .unwrap();
        config
            .set_in_layer("database.port", 5432i64, layer)
            .unwrap();
        config.set_in_layer("server.timeout", 30i64, layer).unwrap();
        config.set_in_layer("debug", true, layer).unwrap();

        // Verify the file contains proper TOML structure
        let content = fs::read_to_string(&config_path).unwrap();
        println!("Generated TOML:\n{}", content);

        // Should contain proper nested structure
        assert!(content.contains("[database]"));
        assert!(content.contains("host = \"localhost\""));
        assert!(content.contains("port = 5432"));
        assert!(content.contains("[server]"));
        assert!(content.contains("timeout = 30"));
        assert!(content.contains("debug = true"));

        // Create new config and verify values can be read back
        let file_layer = FileLayer::new(config_path).build().unwrap();
        let new_config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .build()
            .unwrap();

        let host: String = new_config.get("database.host").unwrap();
        assert_eq!(host, "localhost");

        let port: i64 = new_config.get("database.port").unwrap();
        assert_eq!(port, 5432);

        let timeout: i64 = new_config.get("server.timeout").unwrap();
        assert_eq!(timeout, 30);

        let debug: bool = new_config.get("debug").unwrap();
        assert!(debug);
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_config_set_nested_conflict() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("conflict_test.toml");

        let file_layer = FileLayer::new(config_path.clone())
            .with_source(ConfigLayerSource::User(config_path.clone()))
            .required(false)
            .build()
            .unwrap();

        let config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .build()
            .unwrap();

        let layer = config.find_user_layer().unwrap();

        // Set a simple value first
        config
            .set_in_layer("server", "simple_value", layer)
            .unwrap();

        // Try to set a nested value that conflicts - should fail
        let result = config.set_in_layer("server.timeout", 30i64, layer);
        assert!(result.is_err());
        let err_msg = result.unwrap_err().to_string();
        assert!(err_msg.contains("Cannot create nested path"));
        assert!(err_msg.contains("server"));
        assert!(err_msg.contains("is not an object"));
    }

    #[test]
    #[cfg(feature = "environment-vars")]
    fn test_environment_layer() {
        // This test would need to set environment variables
        // For now, just test that the layer can be created
        let env_layer = EnvironmentLayer::new()
            .with_prefix("TEST_")
            .with_source(ConfigLayerSource::EnvironmentOverrides)
            .build();

        let _config = LayeredConfigBuilder::new()
            .with_layer(env_layer)
            .build()
            .unwrap();
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_file_layer_optional() {
        let temp_dir = TempDir::new().unwrap();
        let nonexistent_path = temp_dir.path().join("nonexistent.toml");

        // Should not fail when file doesn't exist and not required
        let file_layer = FileLayer::new(nonexistent_path)
            .required(false)
            .build()
            .unwrap();

        let config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .build()
            .unwrap();

        // Should have no values
        let all_values = config.get_resolved_values();
        assert!(all_values.is_empty());
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_json_file_layer() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.json");

        // Write a JSON config file
        let json_content = r#"{
            "database": {
                "host": "localhost",
                "port": 5432
            },
            "debug": true
        }"#;
        fs::write(&config_path, json_content).unwrap();

        // Load it with FileLayer
        let file_layer = FileLayer::new(config_path).build().unwrap();
        let config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .build()
            .unwrap();

        let host: String = config.get("database.host").unwrap();
        assert_eq!(host, "localhost");

        let port: i64 = config.get("database.port").unwrap();
        assert_eq!(port, 5432);

        let debug: bool = config.get("debug").unwrap();
        assert!(debug);
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_layered_saving() {
        let temp_dir = TempDir::new().unwrap();
        let user_config_path = temp_dir.path().join("user.toml");
        let env_config_path = temp_dir.path().join("env.toml");

        // Create initial user config
        fs::write(&user_config_path, "user_key = \"user_value\"").unwrap();

        // Create config with multiple layers
        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("default_key", "default_value")
            .build();

        let user_layer = FileLayer::new(user_config_path.clone()).build().unwrap();

        let env_layer = FileLayer::new(env_config_path.clone())
            .with_source(ConfigLayerSource::ActiveEnvironment(
                env_config_path.clone(),
            ))
            .with_weight(10) // Higher precedence
            .required(false)
            .build()
            .unwrap();

        #[cfg(feature = "environment-vars")]
        let env_vars = EnvironmentLayer::new().with_prefix("TEST_").build();
        #[cfg(not(feature = "environment-vars"))]
        let env_vars =
            InMemoryLayer::new(ConfigLayerSource::Custom("env_vars".to_string())).build();

        let config = LayeredConfigBuilder::new()
            .with_layer(default_layer)
            .with_layer(user_layer)
            .with_layer(env_layer)
            .with_layer(env_vars)
            .build()
            .unwrap();

        // Test writable layer detection
        let writable_layers = config.writable_layers();
        assert_eq!(writable_layers.len(), 2); // user and env config files

        // Test preferred writable layer (should be env due to higher weight)
        let preferred = config.preferred_writable_layer().unwrap();
        assert!(matches!(
            preferred.source(),
            ConfigLayerSource::ActiveEnvironment(_)
        ));

        // Test saving to preferred layer
        config.set("new_key", "new_value").unwrap();

        // Verify it was saved to the env config file (higher precedence)
        let env_content = fs::read_to_string(&env_config_path).unwrap();
        assert!(env_content.contains("new_key = \"new_value\""));

        // Test saving to specific layer source
        let user_layer = config.find_user_layer().unwrap();
        config
            .set_in_layer("user_specific", "user_value", user_layer)
            .unwrap();

        // Verify it was saved to user config
        let user_content = fs::read_to_string(&user_config_path).unwrap();
        assert!(user_content.contains("user_specific = \"user_value\""));
        assert!(user_content.contains("user_key = \"user_value\"")); // Original content preserved

        // Test that non-file layers are not writable
        for layer in config.layers() {
            match layer.source() {
                ConfigLayerSource::Default => {
                    assert!(!layer.is_writable());
                }
                #[cfg(feature = "environment-vars")]
                ConfigLayerSource::EnvironmentOverrides => {
                    assert!(!layer.is_writable());
                }
                #[cfg(feature = "file-system")]
                ConfigLayerSource::User(_)
                | ConfigLayerSource::Project(_)
                | ConfigLayerSource::ActiveEnvironment(_) => {
                    assert!(layer.is_writable());
                }
                _ => {}
            }
        }

        // Test that trying to write to read-only layers fails
        for layer in config.layers() {
            if !layer.is_writable() {
                let result = config.set_in_layer("test_key", "test_value", layer);
                assert!(result.is_err());
                let err_msg = result.unwrap_err().to_string();
                assert!(err_msg.contains("Cannot write to read-only layer"));
            }
        }
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_project_layer() {
        let temp_dir = TempDir::new().unwrap();
        let project_config_path = temp_dir.path().join(".rialoconfig.toml");
        let user_config_path = temp_dir.path().join("user.toml");

        // Create initial project config
        fs::write(&project_config_path, "project_setting = \"project_value\"").unwrap();

        // Create layers with project config
        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("default_key", "default_value")
            .build();

        let user_layer = FileLayer::new(user_config_path.clone())
            .required(false)
            .build()
            .unwrap();

        let project_layer = FileLayer::new(project_config_path.clone())
            .with_source(ConfigLayerSource::Project(project_config_path.clone()))
            .build()
            .unwrap();

        let config = LayeredConfigBuilder::new()
            .with_layer(default_layer)
            .with_layer(user_layer)
            .with_layer(project_layer) // Should have higher precedence than user
            .build()
            .unwrap();

        // Test that we can find the project layer
        let project_layer = config.find_project_layer().unwrap();
        assert!(matches!(
            project_layer.source(),
            ConfigLayerSource::Project(_)
        ));

        // Test saving to project layer
        config
            .set_in_layer("new_project_setting", "new_value", project_layer)
            .unwrap();

        // Verify it was saved to the project config file
        let project_content = fs::read_to_string(&project_config_path).unwrap();
        assert!(project_content.contains("new_project_setting = \"new_value\""));
        assert!(project_content.contains("project_setting = \"project_value\"")); // Original content preserved

        // Test that project settings are accessible
        let project_setting: String = config.get("project_setting").unwrap();
        assert_eq!(project_setting, "project_value");
    }

    #[test]
    #[cfg(feature = "file-system")]
    fn test_yaml_file_layer() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("config.yaml");

        // Write a YAML config file
        let yaml_content = r#"
database:
  host: localhost
  port: 5432
debug: true
features:
  - auth
  - logging
"#;
        fs::write(&config_path, yaml_content).unwrap();

        // Load it with FileLayer
        let file_layer = FileLayer::new(config_path).build().unwrap();
        let config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .build()
            .unwrap();

        let host: String = config.get("database.host").unwrap();
        assert_eq!(host, "localhost");

        let debug: bool = config.get("debug").unwrap();
        assert!(debug);

        // Test array access
        let all_values = config.get_resolved_values();
        if let Some(ConfigValue::Array(features)) = all_values.get("features") {
            assert_eq!(features.len(), 2);
        } else {
            panic!("Expected features to be an array");
        }
    }

    #[cfg(feature = "schema")]
    #[test]
    fn test_schema_validation_success() {
        use serde_json::json;

        // Define a schema that requires certain properties
        let schema = json!({
            "type": "object",
            "properties": {
                "database": {
                    "type": "object",
                    "properties": {
                        "host": { "type": "string" },
                        "port": { "type": "integer", "minimum": 1, "maximum": 65535 }
                    },
                    "required": ["host", "port"]
                },
                "debug": { "type": "boolean" }
            },
            "required": ["database"]
        });

        // Create layers that conform to the schema
        // Note: Using nested object structure instead of flat keys
        let mut database = IndexMap::new();
        database.insert(
            "host".to_string(),
            ConfigValue::String("localhost".to_string()),
        );
        database.insert("port".to_string(), ConfigValue::Integer(5432));

        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("database", ConfigValue::Object(database))
            .with_value("debug", false)
            .build();

        let override_layer = InMemoryLayer::new(ConfigLayerSource::CommandLineArgs)
            .with_value("debug", true)
            .build();

        // Build config with schema validation
        let config = LayeredConfigBuilder::new()
            .with_layer(default_layer)
            .with_layer(override_layer)
            .with_schema(schema)
            .build()
            .unwrap();

        // Validation should pass
        config.validate().unwrap();

        // Values should be accessible
        let host: String = config.get("database.host").unwrap();
        assert_eq!(host, "localhost");

        let port: i64 = config.get("database.port").unwrap();
        assert_eq!(port, 5432);

        let debug: bool = config.get("debug").unwrap();
        assert!(debug); // Overridden by CLI args
    }

    #[cfg(feature = "schema")]
    #[test]
    fn test_schema_validation_failure() {
        use serde_json::json;

        // Define a strict schema
        let schema = json!({
            "type": "object",
            "properties": {
                "database": {
                    "type": "object",
                    "properties": {
                        "host": { "type": "string" },
                        "port": { "type": "integer", "minimum": 1, "maximum": 65535 }
                    },
                    "required": ["host", "port"]
                }
            },
            "required": ["database"]
        });

        // Create a layer with invalid data (string instead of integer for port)
        let invalid_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("database.host", "localhost")
            .with_value("database.port", "invalid_port") // String instead of integer
            .build();

        // Building with schema should fail
        let result = LayeredConfigBuilder::new()
            .with_layer(invalid_layer)
            .with_schema(schema)
            .build();

        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();
        assert!(error_msg.contains("Schema validation failed"));
    }

    #[cfg(feature = "schema")]
    #[test]
    fn test_schema_validation_missing_required_field() {
        use serde_json::json;

        let schema = json!({
            "type": "object",
            "properties": {
                "database": {
                    "type": "object",
                    "properties": {
                        "host": { "type": "string" },
                        "port": { "type": "integer" }
                    },
                    "required": ["host", "port"]
                }
            },
            "required": ["database"]
        });

        // Create layer missing required port field
        let incomplete_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("database.host", "localhost")
            // Missing database.port
            .build();

        // Should fail validation due to missing required field
        let result = LayeredConfigBuilder::new()
            .with_layer(incomplete_layer)
            .with_schema(schema)
            .build();

        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();
        assert!(error_msg.contains("Schema validation failed"));
    }

    #[cfg(all(feature = "schema", feature = "file-system"))]
    #[test]
    fn test_schema_validation_with_final_layers() {
        use serde_json::json;

        let schema = json!({
            "type": "object",
            "properties": {
                "app_name": { "type": "string" },
                "test_mode": { "type": "boolean" },
                "database": {
                    "type": "object",
                    "properties": {
                        "url": { "type": "string" }
                    }
                }
            },
            "required": ["app_name"]
        });

        // User layer with sensitive production data
        let user_layer = InMemoryLayer::new(ConfigLayerSource::User("/home/user/.config".into()))
            .with_value("app_name", "MyApp")
            .with_value("api_key", "secret-production-key") // This will be blocked
            .build();

        // Final test layer that blocks inheritance
        let test_layer =
            InMemoryLayer::new(ConfigLayerSource::ActiveEnvironment("/tmp/test".into()))
                .with_value("final", true)
                .with_value("app_name", "MyApp-Test")
                .with_value("test_mode", true)
                .with_value("database.url", "test-db-url")
                .build();

        // Should validate successfully - only final layer values are considered
        let config = LayeredConfigBuilder::new()
            .with_layer(user_layer)
            .with_layer(test_layer)
            .with_schema(schema)
            .build()
            .unwrap();

        // Validation should pass for resolved config
        config.validate().unwrap();

        // Should only have access to test layer values
        let app_name: String = config.get("app_name").unwrap();
        assert_eq!(app_name, "MyApp-Test");

        let test_mode: bool = config.get("test_mode").unwrap();
        assert!(test_mode);

        // Sensitive data should be blocked
        let api_key: Option<String> = config.get("api_key");
        assert!(api_key.is_none());
    }

    #[cfg(all(feature = "schema", feature = "environment-vars"))]
    #[test]
    fn test_schema_validation_empty_layers() {
        use serde_json::json;

        let schema = json!({
            "type": "object",
            "properties": {
                "optional_field": { "type": "string" }
            }
        });

        // Create config with empty layers
        let empty_layer = InMemoryLayer::new(ConfigLayerSource::Default).build();
        let env_layer = EnvironmentLayer::new().with_prefix("NONEXISTENT_").build();

        // Should build successfully with empty layers
        let config = LayeredConfigBuilder::new()
            .with_layer(empty_layer)
            .with_layer(env_layer)
            .with_schema(schema)
            .build()
            .unwrap();

        // Empty config should validate
        config.validate().unwrap();

        // Should have no values
        let all_values = config.get_resolved_values();
        assert!(all_values.is_empty());
    }

    #[cfg(all(feature = "schema", feature = "file-system"))]
    #[test]
    fn test_schema_validation_detailed_error_reporting() {
        use serde_json::json;

        let schema = json!({
            "type": "object",
            "properties": {
                "database": {
                    "type": "object",
                    "properties": {
                        "host": { "type": "string" },
                        "port": { "type": "integer", "minimum": 1, "maximum": 65535 }
                    },
                    "required": ["host", "port"]
                },
                "app_name": { "type": "string" }
            },
            "required": ["database", "app_name"]
        });

        // Create layers with some valid and some invalid data
        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("app_name", "MyApp")
            .build();

        let user_layer = InMemoryLayer::new(ConfigLayerSource::User("/home/user/.config".into()))
            .with_value("database.host", "localhost") // Valid
            .build();

        let cli_layer = InMemoryLayer::new(ConfigLayerSource::CommandLineArgs)
            .with_value("database.port", "not_a_number") // Invalid - should be integer
            .build();

        // Building with schema should fail with detailed error info
        let result = LayeredConfigBuilder::new()
            .with_layer(default_layer)
            .with_layer(user_layer)
            .with_layer(cli_layer)
            .with_schema(schema)
            .build();

        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();

        // Should show which layers contributed to the error
        println!("Detailed error message:\n{}", error_msg);

        assert!(error_msg.contains("Schema validation failed"));
        // Should mention contributing layers
        assert!(error_msg.contains("Contributing layers") || error_msg.contains("CommandLineArgs"));
    }

    #[cfg(all(feature = "schema", feature = "file-system"))]
    #[test]
    fn test_schema_validation_missing_field_with_layer_info() {
        use serde_json::json;

        let schema = json!({
            "type": "object",
            "properties": {
                "required_field": { "type": "string" }
            },
            "required": ["required_field"]
        });

        // Create layers that don't provide the required field
        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("optional_field", "some_value")
            .build();

        let user_layer = InMemoryLayer::new(ConfigLayerSource::User("/user/config".into()))
            .with_value("another_field", "another_value")
            .build();

        // Should fail validation and show which layers were involved
        let result = LayeredConfigBuilder::new()
            .with_layer(default_layer)
            .with_layer(user_layer)
            .with_schema(schema)
            .build();

        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();

        println!("Missing field error message:\n{}", error_msg);

        assert!(error_msg.contains("Schema validation failed"));
        assert!(error_msg.contains("required_field") || error_msg.contains("required property"));
        // Should show contributing layers (even though they don't have the required field)
        assert!(error_msg.contains("Contributing layers") || error_msg.contains("Default"));
    }

    #[cfg(all(feature = "schema", feature = "file-system"))]
    #[test]
    fn test_schema_validation_specific_field_error_reporting() {
        use serde_json::json;

        let schema = json!({
            "type": "object",
            "properties": {
                "database": {
                    "type": "object",
                    "properties": {
                        "host": { "type": "string" },
                        "port": { "type": "integer", "minimum": 1, "maximum": 65535 }
                    },
                    "required": ["host", "port"]
                }
            },
            "required": ["database"]
        });

        // Create layers where we can trace specific field contributions
        let default_layer = InMemoryLayer::new(ConfigLayerSource::Default)
            .with_value("database.host", "localhost") // This layer provides host
            .build();

        let user_layer =
            InMemoryLayer::new(ConfigLayerSource::User("/home/user/.rialoconfig".into()))
                .with_value("database.port", 999999) // This layer provides invalid port (too high)
                .build();

        // Should fail validation with detailed layer information
        let result = LayeredConfigBuilder::new()
            .with_layer(default_layer)
            .with_layer(user_layer)
            .with_schema(schema)
            .build();

        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();

        println!("Field-specific error message:\n{}", error_msg);

        assert!(error_msg.contains("Schema validation failed"));
        // Should show that both layers contributed to the database configuration
        assert!(error_msg.contains("Contributing layers"));
        assert!(error_msg.contains("Default") && error_msg.contains("User"));
    }

    #[cfg(all(feature = "schema", feature = "file-system"))]
    #[test]
    fn test_continuous_validation_on_set_operations() {
        use serde_json::json;

        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("test_validation.toml");

        // Start with valid initial data that satisfies the schema
        std::fs::write(
            &config_path,
            r#"
[database]
host = "initial-host"
port = 3000
"#,
        )
        .unwrap();

        // Define a schema that requires database config to be valid
        let schema = json!({
            "type": "object",
            "properties": {
                "database": {
                    "type": "object",
                    "properties": {
                        "host": { "type": "string" },
                        "port": { "type": "integer", "minimum": 1, "maximum": 65535 }
                    },
                    "required": ["host", "port"]
                }
            },
            "required": ["database"]
        });

        // Create configuration with schema validation
        let file_layer = FileLayer::new(config_path.clone())
            .with_source(ConfigLayerSource::User(config_path.clone()))
            .build()
            .unwrap();

        let config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .with_schema(schema)
            .build()
            .unwrap();

        let layer = config.find_user_layer().unwrap();

        // Test 1: Set valid values - should succeed
        config
            .set_in_layer("database.host", "localhost", layer)
            .unwrap();
        config
            .set_in_layer("database.port", 5432i64, layer)
            .unwrap();

        // Verify file was written
        let content = std::fs::read_to_string(&config_path).unwrap();
        assert!(content.contains("host = \"localhost\""));
        assert!(content.contains("port = 5432"));

        // Test 2: Try to set invalid value - should fail and rollback
        let result = config.set_in_layer("database.port", 999999i64, layer);
        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();
        assert!(error_msg.contains("Configuration validation failed"));
        assert!(error_msg.contains("Changes have been rolled back"));

        // Verify file was rolled back - should still have valid port
        let content = std::fs::read_to_string(&config_path).unwrap();
        assert!(content.contains("port = 5432")); // Original valid value
        assert!(!content.contains("port = 999999")); // Invalid value should not be present

        // Test 3: Try to set invalid type - should fail and rollback
        let result = config.set_in_layer("database.port", "not_a_number", layer);
        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();
        assert!(error_msg.contains("Configuration validation failed"));

        // Verify file still contains valid configuration
        let content = std::fs::read_to_string(&config_path).unwrap();
        assert!(content.contains("host = \"localhost\""));
        assert!(content.contains("port = 5432"));

        // Test 4: Using the convenience set() method should also validate
        let result = config.set("database.host", 12345i64); // Invalid type for host
        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();
        assert!(error_msg.contains("Configuration validation failed"));

        // Verify host is still the original value
        let content = std::fs::read_to_string(&config_path).unwrap();
        assert!(content.contains("host = \"localhost\"")); // Original valid value
    }

    #[cfg(all(feature = "schema", feature = "file-system"))]
    #[test]
    fn test_continuous_validation_rollback_new_file() {
        use serde_json::json;

        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("new_file_test.toml");

        // Start with a valid initial configuration file
        std::fs::write(&config_path, r#"required_field = "initial_value""#).unwrap();

        // Define a schema that requires certain properties
        let schema = json!({
            "type": "object",
            "properties": {
                "required_field": { "type": "string" },
                "optional_field": { "type": "string" }
            },
            "required": ["required_field"]
        });

        // Create config with the file
        let file_layer = FileLayer::new(config_path.clone())
            .with_source(ConfigLayerSource::User(config_path.clone()))
            .build()
            .unwrap();

        let config = LayeredConfigBuilder::new()
            .with_layer(file_layer)
            .with_schema(schema)
            .build()
            .unwrap();

        let layer = config.find_user_layer().unwrap();

        // File should exist initially with valid content
        assert!(config_path.exists());

        // Test: Try to set a value that would make the config invalid (remove required field)
        // We'll test by setting a value that causes validation to fail due to type mismatch
        let result = config.set_in_layer("required_field", 12345i64, layer); // Should be string, not integer
        assert!(result.is_err());
        let error_msg = result.unwrap_err().to_string();
        assert!(error_msg.contains("Configuration validation failed"));
        assert!(error_msg.contains("Changes have been rolled back"));

        // File should have been rolled back to the original valid content
        let content = std::fs::read_to_string(&config_path).unwrap();
        assert!(content.contains("required_field = \"initial_value\"")); // Original value restored
        assert!(!content.contains("required_field = 12345")); // Invalid value should not be present

        // Now try setting a valid configuration - should succeed
        config
            .set_in_layer("required_field", "valid_value", layer)
            .unwrap();
        config
            .set_in_layer("optional_field", "optional_value", layer)
            .unwrap();

        // File should now contain the valid updates
        let content = std::fs::read_to_string(&config_path).unwrap();
        assert!(content.contains("required_field = \"valid_value\""));
        assert!(content.contains("optional_field = \"optional_value\""));
    }
}
