//! Configuration System Traits
//!
//! This module provides trait-based abstractions for configuration management
//! in Rustelo applications. It supports layered configuration resolution,
//! WASM constant generation, and extensible configuration sources.
//!
//! # Architecture
//!
//! The configuration system follows a trait-based design with these core
//! concepts:
//!
//! - **ConfigResolver**: Core resolution capability
//! - **LayeredResolver**: Hierarchical configuration with precedence
//! - **WasmConstantGenerator**: Build-time constant generation for WASM
//! - **BuildTimeResolver**: Build process integration
//! - **ConfigSource**: Pluggable configuration sources
//!
//! # Example Usage
//!
//! ```rust
//! use rustelo_core_types::traits_config::*;
//! use std::path::PathBuf;
//!
//! // Use default implementation
//! let mut resolver = DefaultConfigResolver::new(PathBuf::from("."))?;
//! let config = resolver.resolve()?;
//!
//! // Custom resolver
//! struct MyResolver;
//! impl ConfigResolver for MyResolver {
//!     type Config = MyAppConfig;
//!     type Error = MyError;
//!
//!     fn resolve(&mut self) -> Result<Self::Config, Self::Error> {
//!         // Custom resolution logic
//!     }
//! }
//! ```

use std::any::Any;
use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt;
use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

/// Core configuration resolution trait
///
/// Provides the fundamental capability to resolve configuration from various
/// sources. Implementations can combine TOML files, environment variables,
/// databases, etc.
pub trait ConfigResolver {
    /// The configuration type this resolver produces
    type Config: Clone + Send + Sync + 'static;

    /// Error type for resolution failures
    type Error: StdError + Send + Sync + 'static;

    /// Resolve configuration from all available sources
    ///
    /// This method should apply the full resolution hierarchy and return
    /// a complete configuration object.
    fn resolve(&mut self) -> Result<Self::Config, Self::Error>;

    /// Resolve a specific configuration section
    ///
    /// Useful for getting partial configurations or specific subsections.
    fn resolve_section<T>(&mut self, section: &str) -> Result<T, Self::Error>
    where
        T: for<'de> Deserialize<'de> + Clone + Send + Sync + 'static;

    /// Check if configuration has changed since last resolution
    ///
    /// Useful for hot-reloading and caching decisions.
    fn has_changed(&self) -> bool {
        false // Default: assume no change tracking
    }

    /// Clear any cached configuration state
    fn clear_cache(&mut self) {
        // Default: no-op
    }
}

/// WASM constant generation capability
///
/// Generates compile-time constants from resolved configuration for use in WASM
/// builds. This solves the problem of runtime configuration access in WASM
/// environments.
pub trait WasmConstantGenerator {
    type Config;

    /// Generate WASM-compatible constants from configuration
    ///
    /// Extracts compile-time constants from the resolved configuration
    /// that can be safely used in WASM environments.
    fn generate_constants(&self, config: &Self::Config) -> WasmConstants;

    /// Write constants to a Rust file for inclusion in builds
    ///
    /// Generates a .rs file that can be included via include!() macro,
    /// providing const values for WASM builds.
    fn write_constants_file(
        &self,
        constants: &WasmConstants,
        out_path: &Path,
    ) -> Result<(), Box<dyn StdError>>;

    /// Generate convenience getter functions
    ///
    /// Creates helper functions for accessing constants in a type-safe way.
    fn generate_getters(&self, constants: &WasmConstants) -> String {
        let mut code = String::new();

        // String constants getters
        for name in constants.string_constants.keys() {
            code.push_str(&format!(
                "/// Get {} constant\npub fn get_{}() -> &'static str {{\n    {}\n}}\n\n",
                name.to_lowercase(),
                name.to_lowercase(),
                name
            ));
        }

        // Numeric constants getters
        for name in constants.numeric_constants.keys() {
            code.push_str(&format!(
                "/// Get {} constant\npub fn get_{}() -> i64 {{\n    {}\n}}\n\n",
                name.to_lowercase(),
                name.to_lowercase(),
                name
            ));
        }

        // Boolean constants getters
        for name in constants.boolean_constants.keys() {
            code.push_str(&format!(
                "/// Get {} constant\npub fn get_{}() -> bool {{\n    {}\n}}\n\n",
                name.to_lowercase(),
                name.to_lowercase(),
                name
            ));
        }

        code
    }
}

