/// Builder for ConfigManager with advanced configuration options.
///
/// Provides a fluent interface for constructing ConfigManager instances
/// with optional asset classification, caching, and database integration.
pub struct ConfigManagerBuilder {
    config: ServiceConfig,
    asset_manager: Option<crate::asset_classification::AssetClassificationManager>,
    cache_timeout: std::time::Duration,
}

impl ConfigManagerBuilder {
    /// Creates a new ConfigManagerBuilder with the specified service configuration.
    pub const fn new(config: ServiceConfig) -> Self {
        Self {
            config,
            asset_manager: None,
            cache_timeout: std::time::Duration::from_secs(300),
        }
    }

    /// Sets the asset classification manager.
    pub fn with_asset_classification(
        mut self,
        manager: crate::asset_classification::AssetClassificationManager,
    ) -> Self {
        self.asset_manager = Some(manager);
        self
    }

    /// Sets the cache timeout duration.
    pub const fn with_cache_timeout(mut self, timeout: std::time::Duration) -> Self {
        self.cache_timeout = timeout;
        self
    }

    /// Builds the ConfigManager with the specified configuration.
    pub fn build(self) -> ConfigManager {
        ConfigManager {
            config: Arc::new(self.config),
            asset_classification: Arc::new(RwLock::new(self.asset_manager)),
            cache: Arc::new(RwLock::new(HashMap::new())),
            cache_timeout: self.cache_timeout,
        }
    }

    /// Builds the ConfigManager with database integration.
    ///
    /// # Errors
    /// Returns error if the operation fails
    #[cfg(feature = "postgres")]
    pub async fn build_with_database(
        self,
        database_pool: sqlx::PgPool,
    ) -> Result<ConfigManager, Box<dyn std::error::Error + Send + Sync>> {
        let manager = self.build();
        manager
            .initialize_asset_classification(database_pool)
            .await?;
        Ok(manager)
    }
}

// Configuration management and service configuration structures.
//
// This module provides the core configuration management infrastructure for
// the Foxhunt trading system. It handles service-specific configuration,
// environment management, and provides thread-safe access to configuration
// data across the application.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

/// Service-specific configuration structure.
///
/// Contains metadata and settings for a specific service in the Foxhunt
/// trading system. Supports environment-specific configuration and
/// versioning for configuration management and deployment tracking.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceConfig {
    /// Service name (e.g., "trading_service", "ml_training_service")
    pub name: String,
    /// Deployment environment (e.g., "development", "staging", "production")
    pub environment: String,
    /// Service version for deployment tracking
    pub version: String,
    /// Service-specific configuration settings as JSON
    pub settings: serde_json::Value,
}

/// Thread-safe configuration manager for comprehensive service configuration.
///
/// Provides centralized access to service configuration with support for:
/// - Asset classification management
///
/// - Hot-reload capabilities
/// - Environment-specific settings
///
/// - Thread-safe access patterns
///
/// Ensures configuration consistency across all components of a service.
pub struct ConfigManager {
    config: Arc<ServiceConfig>,
    /// Asset classification manager for symbol-based configuration
    asset_classification:
        Arc<RwLock<Option<crate::asset_classification::AssetClassificationManager>>>,
    /// Configuration cache for performance
    cache: Arc<RwLock<HashMap<String, (serde_json::Value, DateTime<Utc>)>>>,
    /// Cache timeout duration
    cache_timeout: std::time::Duration,
}

impl ConfigManager {
    /// Creates a new ConfigManager with the provided service configuration.
    ///
    /// The configuration is wrapped in an Arc for efficient sharing across
    /// multiple threads and components within the service.
    ///
    /// # Arguments
    ///
    /// * `config` - The service configuration to manage
    pub fn new(config: ServiceConfig) -> Self {
        Self {
            config: Arc::new(config),
            asset_classification: Arc::new(RwLock::new(None)),
            cache: Arc::new(RwLock::new(HashMap::new())),
            cache_timeout: std::time::Duration::from_secs(300), // 5 minutes
        }
    }