/// Layered configuration resolution with hierarchical precedence
///
/// Extends basic configuration resolution with support for configuration layers
/// that can override each other based on precedence rules.
pub trait LayeredResolver: ConfigResolver {
    /// Add a configuration layer with specific precedence
    ///
    /// Layers with higher precedence values override those with lower values.
    fn add_layer(&mut self, layer: ConfigLayer) -> Result<(), Self::Error>;

    /// Remove a configuration layer
    fn remove_layer(&mut self, layer_id: &str) -> Result<(), Self::Error>;

    /// Clear all layers and rediscover from filesystem/environment
    fn refresh_layers(&mut self) -> Result<(), Self::Error>;

    /// Get current layer hierarchy (ordered by precedence)
    fn get_layers(&self) -> &[ConfigLayer];

    /// Get configuration from a specific layer
    fn resolve_layer<T>(&mut self, layer_id: &str) -> Result<Option<T>, Self::Error>
    where
        T: for<'de> Deserialize<'de> + Clone + Send + Sync + 'static;
}

/// Build-time configuration resolution
///
/// Provides capabilities needed during the build process, including static
/// resolution and build artifact generation.
pub trait BuildTimeResolver {
    type Config: Clone + Send + Sync + 'static;
    type Error: StdError + Send + Sync + 'static;

    /// Resolve configuration during build process
    ///
    /// This is called from build.rs scripts to get configuration that
    /// can be embedded into the binary.
    fn resolve_for_build(project_root: &Path) -> Result<Self::Config, Self::Error>;

    /// Generate build artifacts from resolved configuration
    ///
    /// Creates files, constants, and other build-time artifacts needed
    /// for the application to function.
    fn generate_build_artifacts(
        config: &Self::Config,
        out_dir: &Path,
    ) -> Result<(), Box<dyn StdError>>;

    /// Get list of files that should trigger rebuild when changed
    fn get_watch_files(project_root: &Path) -> Vec<PathBuf> {
        vec![
            project_root.join("config"),
            project_root.join(".env"),
            project_root.join("config.toml"),
        ]
    }
}

/// Configuration source abstraction
///
/// Allows pluggable configuration sources like files, databases, HTTP
/// endpoints, etc.
pub trait ConfigSource {
    type Error: StdError + Send + Sync + 'static;

    /// Unique identifier for this configuration source
    fn id(&self) -> &str;

    /// Priority/precedence of this source (higher = higher precedence)
    fn precedence(&self) -> u8;

    /// Load configuration data from this source
    fn load(&self) -> Result<ConfigData, Self::Error>;

    /// Check if this source has changed since last load
    fn has_changed(&self) -> bool {
        false
    }

    /// Description of this configuration source for debugging
    fn description(&self) -> String {
        format!("ConfigSource({})", self.id())
    }
}

/// Raw configuration data from a source
#[derive(Debug, Clone)]
pub struct ConfigData {
    /// Raw configuration content (usually TOML, JSON, etc.)
    pub content: String,
    /// Format hint (toml, json, yaml, etc.)
    pub format: ConfigFormat,
    /// Source metadata
    pub metadata: ConfigMetadata,
}

/// Configuration format types
#[derive(Debug, Clone, PartialEq)]
pub enum ConfigFormat {
    Toml,
    Json,
    Yaml,
    Env,
    Custom(String),
}