    /// Creates a new ConfigManager with asset classification support.
    ///
    /// Initializes the manager with both service configuration and
    /// asset classification capabilities for comprehensive trading
    /// parameter management.
    ///
    /// # Arguments
    ///
    /// * `config` - The service configuration to manage
    /// * `asset_manager` - Pre-configured asset classification manager
    pub fn with_asset_classification(
        config: ServiceConfig,
        asset_manager: crate::asset_classification::AssetClassificationManager,
    ) -> Self {
        Self {
            config: Arc::new(config),
            asset_classification: Arc::new(RwLock::new(Some(asset_manager))),
            cache: Arc::new(RwLock::new(HashMap::new())),
            cache_timeout: std::time::Duration::from_secs(300),
        }
    }

    /// Returns a shared reference to the service configuration.
    ///
    /// Provides thread-safe access to the configuration data through Arc cloning.
    ///
    /// The returned Arc can be shared across threads without additional locking.
    ///
    /// # Returns
    ///
    /// An Arc containing the service configuration
    pub fn get_config(&self) -> Arc<ServiceConfig> {
        Arc::clone(&self.config)
    }

    /// Initializes asset classification with database-backed configurations.
    ///
    /// Loads asset classification configurations from the database and
    /// initializes the asset classification manager for dynamic symbol
    /// classification and trading parameter retrieval.
    ///
    /// # Errors
    /// Returns error if the operation fails
    #[cfg(feature = "postgres")]
    pub async fn initialize_asset_classification(
        &self,
        database_pool: sqlx::PgPool,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        let mut loader = crate::database::PostgresAssetClassificationLoader::with_pool(database_pool);
        let configs = loader.load_asset_configurations().await?;

        let mut manager = crate::asset_classification::AssetClassificationManager::new();
        manager.load_configurations(configs).await?;

        if let Ok(mut asset_classification) = self.asset_classification.write() {
            *asset_classification = Some(manager);
        }

        Ok(())
    }

    /// Classifies a symbol using the asset classification manager.
    ///
    /// Returns the asset class for the given symbol based on configured
    /// pattern matching rules and explicit mappings.
    ///
    /// # Arguments
    ///
    /// * `symbol` - The trading symbol to classify
    ///
    /// # Returns
    ///
    /// The asset class or Unknown if classification fails
    pub fn classify_symbol(&self, symbol: &str) -> crate::asset_classification::AssetClass {
        if let Ok(asset_classification) = self.asset_classification.read() {
            if let Some(ref manager) = *asset_classification {
                return manager.classify_symbol(symbol);
            }
        }
        crate::asset_classification::AssetClass::Unknown
    }

    /// Gets trading parameters for a symbol.
    ///
    /// Retrieves comprehensive trading parameters including position limits,
    /// risk thresholds, and execution configuration for the specified symbol.
    ///
    /// # Arguments
    ///
    /// * `symbol` - The trading symbol
    ///
    /// # Returns
    ///
    /// Trading parameters if available, None otherwise
    pub fn get_trading_parameters(
        &self,
        symbol: &str,
    ) -> Option<crate::asset_classification::TradingParameters> {
        if let Ok(asset_classification) = self.asset_classification.read() {
            if let Some(ref manager) = *asset_classification {
                return manager.get_trading_parameters(symbol).cloned();
            }
        }
        None
    }

    /// Gets volatility profile for a symbol.
    ///
    /// Retrieves the volatility profile including base volatility,
    /// stress multipliers, and jump risk characteristics.
    ///
    /// # Arguments
    ///
    /// * `symbol` - The trading symbol
    ///
    /// # Returns
    ///
    /// Volatility profile if available, None otherwise
    pub fn get_volatility_profile(
        &self,
        symbol: &str,
    ) -> Option<crate::asset_classification::VolatilityProfile> {
        if let Ok(asset_classification) = self.asset_classification.read() {
            if let Some(ref manager) = *asset_classification {
                return manager.get_volatility_profile(symbol).cloned();
            }
        }
        None
    }

    /// Gets daily volatility estimate for a symbol.
    ///
    /// Calculates the daily volatility from the annual volatility
    /// using standard financial mathematics (annual / sqrt(252)).
    ///
    /// # Arguments
    ///
    /// * `symbol` - The trading symbol
    ///
    /// # Returns
    ///
    /// Daily volatility estimate as a decimal
    pub fn get_daily_volatility(&self, symbol: &str) -> f64 {
        if let Ok(asset_classification) = self.asset_classification.read() {
            if let Some(ref manager) = *asset_classification {
                return manager.get_daily_volatility(symbol);
            }
        }
        0.05 // Default 5% daily volatility for unknown symbols
    }

    /// Gets position size recommendation for a symbol.
    ///
    /// Calculates recommended position size based on portfolio NAV
    /// and the symbol's configured position limits.
    ///
    /// # Arguments
    ///
    /// * `symbol` - The trading symbol
    /// * `portfolio_nav` - Current portfolio net asset value
    ///
    /// # Returns
    ///
    /// Recommended position size if available
    pub fn get_position_size_recommendation(
        &self,
        symbol: &str,
        portfolio_nav: rust_decimal::Decimal,
    ) -> Option<rust_decimal::Decimal> {
        if let Ok(asset_classification) = self.asset_classification.read() {
            if let Some(ref manager) = *asset_classification {
                return manager.get_position_size_recommendation(symbol, portfolio_nav);
            }
        }
        None
    }

    /// Checks if trading is active for a symbol at the given time.
    ///
    /// Validates trading hours and market schedule for the symbol.
    ///
    /// # Arguments
    ///
    /// * `symbol` - The trading symbol
    /// * `timestamp` - The timestamp to check
    ///
    /// # Returns
    ///
    /// True if trading is active, false otherwise
    pub fn is_trading_active(&self, symbol: &str, timestamp: DateTime<Utc>) -> bool {
        if let Ok(asset_classification) = self.asset_classification.read() {
            if let Some(ref manager) = *asset_classification {
                return manager.is_trading_active(symbol, timestamp);
            }
        }
        true // Default to always active if no classification available
    }

    /// Reloads asset classification configurations.
    ///
    /// Triggers a reload of asset classification configurations
    /// for hot-reload functionality in production environments.
    ///
    /// # Errors
    /// Returns error if the operation fails
    #[cfg(feature = "postgres")]
    pub async fn reload_asset_classification(
        &self,
        database_pool: sqlx::PgPool,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        let needs_reload = {
            let asset_classification = self.asset_classification.read().ok();
            asset_classification
                .as_ref()
                .and_then(|ac| ac.as_ref())
                .map(|manager| manager.needs_reload())
                .unwrap_or(false)
        }; // Lock released here

        if needs_reload {
            self.initialize_asset_classification(database_pool).await?;
        }
        Ok(())
    }

    /// Gets cached configuration value.
    ///
    /// Retrieves a cached configuration value with automatic expiration.
    ///
    /// # Arguments
    ///
    /// * `key` - Cache key
    ///
    /// # Returns
    ///
    /// Cached value if available and not expired
    pub fn get_cached_config(&self, key: &str) -> Option<serde_json::Value> {
        if let Ok(cache) = self.cache.read() {
            if let Some((value, timestamp)) = cache.get(key) {
                let elapsed = Utc::now().signed_duration_since(*timestamp);
                if elapsed.to_std().unwrap_or_default() < self.cache_timeout {
                    return Some(value.clone());
                }
            }
        }
        None
    }

    /// Sets cached configuration value.
    ///
    /// Stores a configuration value in the cache with timestamp.
    ///
    /// # Arguments
    ///
    /// * `key` - Cache key
    /// * `value` - Value to cache
    pub fn set_cached_config(&self, key: String, value: serde_json::Value) {
        if let Ok(mut cache) = self.cache.write() {
            cache.insert(key, (value, Utc::now()));
        }
    }