/// Configuration metadata
#[derive(Debug, Clone)]
pub struct ConfigMetadata {
    /// Source identifier
    pub source_id: String,
    /// Last modification time (if available)
    pub modified: Option<std::time::SystemTime>,
    /// Additional metadata
    pub extra: HashMap<String, String>,
}

/// Configuration layer with precedence information
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfigLayer {
    /// Local project overrides (highest precedence)
    Local(PathBuf),
    /// Environment-specific configuration (dev, staging, prod)
    Environment(String, PathBuf),
    /// Feature-specific configurations
    Feature(String, PathBuf),
    /// Template defaults
    Template(PathBuf),
    /// Framework defaults (lowest precedence)
    Framework,
}

impl ConfigLayer {
    /// Get the precedence value (higher = higher precedence)
    pub fn precedence(&self) -> u8 {
        match self {
            ConfigLayer::Local(_) => 100,
            ConfigLayer::Environment(_, _) => 90,
            ConfigLayer::Feature(_, _) => 75,
            ConfigLayer::Template(_) => 50,
            ConfigLayer::Framework => 25,
        }
    }

    /// Get a human-readable description
    pub fn description(&self) -> String {
        match self {
            ConfigLayer::Local(path) => format!("Local override ({})", path.display()),
            ConfigLayer::Environment(env, path) => {
                format!("Environment '{}' ({})", env, path.display())
            }
            ConfigLayer::Feature(name, path) => format!("Feature '{}' ({})", name, path.display()),
            ConfigLayer::Template(path) => format!("Template default ({})", path.display()),
            ConfigLayer::Framework => "Framework default".to_string(),
        }
    }

    /// Get unique identifier for this layer
    pub fn id(&self) -> String {
        match self {
            ConfigLayer::Local(path) => format!("local:{}", path.display()),
            ConfigLayer::Environment(env, path) => format!("env:{}:{}", env, path.display()),
            ConfigLayer::Feature(name, path) => format!("feature:{}:{}", name, path.display()),
            ConfigLayer::Template(path) => format!("template:{}", path.display()),
            ConfigLayer::Framework => "framework".to_string(),
        }
    }
}

/// WASM constants container
#[derive(Debug, Clone, Default)]
pub struct WasmConstants {
    /// String constants for WASM consumption
    pub string_constants: HashMap<String, String>,
    /// Numeric constants
    pub numeric_constants: HashMap<String, i64>,
    /// Boolean constants
    pub boolean_constants: HashMap<String, bool>,
    /// Path constants (special handling for path validation)
    pub path_constants: HashMap<String, PathBuf>,
}

impl WasmConstants {
    /// Create new empty constants container
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a string constant
    pub fn add_string(&mut self, name: impl Into<String>, value: impl Into<String>) {
        self.string_constants.insert(name.into(), value.into());
    }

    /// Add a numeric constant
    pub fn add_numeric(&mut self, name: impl Into<String>, value: i64) {
        self.numeric_constants.insert(name.into(), value);
    }

    /// Add a boolean constant
    pub fn add_boolean(&mut self, name: impl Into<String>, value: bool) {
        self.boolean_constants.insert(name.into(), value);
    }

    /// Add a path constant
    pub fn add_path(&mut self, name: impl Into<String>, value: PathBuf) {
        self.path_constants.insert(name.into(), value);
    }

    /// Generate Rust code for constants
    pub fn generate_rust_code(&self) -> String {
        let mut code = String::new();
        code.push_str("// Generated configuration constants\n");
        code.push_str("// This file is auto-generated - do not edit manually!\n\n");

        // String constants
        for (name, value) in &self.string_constants {
            code.push_str(&format!(
                "/// Configuration constant: {}\npub const {}: &str = \"{}\";\n\n",
                name,
                name,
                value.replace('"', "\\\"")
            ));
        }

        // Numeric constants
        for (name, value) in &self.numeric_constants {
            code.push_str(&format!(
                "/// Configuration constant: {}\npub const {}: i64 = {};\n\n",
                name, name, value
            ));
        }

        // Boolean constants
        for (name, value) in &self.boolean_constants {
            code.push_str(&format!(
                "/// Configuration constant: {}\npub const {}: bool = {};\n\n",
                name, name, value
            ));
        }

        // Path constants (as strings for WASM compatibility)
        for (name, value) in &self.path_constants {
            code.push_str(&format!(
                "/// Configuration path constant: {}\npub const {}: &str = \"{}\";\n\n",
                name,
                name,
                value.to_string_lossy().replace('"', "\\\"")
            ));
        }

        code
    }
}