    /// Clears expired cache entries.
    ///
    /// Removes cache entries that have exceeded the timeout duration.
    pub fn cleanup_cache(&self) {
        if let Ok(mut cache) = self.cache.write() {
            let now = Utc::now();
            cache.retain(|_, (_, timestamp)| {
                let elapsed = now.signed_duration_since(*timestamp);
                elapsed.to_std().unwrap_or_default() < self.cache_timeout
            });
        }
    }
}

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

    fn create_test_config() -> ServiceConfig {
        ServiceConfig {
            name: "test_service".to_owned(),
            environment: "test".to_owned(),
            version: "1.0.0".to_owned(),
            settings: json!({"test_key": "test_value"}),
        }
    }

    #[test]
    fn test_service_config_creation() {
        let config = create_test_config();
        assert_eq!(config.name, "test_service");
        assert_eq!(config.environment, "test");
        assert_eq!(config.version, "1.0.0");
    }

    #[test]
    fn test_config_manager_new() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);
        let retrieved_config = manager.get_config();
        assert_eq!(retrieved_config.name, "test_service");
    }

    #[test]
    fn test_config_manager_builder() {
        let config = create_test_config();
        let manager = ConfigManagerBuilder::new(config)
            .with_cache_timeout(std::time::Duration::from_secs(60))
            .build();

        let retrieved_config = manager.get_config();
        assert_eq!(retrieved_config.name, "test_service");
    }

    #[test]
    fn test_config_manager_cache_set_and_get() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let test_value = json!({"cached": "data"});
        manager.set_cached_config("test_key".to_owned(), test_value.clone());

        let retrieved = manager.get_cached_config("test_key");
        assert!(retrieved.is_some());
        assert_eq!(retrieved.unwrap(), test_value);
    }

    #[test]
    fn test_config_manager_cache_miss() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let retrieved = manager.get_cached_config("nonexistent_key");
        assert!(retrieved.is_none());
    }

    #[test]
    fn test_config_manager_cleanup_cache() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let test_value = json!({"cached": "data"});
        manager.set_cached_config("test_key".to_owned(), test_value);

        manager.cleanup_cache();

        // Cache entry should still exist since it was just created
        let retrieved = manager.get_cached_config("test_key");
        assert!(retrieved.is_some());
    }

    #[test]
    fn test_config_manager_classify_symbol_without_asset_manager() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let asset_class = manager.classify_symbol("AAPL");
        assert_eq!(
            asset_class,
            crate::asset_classification::AssetClass::Unknown
        );
    }

    #[test]
    fn test_config_manager_get_daily_volatility_default() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let volatility = manager.get_daily_volatility("AAPL");
        assert_eq!(volatility, 0.05); // Default value
    }

    #[test]
    fn test_config_manager_is_trading_active_default() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let now = chrono::Utc::now();
        let is_active = manager.is_trading_active("AAPL", now);
        assert!(is_active); // Default to always active
    }

    #[test]
    fn test_config_manager_get_trading_parameters_none() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let params = manager.get_trading_parameters("AAPL");
        assert!(params.is_none());
    }

    #[test]
    fn test_config_manager_get_volatility_profile_none() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let profile = manager.get_volatility_profile("AAPL");
        assert!(profile.is_none());
    }

    #[test]
    fn test_config_manager_get_position_size_recommendation_none() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let recommendation =
            manager.get_position_size_recommendation("AAPL", rust_decimal::Decimal::new(100000, 0));
        assert!(recommendation.is_none());
    }

    #[test]
    fn test_config_manager_with_asset_classification() {
        let config = create_test_config();
        let asset_manager = crate::asset_classification::AssetClassificationManager::new();
        let manager = ConfigManager::with_asset_classification(config, asset_manager);

        let retrieved_config = manager.get_config();
        assert_eq!(retrieved_config.name, "test_service");
    }

    #[test]
    fn test_builder_with_asset_classification() {
        let config = create_test_config();
        let asset_manager = crate::asset_classification::AssetClassificationManager::new();

        let manager = ConfigManagerBuilder::new(config)
            .with_asset_classification(asset_manager)
            .build();

        let retrieved_config = manager.get_config();
        assert_eq!(retrieved_config.name, "test_service");
    }

    #[test]
    fn test_service_config_serialization() {
        let config = create_test_config();
        let serialized = serde_json::to_string(&config).unwrap();
        let deserialized: ServiceConfig = serde_json::from_str(&serialized).unwrap();

        assert_eq!(config.name, deserialized.name);
        assert_eq!(config.environment, deserialized.environment);
        assert_eq!(config.version, deserialized.version);
    }

    #[test]
    fn test_config_manager_multiple_cache_entries() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        for i in 0..10 {
            manager.set_cached_config(format!("key_{}", i), json!({"value": i}));
        }

        for i in 0..10 {
            let retrieved = manager.get_cached_config(&format!("key_{}", i));
            assert!(retrieved.is_some());
        }
    }

    #[test]
    fn test_config_manager_cache_overwrite() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        manager.set_cached_config("key".to_owned(), json!({"value": 1}));
        manager.set_cached_config("key".to_owned(), json!({"value": 2}));

        let retrieved = manager.get_cached_config("key");
        assert_eq!(retrieved.unwrap(), json!({"value": 2}));
    }

    #[test]
    fn test_builder_custom_cache_timeout() {
        let config = create_test_config();
        let custom_timeout = std::time::Duration::from_secs(120);

        let manager = ConfigManagerBuilder::new(config)
            .with_cache_timeout(custom_timeout)
            .build();

        // Cache timeout is set internally
        let retrieved_config = manager.get_config();
        assert_eq!(retrieved_config.name, "test_service");
    }

    #[test]
    fn test_config_manager_shared_config() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let config1 = manager.get_config();
        let config2 = manager.get_config();

        // Both should point to the same Arc
        assert_eq!(config1.name, config2.name);
    }

    #[test]
    fn test_service_config_clone() {
        let config1 = create_test_config();
        let config2 = config1.clone();

        assert_eq!(config1.name, config2.name);
        assert_eq!(config1.environment, config2.environment);
        assert_eq!(config1.version, config2.version);
    }

    #[test]
    fn test_config_manager_cache_timeout_configuration() {
        let config = create_test_config();
        let custom_timeout = std::time::Duration::from_millis(10);
        let manager = ConfigManagerBuilder::new(config)
            .with_cache_timeout(custom_timeout)
            .build();

        // Cache timeout is configured internally
        assert_eq!(manager.cache_timeout, custom_timeout);

        // Test that cache still works normally
        manager.set_cached_config("test_key".to_owned(), json!({"value": 42}));
        assert!(manager.get_cached_config("test_key").is_some());
    }

    #[test]
    fn test_config_manager_concurrent_access() {
        use std::sync::Arc;
        use std::thread;

        let config = create_test_config();
        let manager = Arc::new(ConfigManager::new(config));

        let mut handles = vec![];

        for i in 0..10 {
            let manager_clone = Arc::clone(&manager);
            let handle = thread::spawn(move || {
                manager_clone
                    .set_cached_config(format!("concurrent_key_{}", i), json!({"thread_id": i}));
                manager_clone.get_cached_config(&format!("concurrent_key_{}", i))
            });
            handles.push(handle);
        }

        for handle in handles {
            assert!(handle.join().unwrap().is_some());
        }
    }

    #[test]
    fn test_config_manager_daily_volatility_fallback() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        // Should return default 5% for unknown symbols
        let vol = manager.get_daily_volatility("UNKNOWN_SYMBOL");
        assert_eq!(vol, 0.05);
    }

    #[test]
    fn test_config_manager_position_size_none() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        // Should return None without asset classification
        let size =
            manager.get_position_size_recommendation("AAPL", rust_decimal::Decimal::new(100000, 0));
        assert!(size.is_none());
    }

    #[test]
    fn test_service_config_validation() {
        let mut config = create_test_config();

        // Valid config
        assert!(!config.name.is_empty());
        assert!(!config.environment.is_empty());

        // Test with empty name
        config.name = String::new();
        assert!(config.name.is_empty());
    }

    #[test]
    fn test_config_manager_cache_clear() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        // Add some cache entries
        manager.set_cached_config("key1".to_owned(), json!({"value": 1}));
        manager.set_cached_config("key2".to_owned(), json!({"value": 2}));

        assert!(manager.get_cached_config("key1").is_some());
        assert!(manager.get_cached_config("key2").is_some());

        // Manual clear
        if let Ok(mut cache) = manager.cache.write() {
            cache.clear();
        }

        assert!(manager.get_cached_config("key1").is_none());
        assert!(manager.get_cached_config("key2").is_none());
    }

    #[test]
    fn test_builder_default_values() {
        let config = create_test_config();
        let manager = ConfigManagerBuilder::new(config.clone()).build();

        let retrieved = manager.get_config();
        assert_eq!(retrieved.name, config.name);
        assert_eq!(retrieved.environment, config.environment);
    }

    #[test]
    fn test_config_manager_arc_cloning() {
        let config = create_test_config();
        let manager = ConfigManager::new(config);

        let config1 = Arc::clone(&manager.config);
        let config2 = Arc::clone(&manager.config);

        assert_eq!(config1.name, config2.name);
        assert_eq!(Arc::strong_count(&manager.config), 3); // Original + 2 clones
    }
}