/// Configuration error types
#[derive(Debug)]
pub enum ConfigError {
    /// File not found
    FileNotFound(PathBuf),
    /// Parse error
    ParseError(String),
    /// Layer not found
    LayerNotFound(String),
    /// Resolution failed
    ResolutionFailed(String),
    /// I/O error
    IoError(std::io::Error),
    /// Custom error
    Custom(String),
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ConfigError::FileNotFound(path) => {
                write!(f, "Configuration file not found: {}", path.display())
            }
            ConfigError::ParseError(msg) => write!(f, "Configuration parse error: {}", msg),
            ConfigError::LayerNotFound(id) => write!(f, "Configuration layer not found: {}", id),
            ConfigError::ResolutionFailed(msg) => {
                write!(f, "Configuration resolution failed: {}", msg)
            }
            ConfigError::IoError(err) => write!(f, "Configuration I/O error: {}", err),
            ConfigError::Custom(msg) => write!(f, "Configuration error: {}", msg),
        }
    }
}

impl StdError for ConfigError {}

/// Trait for types that can be merged in layered configuration
///
/// This is used when combining configuration from multiple layers.
pub trait Mergeable {
    /// Merge another instance into this one, with the other taking precedence
    fn merge(&mut self, other: Self);

    /// Check if this configuration is empty/default
    fn is_empty(&self) -> bool {
        false
    }
}

/// Configuration validation trait
///
/// Allows configurations to validate themselves after resolution.
pub trait Validatable {
    type Error: StdError + Send + Sync + 'static;

    /// Validate this configuration
    fn validate(&self) -> Result<(), Self::Error>;

    /// Get validation warnings (non-fatal issues)
    fn warnings(&self) -> Vec<String> {
        vec![]
    }
}

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

    #[test]
    fn test_config_layer_precedence() {
        let local = ConfigLayer::Local(PathBuf::from("/local"));
        let env = ConfigLayer::Environment("dev".into(), PathBuf::from("/env"));
        let feature = ConfigLayer::Feature("auth".into(), PathBuf::from("/feature"));
        let template = ConfigLayer::Template(PathBuf::from("/template"));
        let framework = ConfigLayer::Framework;

        assert!(local.precedence() > env.precedence());
        assert!(env.precedence() > feature.precedence());
        assert!(feature.precedence() > template.precedence());
        assert!(template.precedence() > framework.precedence());
    }

    #[test]
    fn test_wasm_constants() {
        let mut constants = WasmConstants::new();
        constants.add_string("SITE_NAME", "My Site");
        constants.add_numeric("PORT", 3030);
        constants.add_boolean("DEBUG", true);
        constants.add_path("CONTENT_PATH", PathBuf::from("/content"));

        let code = constants.generate_rust_code();
        assert!(code.contains("pub const SITE_NAME: &str = \"My Site\""));
        assert!(code.contains("pub const PORT: i64 = 3030"));
        assert!(code.contains("pub const DEBUG: bool = true"));
        assert!(code.contains("pub const CONTENT_PATH: &str = \"/content\""));
    }

    #[test]
    fn test_config_layer_id() {
        let layer = ConfigLayer::Feature("auth".into(), PathBuf::from("/path"));
        assert_eq!(layer.id(), "feature:auth:/path");

        let framework = ConfigLayer::Framework;
        assert_eq!(framework.id(), "framework");
    }
}
