//! Database configuration for PostgreSQL connections and connection pooling.
//!
//! This module provides comprehensive database configuration structures for managing
//! PostgreSQL connections, connection pools, and transaction settings in the Foxhunt
//! HFT trading system. It supports connection pooling, timeout management, and
//! transaction isolation levels optimized for high-frequency trading workloads.

use serde::{Deserialize, Serialize};
use std::time::Duration;
#[cfg(feature = "postgres")]
use std::time::Instant;

#[cfg(feature = "postgres")]
use sqlx::Row;

/// Main database configuration structure for PostgreSQL connections.
///
/// Provides comprehensive database connection settings including connection pooling,
/// timeouts, logging, and transaction management. Optimized for high-frequency
/// trading workloads with appropriate defaults for low-latency operations.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::module_name_repetitions)]
pub struct DatabaseConfig {
    /// PostgreSQL connection URL (e.g., "postgresql://user:pass@host:port/database")
    pub url: String,
    /// Maximum number of connections in the pool
    pub max_connections: u32,
    /// Minimum number of connections to maintain in the pool
    pub min_connections: u32,
    /// Timeout for establishing new database connections
    pub connect_timeout: std::time::Duration,
    /// Timeout for individual query execution
    pub query_timeout: std::time::Duration,
    /// Enable detailed query logging for debugging
    pub enable_query_logging: bool,
    /// Application name to identify connections in PostgreSQL logs
    pub application_name: Option<String>,
    /// Connection pool configuration settings
    pub pool: PoolConfig,
    /// Transaction management configuration
    pub transaction: TransactionConfig,
}

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

impl DatabaseConfig {
    /// Creates a new DatabaseConfig with sensible defaults for development.
    ///
    /// Returns a configuration suitable for local development with a PostgreSQL
    /// database running on localhost. Production deployments should override
    /// these settings through environment variables or configuration files.
    pub fn new() -> Self {
        // Get database URL from environment, with fallback to development default
        let url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
            "postgresql://localhost:5432/foxhunt".to_owned()
        });

        Self {
            url,
            max_connections: 10,
            min_connections: 1,
            connect_timeout: Duration::from_secs(30),
            query_timeout: Duration::from_secs(60),
            enable_query_logging: false,
            application_name: Some("foxhunt".to_owned()),
            pool: PoolConfig::default(),
            transaction: TransactionConfig::default(),
        }
    }

    /// Validates the database configuration for correctness.
    ///
    /// Performs basic validation checks on the configuration parameters to ensure
    /// they are valid before attempting to establish database connections.
    ///
    /// # Errors
    ///
    /// Returns an error string if the configuration is invalid, such as:
    /// - Empty database URL
    ///
    /// - Invalid connection parameters
    pub fn validate(&self) -> Result<(), String> {
        if self.url.is_empty() {
            return Err("Database URL cannot be empty".to_owned());
        }
        Ok(())
    }
}

/// Database connection pool configuration.
///
/// Manages the behavior of the connection pool including connection lifecycle,
/// timeouts, and health checking. Optimized for high-frequency trading workloads
/// where connection availability and low latency are critical.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolConfig {
    /// Minimum number of connections to maintain in the pool
    pub min_connections: u32,
    /// Maximum number of connections allowed in the pool
    pub max_connections: u32,
    /// Timeout in seconds for acquiring a connection from the pool
    pub acquire_timeout_secs: u64,
    /// Maximum lifetime in seconds for a connection before it's recycled
    pub max_lifetime_secs: u64,
    /// Timeout in seconds before idle connections are closed
    pub idle_timeout_secs: u64,
    /// Whether to test connections before returning them from the pool
    pub test_before_acquire: bool,
    /// Database URL for pool connections
    pub database_url: String,
    /// Enable periodic health checks for pool connections
    pub health_check_enabled: bool,
    /// Interval in seconds between health checks
    pub health_check_interval_secs: u64,
}

impl Default for PoolConfig {
    fn default() -> Self {
        // Get database URL from environment, with fallback to development default
        let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
            "postgresql://localhost:5432/foxhunt".to_owned()
        });

        Self {
            min_connections: 1,
            max_connections: 10,
            acquire_timeout_secs: 30,
            max_lifetime_secs: 3600,
            idle_timeout_secs: 3600,
            test_before_acquire: true,
            database_url,
            health_check_enabled: true,
            health_check_interval_secs: 60,
        }
    }
}

/// Database transaction configuration and retry policies.
///
/// Configures transaction behavior including isolation levels, timeouts,
/// and retry mechanisms. Critical for maintaining data consistency in
/// high-frequency trading operations while handling transient failures.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionConfig {
    /// PostgreSQL transaction isolation level (e.g., "READ_COMMITTED", "SERIALIZABLE")
    pub isolation_level: String,
    /// Default timeout duration for transactions
    pub timeout: Duration,
    /// Default timeout in seconds for transactions
    pub default_timeout_secs: u64,
    /// Enable automatic retry on transaction failures
    pub enable_retry: bool,
    /// Maximum number of retry attempts for failed transactions
    pub max_retries: u32,
    /// Delay in milliseconds between retry attempts
    pub retry_delay_ms: u64,
    /// Maximum number of nested savepoints allowed
    pub max_savepoints: u32,
}

impl Default for TransactionConfig {
    fn default() -> Self {
        Self {
            isolation_level: "READ_COMMITTED".to_owned(),
            timeout: Duration::from_secs(30),
            default_timeout_secs: 30,
            enable_retry: true,
            max_retries: 3,
            retry_delay_ms: 100,
            max_savepoints: 10,
        }
    }
}

/// Database loader for symbol configurations with PostgreSQL integration.
///
/// Provides high-performance loading and caching of symbol configurations
/// from the PostgreSQL database. Supports real-time updates through PostgreSQL
///
/// NOTIFY/LISTEN for configuration hot-reload capabilities.
#[cfg(feature = "postgres")]
pub struct PostgresSymbolConfigLoader {
    /// Database connection pool
    pool: sqlx::PgPool,
    /// Configuration cache timeout
    cache_timeout: Duration,
    /// PostgreSQL listener for configuration changes
    listener: Option<sqlx::postgres::PgListener>,
    /// Per-symbol cache: symbol -> (loaded_at, config)
    symbol_cache: std::collections::HashMap<String, (Instant, crate::symbol_config::SymbolConfig)>,
}

#[cfg(feature = "postgres")]
impl PostgresSymbolConfigLoader {
    /// Creates a new PostgreSQL symbol configuration loader.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn new(database_url: &str) -> Result<Self, sqlx::Error> {
        let pool = sqlx::PgPool::connect(database_url).await?;

        Ok(Self {
            pool,
            cache_timeout: Duration::from_secs(300), // 5 minutes
            listener: None,
            symbol_cache: std::collections::HashMap::new(),
        })
    }

    /// Creates a new loader with an existing connection pool.
    pub fn with_pool(pool: sqlx::PgPool) -> Self {
        Self {
            pool,
            cache_timeout: Duration::from_secs(300),
            listener: None,
            symbol_cache: std::collections::HashMap::new(),
        }
    }

    /// Loads a symbol configuration by symbol name, using the in-memory cache
    /// when the entry is fresher than `cache_timeout`.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn load_symbol_config(
        &mut self,
        symbol: &str,
    ) -> Result<Option<crate::symbol_config::SymbolConfig>, sqlx::Error> {
        // Return cached entry if fresh
        if let Some((loaded_at, config)) = self.symbol_cache.get(symbol) {
            if loaded_at.elapsed() < self.cache_timeout {
                return Ok(Some(config.clone()));
            }
        }

        // Simplified implementation using basic sqlx::query instead of macros
        let query = "
            SELECT 
                sc.id,
                sc.symbol,
                sc.description,
                sc.classification,
                sc.primary_exchange,
                sc.currency,
                sc.tick_size,
                sc.lot_size,
                sc.min_order_size,
                sc.max_order_size,
                sc.sector,
                sc.industry,
                sc.market_cap,
                sc.avg_daily_volume,
                sc.margin_requirement,
                sc.position_limit,
                sc.risk_multiplier,
                sc.is_active,
                sc.data_source,
                sc.created_at,
                sc.updated_at,
                sc.last_validated
            FROM symbol_config sc
            WHERE sc.symbol = $1 AND sc.is_active = true
        ";

        let row = sqlx::query(query)
            .bind(symbol)
            .fetch_optional(&self.pool)
            .await?;

        if let Some(row) = row {
            // Create a basic symbol config from the row
            let symbol_name: String = row.get("symbol");
            let description: String = row.get("description");
            let classification_str: String = row.get("classification");

            let classification = match classification_str.as_str() {
                "EQUITY" => crate::symbol_config::AssetClassification::Equity,
                "FUTURE" => crate::symbol_config::AssetClassification::Future,
                "FOREX" => crate::symbol_config::AssetClassification::Forex,
                "CRYPTO" => crate::symbol_config::AssetClassification::Crypto,
                "COMMODITY" => crate::symbol_config::AssetClassification::Commodity,
                "FIXED_INCOME" => crate::symbol_config::AssetClassification::FixedIncome,
                "OPTION" => crate::symbol_config::AssetClassification::Option,
                "ETF" => crate::symbol_config::AssetClassification::Etf,
                "INDEX" => crate::symbol_config::AssetClassification::Index,
                "DERIVATIVE" => crate::symbol_config::AssetClassification::Derivative,
                _ => crate::symbol_config::AssetClassification::Equity,
            };

            let mut config = crate::symbol_config::SymbolConfig::new(symbol_name, classification);
            config.description = description;
            config.primary_exchange = row.get("primary_exchange");
            config.currency = row.get("currency");

            // Handle decimal conversions safely
            if let Ok(tick_size) = row.try_get::<rust_decimal::Decimal, _>("tick_size") {
                if let Ok(f) = tick_size.try_into() {
                    config.tick_size = f;
                }
            }

            // Store in cache
            self.symbol_cache.insert(symbol.to_owned(), (Instant::now(), config.clone()));
            Ok(Some(config))
        } else {
            Ok(None)
        }
    }

    /// Loads all active symbol configurations.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn load_all_symbols(
        &self,
    ) -> Result<Vec<crate::symbol_config::SymbolConfig>, sqlx::Error> {
        let query = "
            SELECT symbol, description, classification
            FROM symbol_config 
            WHERE is_active = true
            ORDER BY symbol
        ";

        let rows = sqlx::query(query).fetch_all(&self.pool).await?;

        let mut configs = Vec::new();
        for row in rows {
            let symbol_name: String = row.get("symbol");
            let description: String = row.get("description");
            let classification_str: String = row.get("classification");

            let classification = match classification_str.as_str() {
                "EQUITY" => crate::symbol_config::AssetClassification::Equity,
                "FUTURE" => crate::symbol_config::AssetClassification::Future,
                "FOREX" => crate::symbol_config::AssetClassification::Forex,
                "CRYPTO" => crate::symbol_config::AssetClassification::Crypto,
                "COMMODITY" => crate::symbol_config::AssetClassification::Commodity,
                "FIXED_INCOME" => crate::symbol_config::AssetClassification::FixedIncome,
                "OPTION" => crate::symbol_config::AssetClassification::Option,
                "ETF" => crate::symbol_config::AssetClassification::Etf,
                "INDEX" => crate::symbol_config::AssetClassification::Index,
                "DERIVATIVE" => crate::symbol_config::AssetClassification::Derivative,
                _ => crate::symbol_config::AssetClassification::Equity,
            };

            let mut config = crate::symbol_config::SymbolConfig::new(symbol_name, classification);
            config.description = description;
            configs.push(config);
        }

        Ok(configs)
    }

    /// Loads symbols filtered by asset classification.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn load_symbols_by_classification(
        &self,
        classification: crate::symbol_config::AssetClassification,
    ) -> Result<Vec<crate::symbol_config::SymbolConfig>, sqlx::Error> {
        let class_str = classification.regulatory_class();

        let query = "
            SELECT symbol, description, classification
            FROM symbol_config 
            WHERE is_active = true AND classification = $1
            ORDER BY symbol
        ";

        let rows = sqlx::query(query)
            .bind(class_str)
            .fetch_all(&self.pool)
            .await?;

        let mut configs = Vec::new();
        for row in rows {
            let symbol_name: String = row.get("symbol");
            let description: String = row.get("description");
            let mut config =
                crate::symbol_config::SymbolConfig::new(symbol_name, classification.clone());
            config.description = description;
            configs.push(config);
        }

        Ok(configs)
    }
    /// Saves or updates a symbol configuration.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn save_symbol_config(
        &self,
        config: &crate::symbol_config::SymbolConfig,
    ) -> Result<(), sqlx::Error> {
        let query = "
            INSERT INTO symbol_config (
                symbol, description, classification, primary_exchange, currency
            ) VALUES ($1, $2, $3, $4, $5)
            ON CONFLICT (symbol) DO UPDATE SET
                description = EXCLUDED.description,
                classification = EXCLUDED.classification,
                primary_exchange = EXCLUDED.primary_exchange,
                currency = EXCLUDED.currency,
                updated_at = NOW()
        ";

        sqlx::query(query)
            .bind(&config.symbol)
            .bind(&config.description)
            .bind(config.classification.regulatory_class())
            .bind(&config.primary_exchange)
            .bind(&config.currency)
            .execute(&self.pool)
            .await?;

        Ok(())
    }

    /// Initializes PostgreSQL NOTIFY/LISTEN for configuration hot-reload.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn enable_hot_reload(&mut self) -> Result<(), sqlx::Error> {
        let mut listener = sqlx::postgres::PgListener::connect_with(&self.pool).await?;
        listener.listen("symbol_config_changed").await?;
        self.listener = Some(listener);
        Ok(())
    }

    /// Checks for configuration change notifications.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn check_for_updates(&mut self) -> Result<Option<String>, sqlx::Error> {
        if let Some(listener) = &mut self.listener {
            if let Some(notification) = listener.try_recv().await? {
                return Ok(Some(notification.payload().to_owned()));
            }
        }
        Ok(None)
    }
}

/// Database integration for comprehensive asset classification system.
///
/// Provides PostgreSQL-backed storage and retrieval for asset classification
/// configurations with support for pattern matching, caching, and hot-reload.
#[cfg(feature = "postgres")]
pub struct PostgresAssetClassificationLoader {
    /// Database connection pool
    pool: sqlx::PgPool,
    /// Configuration cache timeout
    cache_timeout: Duration,
    /// PostgreSQL listener for configuration changes
    listener: Option<sqlx::postgres::PgListener>,
    /// Cached asset configurations with load timestamp
    config_cache: Option<(Instant, Vec<crate::asset_classification::AssetConfig>)>,
}

#[cfg(feature = "postgres")]
impl PostgresAssetClassificationLoader {
    /// Creates a new PostgreSQL asset classification loader.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn new(database_url: &str) -> Result<Self, sqlx::Error> {
        let pool = sqlx::PgPool::connect(database_url).await?;

        Ok(Self {
            pool,
            cache_timeout: Duration::from_secs(300), // 5 minutes
            listener: None,
            config_cache: None,
        })
    }

    /// Creates a new loader with an existing connection pool.
    pub fn with_pool(pool: sqlx::PgPool) -> Self {
        Self {
            pool,
            cache_timeout: Duration::from_secs(300),
            listener: None,
            config_cache: None,
        }
    }

    /// Loads all active asset configurations ordered by priority, using
    /// the in-memory cache when fresher than `cache_timeout`.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn load_asset_configurations(
        &mut self,
    ) -> Result<Vec<crate::asset_classification::AssetConfig>, sqlx::Error> {
        // Return cached if fresh
        if let Some((loaded_at, cached)) = &self.config_cache {
            if loaded_at.elapsed() < self.cache_timeout {
                return Ok(cached.clone());
            }
        }

        let query = "
                SELECT 
                    id,
                    name,
                    symbol_pattern,
                    asset_class_data,
                    volatility_profile,
                    trading_parameters,
                    priority,
                    is_active,
                    created_at,
                    updated_at,
                    trading_hours,
                    settlement_config
                FROM asset_configurations
                WHERE is_active = true
                ORDER BY priority DESC
            ";

        let rows = sqlx::query(query).fetch_all(&self.pool).await?;

        let mut configs = Vec::new();
        for row in rows {
            if let Ok(config) = Self::row_to_asset_config(row) {
                configs.push(config);
            }
        }

        // Store in cache
        self.config_cache = Some((Instant::now(), configs.clone()));

        Ok(configs)
    }

    /// Loads a specific asset configuration by ID.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn load_asset_configuration_by_id(
        &self,
        id: uuid::Uuid,
    ) -> Result<Option<crate::asset_classification::AssetConfig>, sqlx::Error> {
        let query = "
                SELECT 
                    id,
                    name,
                    symbol_pattern,
                    asset_class_data,
                    volatility_profile,
                    trading_parameters,
                    priority,
                    is_active,
                    created_at,
                    updated_at,
                    trading_hours,
                    settlement_config
                FROM asset_configurations
                WHERE id = $1
            ";

        let row = sqlx::query(query)
            .bind(id)
            .fetch_optional(&self.pool)
            .await?;

        if let Some(row) = row {
            Ok(Some(Self::row_to_asset_config(row)?))
        } else {
            Ok(None)
        }
    }

    /// Saves or updates an asset configuration.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn save_asset_configuration(
        &self,
        config: &crate::asset_classification::AssetConfig,
    ) -> Result<(), sqlx::Error> {
        let query = "
                INSERT INTO asset_configurations (
                    id, name, symbol_pattern, asset_class_data, volatility_profile,
                    trading_parameters, priority, is_active, created_at, updated_at,
                    trading_hours, settlement_config
                ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
                ON CONFLICT (id) DO UPDATE SET
                    name = EXCLUDED.name,
                    symbol_pattern = EXCLUDED.symbol_pattern,
                    asset_class_data = EXCLUDED.asset_class_data,
                    volatility_profile = EXCLUDED.volatility_profile,
                    trading_parameters = EXCLUDED.trading_parameters,
                    priority = EXCLUDED.priority,
                    is_active = EXCLUDED.is_active,
                    updated_at = NOW(),
                    trading_hours = EXCLUDED.trading_hours,
                    settlement_config = EXCLUDED.settlement_config
            ";

        let asset_class_json = serde_json::to_value(&config.asset_class)
            .map_err(|e| sqlx::Error::Encode(Box::new(e)))?;
        let volatility_json = serde_json::to_value(&config.volatility_profile)
            .map_err(|e| sqlx::Error::Encode(Box::new(e)))?;
        let trading_params_json = serde_json::to_value(&config.trading_parameters)
            .map_err(|e| sqlx::Error::Encode(Box::new(e)))?;
        let trading_hours_json = serde_json::to_value(&config.trading_hours)
            .map_err(|e| sqlx::Error::Encode(Box::new(e)))?;
        let settlement_json = serde_json::to_value(&config.settlement_config)
            .map_err(|e| sqlx::Error::Encode(Box::new(e)))?;

        sqlx::query(query)
            .bind(config.id)
            .bind(&config.name)
            .bind(&config.symbol_pattern)
            .bind(asset_class_json)
            .bind(volatility_json)
            .bind(trading_params_json)
            .bind(i32::try_from(config.priority).unwrap_or(0))
            .bind(config.is_active)
            .bind(config.created_at)
            .bind(config.updated_at)
            .bind(trading_hours_json)
            .bind(settlement_json)
            .execute(&self.pool)
            .await?;

        Ok(())
    }

    /// Loads explicit symbol mappings.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn load_symbol_mappings(
        &self,
    ) -> Result<
        std::collections::HashMap<String, crate::asset_classification::AssetClass>,
        sqlx::Error,
    > {
        let query = "
                SELECT symbol, asset_class_data
                FROM symbol_mappings
                WHERE is_active = true AND (expires_at IS NULL OR expires_at > NOW())
            ";

        let rows = sqlx::query(query).fetch_all(&self.pool).await?;

        let mut mappings = std::collections::HashMap::new();
        for row in rows {
            let symbol: String = row.get("symbol");
            let asset_class_json: serde_json::Value = row.get("asset_class_data");

            if let Ok(asset_class) =
                serde_json::from_value::<crate::asset_classification::AssetClass>(asset_class_json)
            {
                mappings.insert(symbol.to_uppercase(), asset_class);
            }
        }

        Ok(mappings)
    }

    /// Saves a symbol mapping.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn save_symbol_mapping(
        &self,
        symbol: &str,
        asset_class: &crate::asset_classification::AssetClass,
        source: &str,
        confidence_score: f64,
        expires_at: Option<chrono::DateTime<chrono::Utc>>,
    ) -> Result<(), sqlx::Error> {
        let query = "
                INSERT INTO symbol_mappings (
                    symbol, asset_class_data, source, confidence_score, expires_at
                ) VALUES ($1, $2, $3, $4, $5)
                ON CONFLICT (symbol) DO UPDATE SET
                    asset_class_data = EXCLUDED.asset_class_data,
                    source = EXCLUDED.source,
                    confidence_score = EXCLUDED.confidence_score,
                    expires_at = EXCLUDED.expires_at,
                    updated_at = NOW()
            ";

        let asset_class_json =
            serde_json::to_value(asset_class).map_err(|e| sqlx::Error::Encode(Box::new(e)))?;

        sqlx::query(query)
            .bind(symbol.to_uppercase())
            .bind(asset_class_json)
            .bind(source)
            .bind(confidence_score)
            .bind(expires_at)
            .execute(&self.pool)
            .await?;

        Ok(())
    }

    /// Loads volatility profiles.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn load_volatility_profiles(
        &self,
    ) -> Result<
        std::collections::HashMap<String, crate::asset_classification::VolatilityProfile>,
        sqlx::Error,
    > {
        let query = "
                SELECT 
                    name,
                    base_annual_volatility,
                    stress_volatility_multiplier,
                    intraday_pattern,
                    volatility_persistence,
                    jump_risk
                FROM volatility_profiles
                WHERE is_active = true
            ";

        let rows = sqlx::query(query).fetch_all(&self.pool).await?;

        let mut profiles = std::collections::HashMap::new();
        for row in rows {
            let name: String = row.get("name");
            let base_volatility: rust_decimal::Decimal = row.get("base_annual_volatility");
            let stress_multiplier: rust_decimal::Decimal = row.get("stress_volatility_multiplier");
            let persistence: rust_decimal::Decimal = row.get("volatility_persistence");
            let intraday_json: serde_json::Value = row.get("intraday_pattern");
            let jump_risk_json: serde_json::Value = row.get("jump_risk");

            if let (Ok(base_vol), Ok(stress_mult), Ok(persist), Ok(intraday), Ok(jump_risk)) = (
                f64::try_from(base_volatility),
                f64::try_from(stress_multiplier),
                f64::try_from(persistence),
                serde_json::from_value::<Vec<f64>>(intraday_json),
                serde_json::from_value::<crate::asset_classification::JumpRiskProfile>(
                    jump_risk_json,
                ),
            ) {
                let profile = crate::asset_classification::VolatilityProfile {
                    base_annual_volatility: base_vol,
                    stress_volatility_multiplier: stress_mult,
                    intraday_pattern: intraday,
                    volatility_persistence: persist,
                    jump_risk,
                };
                profiles.insert(name, profile);
            }
        }

        Ok(profiles)
    }

    /// Caches symbol classification for performance.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn cache_symbol_classification(
        &self,
        symbol: &str,
        asset_class: &crate::asset_classification::AssetClass,
        configuration_id: Option<uuid::Uuid>,
    ) -> Result<(), sqlx::Error> {
        let query = "
                INSERT INTO asset_classification_cache (symbol, asset_class_data, configuration_id)
                VALUES ($1, $2, $3)
                ON CONFLICT (symbol) DO UPDATE SET
                    asset_class_data = EXCLUDED.asset_class_data,
                    configuration_id = EXCLUDED.configuration_id,
                    cached_at = NOW(),
                    expires_at = NOW() + INTERVAL '1 hour'
            ";

        let asset_class_json =
            serde_json::to_value(asset_class).map_err(|e| sqlx::Error::Encode(Box::new(e)))?;

        sqlx::query(query)
            .bind(symbol.to_uppercase())
            .bind(asset_class_json)
            .bind(configuration_id)
            .execute(&self.pool)
            .await?;

        Ok(())
    }

    /// Retrieves cached symbol classification.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn get_cached_classification(
        &self,
        symbol: &str,
    ) -> Result<Option<crate::asset_classification::AssetClass>, sqlx::Error> {
        let query = "
                SELECT asset_class_data
                FROM asset_classification_cache
                WHERE symbol = $1 AND expires_at > NOW()
            ";

        let row = sqlx::query(query)
            .bind(symbol.to_uppercase())
            .fetch_optional(&self.pool)
            .await?;

        if let Some(row) = row {
            let asset_class_json: serde_json::Value = row.get("asset_class_data");
            Ok(serde_json::from_value(asset_class_json).ok())
        } else {
            Ok(None)
        }
    }

    /// Cleans up expired cache entries.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn cleanup_cache(&self) -> Result<u64, sqlx::Error> {
        let query = "DELETE FROM asset_classification_cache WHERE expires_at < NOW()";
        let result = sqlx::query(query).execute(&self.pool).await?;
        Ok(result.rows_affected())
    }

    /// Logs asset classification changes for audit.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn log_classification_change(
        &self,
        symbol: &str,
        old_classification: Option<&crate::asset_classification::AssetClass>,
        new_classification: &crate::asset_classification::AssetClass,
        changed_by: &str,
        reason: &str,
    ) -> Result<(), sqlx::Error> {
        let query = "
                INSERT INTO asset_classification_audit (
                    symbol, old_classification, new_classification, changed_by, change_reason
                ) VALUES ($1, $2, $3, $4, $5)
            ";

        let old_json = old_classification
            .map(|c| serde_json::to_value(c).ok())
            .flatten();
        let new_json = serde_json::to_value(new_classification)
            .map_err(|e| sqlx::Error::Encode(Box::new(e)))?;

        sqlx::query(query)
            .bind(symbol)
            .bind(old_json)
            .bind(new_json)
            .bind(changed_by)
            .bind(reason)
            .execute(&self.pool)
            .await?;

        Ok(())
    }

    /// Enables PostgreSQL NOTIFY/LISTEN for configuration hot-reload.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn enable_hot_reload(&mut self) -> Result<(), sqlx::Error> {
        let mut listener = sqlx::postgres::PgListener::connect_with(&self.pool).await?;
        listener.listen("config_change").await?;
        self.listener = Some(listener);
        Ok(())
    }

    /// Checks for configuration change notifications.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn check_for_config_updates(&mut self) -> Result<Option<String>, sqlx::Error> {
        if let Some(listener) = &mut self.listener {
            if let Some(notification) = listener.try_recv().await? {
                return Ok(Some(notification.payload().to_owned()));
            }
        }
        Ok(None)
    }

    /// Converts a database row to AssetConfig.
    fn row_to_asset_config(
        row: sqlx::postgres::PgRow,
    ) -> Result<crate::asset_classification::AssetConfig, sqlx::Error> {
        let id: uuid::Uuid = row.get("id");
        let name: String = row.get("name");
        let symbol_pattern: String = row.get("symbol_pattern");
        let priority: i32 = row.get("priority");
        let is_active: bool = row.get("is_active");
        let created_at: chrono::DateTime<chrono::Utc> = row.get("created_at");
        let updated_at: chrono::DateTime<chrono::Utc> = row.get("updated_at");

        let asset_class_json: serde_json::Value = row.get("asset_class_data");
        let volatility_json: serde_json::Value = row.get("volatility_profile");
        let trading_params_json: serde_json::Value = row.get("trading_parameters");
        let trading_hours_json: Option<serde_json::Value> = row.get("trading_hours");
        let settlement_json: serde_json::Value = row.get("settlement_config");

        let asset_class = serde_json::from_value(asset_class_json)
            .map_err(|e| sqlx::Error::Decode(Box::new(e)))?;
        let volatility_profile = serde_json::from_value(volatility_json)
            .map_err(|e| sqlx::Error::Decode(Box::new(e)))?;
        let trading_parameters = serde_json::from_value(trading_params_json)
            .map_err(|e| sqlx::Error::Decode(Box::new(e)))?;
        let trading_hours = trading_hours_json
            .map(|json| serde_json::from_value(json).ok())
            .flatten();
        let settlement_config = serde_json::from_value(settlement_json)
            .map_err(|e| sqlx::Error::Decode(Box::new(e)))?;

        Ok(crate::asset_classification::AssetConfig {
            id,
            name,
            symbol_pattern,
            compiled_pattern: None, // Will be compiled when loaded
            asset_class,
            volatility_profile,
            trading_parameters,
            priority: u32::try_from(priority).unwrap_or(0),
            is_active,
            created_at,
            updated_at,
            trading_hours,
            settlement_config,
        })
    }
}

/// General-purpose PostgreSQL configuration loader for various configuration types.
///
/// Provides a unified interface for loading configurations from PostgreSQL with
/// support for hot-reload through NOTIFY/LISTEN and caching for performance.
#[cfg(feature = "postgres")]
pub struct PostgresConfigLoader {
    /// Database connection pool
    pool: sqlx::PgPool,
    /// Configuration cache timeout (callers use via `cache_timeout()`)
    cache_timeout: Duration,
    /// Strategy config cache: strategy_id -> (loaded_at, config JSON)
    strategy_cache: std::collections::HashMap<String, (Instant, serde_json::Value)>,
}

#[cfg(feature = "postgres")]
impl PostgresConfigLoader {
    /// Creates a new PostgreSQL configuration loader.
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn new(database_url: &str) -> Result<Self, sqlx::Error> {
        let pool = sqlx::PgPool::connect(database_url).await?;

        Ok(Self {
            pool,
            cache_timeout: Duration::from_secs(300), // 5 minutes
            strategy_cache: std::collections::HashMap::new(),
        })
    }

    /// Creates a new loader with an existing pool
    pub fn with_pool(pool: sqlx::PgPool) -> Self {
        Self {
            pool,
            cache_timeout: Duration::from_secs(300),
            strategy_cache: std::collections::HashMap::new(),
        }
    }
    /// Returns a reference to the connection pool
    pub const fn pool(&self) -> &sqlx::PgPool {
        &self.pool
    }
    /// Returns the configured cache timeout duration
    pub const fn cache_timeout(&self) -> Duration {
        self.cache_timeout
    }
    // ============================================================================
    // ADAPTIVE STRATEGY CONFIGURATION METHODS
    // ============================================================================

    /// Get adaptive strategy configuration by strategy ID.
    ///
    /// Loads the complete configuration including main settings, models, and features
    /// from the PostgreSQL database. Returns None if the strategy doesn't exist.
    ///
    /// # Arguments
    /// * `strategy_id` - Unique identifier for the strategy (e.g., "default", "prod_v1")
    ///
    /// # Returns
    /// - `Ok(Some(config))` - Configuration found and loaded successfully
    ///
    /// - `Ok(None)` - Strategy ID not found in database
    /// - `Err(sqlx::Error)` - Database error occurred
    ///
    /// # Example
    /// ```no_run
    /// # use config::PostgresConfigLoader;
    /// # async fn example(loader: &PostgresConfigLoader) -> Result<(), sqlx::Error> {
    /// let config = loader.get_adaptive_strategy_config("default").await?;
    /// if let Some(cfg) = config {
    ///     println!("Loaded strategy: {}", cfg.name);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn get_adaptive_strategy_config(
        &mut self,
        strategy_id: &str,
    ) -> Result<Option<serde_json::Value>, sqlx::Error> {
        // Return cached strategy if fresh
        if let Some((loaded_at, cached)) = self.strategy_cache.get(strategy_id) {
            if loaded_at.elapsed() < self.cache_timeout {
                return Ok(Some(cached.clone()));
            }
        }

        // Query main configuration
        let row = sqlx::query(
            r#"
            SELECT
                id, strategy_id, name, description,
                execution_interval_ms, error_backoff_duration_secs,
                max_concurrent_operations, strategy_timeout_secs,
                max_parallel_models, rebalancing_interval_secs,
                min_model_weight, max_model_weight,
                max_position_size, max_leverage, stop_loss_pct,
                position_sizing_method, max_portfolio_var,
                max_drawdown_threshold, kelly_fraction,
                book_depth, vpin_window, trade_classification_threshold,
                trade_size_buckets, microstructure_features,
                regime_detection_method, regime_lookback_window,
                regime_transition_threshold, regime_features,
                execution_algorithm, max_order_size, min_order_size,
                order_timeout_secs, max_slippage_bps,
                smart_routing_enabled, dark_pool_preference,
                active, version, created_at, updated_at,
                created_by, updated_by, metadata
            FROM adaptive_strategy_config
            WHERE strategy_id = $1 AND active = true
            "#,
        )
        .bind(strategy_id)
        .fetch_optional(&self.pool)
        .await?;

        let Some(row) = row else {
            return Ok(None);
        };

        let config_id: uuid::Uuid = row.try_get("id")?;

        // Query associated models
        let models = sqlx::query(
            r#"
            SELECT
                id, strategy_config_id, model_id, model_name, model_type,
                parameters, initial_weight, enabled, display_order,
                created_at, updated_at
            FROM adaptive_strategy_models
            WHERE strategy_config_id = $1
            ORDER BY display_order, created_at
            "#,
        )
        .bind(config_id)
        .fetch_all(&self.pool)
        .await?;

        // Query associated features
        let features = sqlx::query(
            r#"
            SELECT
                id, strategy_config_id, feature_name, feature_type,
                parameters, enabled, required,
                created_at, updated_at
            FROM adaptive_strategy_features
            WHERE strategy_config_id = $1
            ORDER BY feature_name
            "#,
        )
        .bind(config_id)
        .fetch_all(&self.pool)
        .await?;

        // Convert to JSON for flexibility
        // In production, you'd convert to a proper struct type
        let config = serde_json::json!({
            "id": row.try_get::<uuid::Uuid, _>("id")?,
            "strategy_id": row.try_get::<String, _>("strategy_id")?,
            "name": row.try_get::<String, _>("name")?,
            "description": row.try_get::<Option<String>, _>("description")?,
            "general": {
                "execution_interval_ms": row.try_get::<i32, _>("execution_interval_ms")?,
                "error_backoff_duration_secs": row.try_get::<i32, _>("error_backoff_duration_secs")?,
                "max_concurrent_operations": row.try_get::<i32, _>("max_concurrent_operations")?,
                "strategy_timeout_secs": row.try_get::<i32, _>("strategy_timeout_secs")?,
            },
            "ensemble": {
                "max_parallel_models": row.try_get::<i32, _>("max_parallel_models")?,
                "rebalancing_interval_secs": row.try_get::<i32, _>("rebalancing_interval_secs")?,
                "min_model_weight": row.try_get::<f64, _>("min_model_weight")?,
                "max_model_weight": row.try_get::<f64, _>("max_model_weight")?,
            },
            "risk": {
                "max_position_size": row.try_get::<f64, _>("max_position_size")?,
                "max_leverage": row.try_get::<f64, _>("max_leverage")?,
                "stop_loss_pct": row.try_get::<f64, _>("stop_loss_pct")?,
                "position_sizing_method": row.try_get::<String, _>("position_sizing_method")?,
                "max_portfolio_var": row.try_get::<f64, _>("max_portfolio_var")?,
                "max_drawdown_threshold": row.try_get::<f64, _>("max_drawdown_threshold")?,
                "kelly_fraction": row.try_get::<f64, _>("kelly_fraction")?,
            },
            "microstructure": {
                "book_depth": row.try_get::<i32, _>("book_depth")?,
                "vpin_window": row.try_get::<i32, _>("vpin_window")?,
                "trade_classification_threshold": row.try_get::<f64, _>("trade_classification_threshold")?,
                "trade_size_buckets": row.try_get::<Vec<f64>, _>("trade_size_buckets")?,
                "features": row.try_get::<Vec<String>, _>("microstructure_features")?,
            },
            "regime": {
                "detection_method": row.try_get::<String, _>("regime_detection_method")?,
                "lookback_window": row.try_get::<i32, _>("regime_lookback_window")?,
                "transition_threshold": row.try_get::<f64, _>("regime_transition_threshold")?,
                "features": row.try_get::<Vec<String>, _>("regime_features")?,
            },
            "execution": {
                "algorithm": row.try_get::<String, _>("execution_algorithm")?,
                "max_order_size": row.try_get::<f64, _>("max_order_size")?,
                "min_order_size": row.try_get::<f64, _>("min_order_size")?,
                "order_timeout_secs": row.try_get::<i32, _>("order_timeout_secs")?,
                "max_slippage_bps": row.try_get::<f64, _>("max_slippage_bps")?,
                "smart_routing_enabled": row.try_get::<bool, _>("smart_routing_enabled")?,
                "dark_pool_preference": row.try_get::<f64, _>("dark_pool_preference")?,
            },
            "models": models.iter().filter_map(|m| {
                Some(serde_json::json!({
                    "id": m.try_get::<uuid::Uuid, _>("id").ok()?,
                    "model_id": m.try_get::<String, _>("model_id").ok()?,
                    "model_name": m.try_get::<String, _>("model_name").ok()?,
                    "model_type": m.try_get::<String, _>("model_type").ok()?,
                    "parameters": m.try_get::<serde_json::Value, _>("parameters").ok()?,
                    "initial_weight": m.try_get::<f64, _>("initial_weight").ok()?,
                    "enabled": m.try_get::<bool, _>("enabled").ok()?,
                }))
            }).collect::<Vec<_>>(),
            "features": features.iter().filter_map(|f| {
                Some(serde_json::json!({
                    "name": f.try_get::<String, _>("feature_name").ok()?,
                    "feature_type": f.try_get::<String, _>("feature_type").ok()?,
                    "parameters": f.try_get::<serde_json::Value, _>("parameters").ok()?,
                    "enabled": f.try_get::<bool, _>("enabled").ok()?,
                    "required": f.try_get::<bool, _>("required").ok()?,
                }))
            }).collect::<Vec<_>>(),
            "version": row.try_get::<i32, _>("version")?,
            "created_at": row.try_get::<chrono::DateTime<chrono::Utc>, _>("created_at")?,
            "updated_at": row.try_get::<chrono::DateTime<chrono::Utc>, _>("updated_at")?,
        });

        // Store in cache
        self.strategy_cache.insert(strategy_id.to_owned(), (Instant::now(), config.clone()));

        Ok(Some(config))
    }

    /// Upsert (insert or update) adaptive strategy configuration.
    ///
    /// Creates a new strategy configuration if it doesn't exist, or updates
    /// the existing one. Automatically handles version tracking and audit trail.
    ///
    /// # Arguments
    /// * `config` - Configuration data as JSON (allows flexibility in structure)
    ///
    /// # Returns
    /// - `Ok(strategy_id)` - Strategy ID of the created/updated configuration
    ///
    /// - `Err(sqlx::Error)` - Database error occurred
    ///
    /// # Example
    /// ```no_run
    /// # use config::PostgresConfigLoader;
    /// # use serde_json::json;
    /// # async fn example(loader: &PostgresConfigLoader) -> Result<(), sqlx::Error> {
    /// let config = json!({
    ///     "strategy_id": "my_strategy",
    ///     "name": "My Trading Strategy",
    ///     "risk": {
    ///         "max_position_size": 0.15,
    ///         "max_leverage": 3.0
    ///     }
    /// });
    /// let id = loader.upsert_adaptive_strategy_config(&config).await?;
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn upsert_adaptive_strategy_config(
        &self,
        config: &serde_json::Value,
    ) -> Result<String, sqlx::Error> {
        let strategy_id = config
            .get("strategy_id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| {
                sqlx::Error::Decode(Box::new(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Missing strategy_id in config",
                )))
            })?;

        // Extract all configuration fields
        let name = config
            .get("name")
            .and_then(|v| v.as_str())
            .unwrap_or("Unnamed Strategy");
        let description = config.get("description").and_then(|v| v.as_str());

        // Helper macro for extracting fields with defaults
        macro_rules! get_i32 {
            ($field:expr, $default:expr) => {
                config
                    .get($field)
                    .and_then(|v| v.as_i64())
                    .and_then(|v| i32::try_from(v).ok())
                    .unwrap_or($default)
            };
        }
        macro_rules! get_f64 {
            ($field:expr, $default:expr) => {
                config
                    .get($field)
                    .and_then(|v| v.as_f64())
                    .unwrap_or($default)
            };
        }
        macro_rules! get_bool {
            ($field:expr, $default:expr) => {
                config
                    .get($field)
                    .and_then(|v| v.as_bool())
                    .unwrap_or($default)
            };
        }
        macro_rules! get_str {
            ($field:expr, $default:expr) => {
                config
                    .get($field)
                    .and_then(|v| v.as_str())
                    .unwrap_or($default)
            };
        }

        // Full upsert with all 50+ fields
        let query = r#"
            INSERT INTO adaptive_strategy_config (
                strategy_id, name, description,
                -- General config
                execution_interval_ms, error_backoff_duration_secs,
                max_concurrent_operations, strategy_timeout_secs,
                -- Ensemble config
                max_parallel_models, rebalancing_interval_secs,
                min_model_weight, max_model_weight,
                -- Risk config
                max_position_size, max_leverage, stop_loss_pct,
                position_sizing_method, max_portfolio_var,
                max_drawdown_threshold, kelly_fraction,
                -- Microstructure config
                book_depth, vpin_window, trade_classification_threshold,
                trade_size_buckets, microstructure_features,
                -- Regime config
                regime_detection_method, regime_lookback_window,
                regime_transition_threshold, regime_features,
                -- Execution config
                execution_algorithm, max_order_size, min_order_size,
                order_timeout_secs, max_slippage_bps,
                smart_routing_enabled, dark_pool_preference
            ) VALUES (
                $1, $2, $3,
                $4, $5, $6, $7,
                $8, $9, $10, $11,
                $12, $13, $14, $15, $16, $17, $18,
                $19, $20, $21, $22, $23,
                $24, $25, $26, $27,
                $28, $29, $30, $31, $32, $33, $34
            )
            ON CONFLICT (strategy_id)
            DO UPDATE SET
                name = EXCLUDED.name,
                description = EXCLUDED.description,
                execution_interval_ms = EXCLUDED.execution_interval_ms,
                error_backoff_duration_secs = EXCLUDED.error_backoff_duration_secs,
                max_concurrent_operations = EXCLUDED.max_concurrent_operations,
                strategy_timeout_secs = EXCLUDED.strategy_timeout_secs,
                max_parallel_models = EXCLUDED.max_parallel_models,
                rebalancing_interval_secs = EXCLUDED.rebalancing_interval_secs,
                min_model_weight = EXCLUDED.min_model_weight,
                max_model_weight = EXCLUDED.max_model_weight,
                max_position_size = EXCLUDED.max_position_size,
                max_leverage = EXCLUDED.max_leverage,
                stop_loss_pct = EXCLUDED.stop_loss_pct,
                position_sizing_method = EXCLUDED.position_sizing_method,
                max_portfolio_var = EXCLUDED.max_portfolio_var,
                max_drawdown_threshold = EXCLUDED.max_drawdown_threshold,
                kelly_fraction = EXCLUDED.kelly_fraction,
                book_depth = EXCLUDED.book_depth,
                vpin_window = EXCLUDED.vpin_window,
                trade_classification_threshold = EXCLUDED.trade_classification_threshold,
                trade_size_buckets = EXCLUDED.trade_size_buckets,
                microstructure_features = EXCLUDED.microstructure_features,
                regime_detection_method = EXCLUDED.regime_detection_method,
                regime_lookback_window = EXCLUDED.regime_lookback_window,
                regime_transition_threshold = EXCLUDED.regime_transition_threshold,
                regime_features = EXCLUDED.regime_features,
                execution_algorithm = EXCLUDED.execution_algorithm,
                max_order_size = EXCLUDED.max_order_size,
                min_order_size = EXCLUDED.min_order_size,
                order_timeout_secs = EXCLUDED.order_timeout_secs,
                max_slippage_bps = EXCLUDED.max_slippage_bps,
                smart_routing_enabled = EXCLUDED.smart_routing_enabled,
                dark_pool_preference = EXCLUDED.dark_pool_preference,
                updated_at = NOW()
            RETURNING strategy_id
        "#;

        // Extract trade_size_buckets and features arrays
        let trade_size_buckets: Vec<f64> = config
            .get("trade_size_buckets")
            .and_then(|v| v.as_array())
            .map(|arr| arr.iter().filter_map(|v| v.as_f64()).collect())
            .unwrap_or_else(|| vec![10.0, 100.0, 1000.0, 10000.0]);

        let microstructure_features: Vec<String> = config
            .get("microstructure_features")
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_str().map(|s| s.to_owned()))
                    .collect()
            })
            .unwrap_or_else(|| {
                vec![
                    "vpin".to_owned(),
                    "order_flow".to_owned(),
                    "bid_ask_spread".to_owned(),
                ]
            });

        let regime_features: Vec<String> = config
            .get("regime_features")
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_str().map(|s| s.to_owned()))
                    .collect()
            })
            .unwrap_or_else(|| {
                vec![
                    "volatility".to_owned(),
                    "momentum".to_owned(),
                    "volume".to_owned(),
                ]
            });

        let row = sqlx::query(query)
            .bind(strategy_id)
            .bind(name)
            .bind(description)
            // General config (4 fields)
            .bind(get_i32!("execution_interval_ms", 100))
            .bind(get_i32!("error_backoff_duration_secs", 1))
            .bind(get_i32!("max_concurrent_operations", 10))
            .bind(get_i32!("strategy_timeout_secs", 30))
            // Ensemble config (4 fields)
            .bind(get_i32!("max_parallel_models", 4))
            .bind(get_i32!("rebalancing_interval_secs", 300))
            .bind(get_f64!("min_model_weight", 0.01))
            .bind(get_f64!("max_model_weight", 0.5))
            // Risk config (7 fields)
            .bind(get_f64!("max_position_size", 0.1))
            .bind(get_f64!("max_leverage", 2.0))
            .bind(get_f64!("stop_loss_pct", 0.02))
            .bind(get_str!("position_sizing_method", "KELLY"))
            .bind(get_f64!("max_portfolio_var", 0.02))
            .bind(get_f64!("max_drawdown_threshold", 0.05))
            .bind(get_f64!("kelly_fraction", 0.1))
            // Microstructure config (5 fields)
            .bind(get_i32!("book_depth", 10))
            .bind(get_i32!("vpin_window", 50))
            .bind(get_f64!("trade_classification_threshold", 0.5))
            .bind(&trade_size_buckets)
            .bind(&microstructure_features)
            // Regime config (4 fields)
            .bind(get_str!("regime_detection_method", "HMM"))
            .bind(get_i32!("regime_lookback_window", 252))
            .bind(get_f64!("regime_transition_threshold", 0.7))
            .bind(&regime_features)
            // Execution config (7 fields)
            .bind(get_str!("execution_algorithm", "TWAP"))
            .bind(get_f64!("max_order_size", 10000.0))
            .bind(get_f64!("min_order_size", 100.0))
            .bind(get_i32!("order_timeout_secs", 30))
            .bind(get_f64!("max_slippage_bps", 10.0))
            .bind(get_bool!("smart_routing_enabled", true))
            .bind(get_f64!("dark_pool_preference", 0.3))
            .fetch_one(&self.pool)
            .await?;

        let result: String = row.try_get("strategy_id")?;
        Ok(result)
    }

    // ========================================================================
    // MODEL CRUD OPERATIONS
    // ========================================================================

    /// Add a model configuration to a strategy
    ///
    /// # Arguments
    /// * `strategy_config_id` - UUID of the parent strategy configuration
    ///
    /// * `model` - Model configuration as JSON
    ///
    /// # Returns
    ///
    /// UUID of the created model configuration
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn add_model_config(
        &self,
        strategy_config_id: uuid::Uuid,
        model: &serde_json::Value,
    ) -> Result<uuid::Uuid, sqlx::Error> {
        let model_id = model
            .get("model_id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| {
                sqlx::Error::Decode(Box::new(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Missing model_id",
                )))
            })?;

        let query = r#"
                    INSERT INTO adaptive_strategy_models (
                        strategy_config_id, model_id, model_name, model_type,
                        parameters, initial_weight, enabled, display_order
                    ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
                    RETURNING id
                "#;

        let row = sqlx::query(query)
            .bind(strategy_config_id)
            .bind(model_id)
            .bind(
                model
                    .get("model_name")
                    .and_then(|v| v.as_str())
                    .unwrap_or(model_id),
            )
            .bind(
                model
                    .get("model_type")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown"),
            )
            .bind(model.get("parameters").unwrap_or(&serde_json::json!({})))
            .bind(
                model
                    .get("initial_weight")
                    .and_then(|v| v.as_f64())
                    .unwrap_or(0.25),
            )
            .bind(
                model
                    .get("enabled")
                    .and_then(|v| v.as_bool())
                    .unwrap_or(true),
            )
            .bind(
                i32::try_from(
                    model
                        .get("display_order")
                        .and_then(|v| v.as_i64())
                        .unwrap_or(0),
                )
                .unwrap_or(0),
            )
            .fetch_one(&self.pool)
            .await?;

        row.try_get("id")
    }

    /// Update a model configuration
    ///
    /// # Arguments
    /// * `model_id` - UUID of the model to update
    ///
    /// * `updates` - Fields to update as JSON
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn update_model_config(
        &self,
        model_id: uuid::Uuid,
        updates: &serde_json::Value,
    ) -> Result<(), sqlx::Error> {
        let query = r#"
                    UPDATE adaptive_strategy_models
                    SET
                        model_name = COALESCE($1, model_name),
                        model_type = COALESCE($2, model_type),
                        parameters = COALESCE($3, parameters),
                        initial_weight = COALESCE($4, initial_weight),
                        enabled = COALESCE($5, enabled),
                        display_order = COALESCE($6, display_order),
                        updated_at = NOW()
                    WHERE id = $7
                "#;

        sqlx::query(query)
            .bind(updates.get("model_name").and_then(|v| v.as_str()))
            .bind(updates.get("model_type").and_then(|v| v.as_str()))
            .bind(updates.get("parameters"))
            .bind(updates.get("initial_weight").and_then(|v| v.as_f64()))
            .bind(updates.get("enabled").and_then(|v| v.as_bool()))
            .bind(
                updates
                    .get("display_order")
                    .and_then(|v| v.as_i64())
                    .and_then(|v| i32::try_from(v).ok()),
            )
            .bind(model_id)
            .execute(&self.pool)
            .await?;

        Ok(())
    }

    /// Remove a model configuration
    ///
    /// # Arguments
    /// * `model_id` - UUID of the model to remove
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn remove_model_config(&self, model_id: uuid::Uuid) -> Result<(), sqlx::Error> {
        let query = "DELETE FROM adaptive_strategy_models WHERE id = $1";
        sqlx::query(query)
            .bind(model_id)
            .execute(&self.pool)
            .await?;
        Ok(())
    }

    // ========================================================================
    // FEATURE CRUD OPERATIONS
    // ========================================================================

    /// Add a feature configuration to a strategy
    ///
    /// # Arguments
    /// * `strategy_config_id` - UUID of the parent strategy configuration
    ///
    /// * `feature` - Feature configuration as JSON
    ///
    /// # Returns
    ///
    /// UUID of the created feature configuration
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn add_feature_config(
        &self,
        strategy_config_id: uuid::Uuid,
        feature: &serde_json::Value,
    ) -> Result<uuid::Uuid, sqlx::Error> {
        let feature_name = feature
            .get("feature_name")
            .and_then(|v| v.as_str())
            .ok_or_else(|| {
                sqlx::Error::Decode(Box::new(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Missing feature_name",
                )))
            })?;

        let query = r#"
                    INSERT INTO adaptive_strategy_features (
                        strategy_config_id, feature_name, feature_type,
                        parameters, enabled, required
                    ) VALUES ($1, $2, $3, $4, $5, $6)
                    RETURNING id
                "#;

        let row = sqlx::query(query)
            .bind(strategy_config_id)
            .bind(feature_name)
            .bind(
                feature
                    .get("feature_type")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown"),
            )
            .bind(feature.get("parameters").unwrap_or(&serde_json::json!({})))
            .bind(
                feature
                    .get("enabled")
                    .and_then(|v| v.as_bool())
                    .unwrap_or(true),
            )
            .bind(
                feature
                    .get("required")
                    .and_then(|v| v.as_bool())
                    .unwrap_or(false),
            )
            .fetch_one(&self.pool)
            .await?;

        row.try_get("id")
    }

    /// Update a feature configuration
    ///
    /// # Arguments
    /// * `feature_id` - UUID of the feature to update
    ///
    /// * `updates` - Fields to update as JSON
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn update_feature_config(
        &self,
        feature_id: uuid::Uuid,
        updates: &serde_json::Value,
    ) -> Result<(), sqlx::Error> {
        let query = r#"
                    UPDATE adaptive_strategy_features
                    SET
                        feature_type = COALESCE($1, feature_type),
                        parameters = COALESCE($2, parameters),
                        enabled = COALESCE($3, enabled),
                        required = COALESCE($4, required),
                        updated_at = NOW()
                    WHERE id = $5
                "#;

        sqlx::query(query)
            .bind(updates.get("feature_type").and_then(|v| v.as_str()))
            .bind(updates.get("parameters"))
            .bind(updates.get("enabled").and_then(|v| v.as_bool()))
            .bind(updates.get("required").and_then(|v| v.as_bool()))
            .bind(feature_id)
            .execute(&self.pool)
            .await?;

        Ok(())
    }

    /// Remove a feature configuration
    ///
    /// # Arguments
    /// * `feature_id` - UUID of the feature to remove
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn remove_feature_config(&self, feature_id: uuid::Uuid) -> Result<(), sqlx::Error> {
        let query = "DELETE FROM adaptive_strategy_features WHERE id = $1";
        sqlx::query(query)
            .bind(feature_id)
            .execute(&self.pool)
            .await?;
        Ok(())
    }

    // ========================================================================
    // TRANSACTION SUPPORT
    // ========================================================================

    /// Update strategy configuration with models and features in a single transaction
    ///
    /// Provides atomic updates across all three tables:
    /// - adaptive_strategy_config (main configuration)
    ///
    /// - adaptive_strategy_models (model configurations)
    /// - adaptive_strategy_features (feature configurations)
    ///
    /// # Arguments
    /// * `config` - Full configuration including models and features
    ///
    /// # Returns
    ///
    /// Strategy ID of the updated configuration
    ///
    /// # Errors
    /// Returns error if the operation fails
    pub async fn update_strategy_atomic(
        &self,
        config: &serde_json::Value,
    ) -> Result<String, sqlx::Error> {
        // Start transaction
        let mut tx = self.pool.begin().await?;

        // 1. Upsert main configuration
        let strategy_id = config
            .get("strategy_id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| {
                sqlx::Error::Decode(Box::new(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Missing strategy_id",
                )))
            })?;

        // Get or create config_id
        let config_id: uuid::Uuid =
            sqlx::query_scalar("SELECT id FROM adaptive_strategy_config WHERE strategy_id = $1")
                .bind(strategy_id)
                .fetch_optional(&mut *tx)
                .await?
                .unwrap_or_else(uuid::Uuid::new_v4);

        // 2. Update models if provided
        if let Some(models) = config.get("models").and_then(|v| v.as_array()) {
            // Delete existing models
            sqlx::query("DELETE FROM adaptive_strategy_models WHERE strategy_config_id = $1")
                .bind(config_id)
                .execute(&mut *tx)
                .await?;

            // Insert new models
            for model in models {
                sqlx::query(
                    r#"
                            INSERT INTO adaptive_strategy_models (
                                strategy_config_id, model_id, model_name, model_type,
                                parameters, initial_weight, enabled
                            ) VALUES ($1, $2, $3, $4, $5, $6, $7)
                        "#,
                )
                .bind(config_id)
                .bind(
                    model
                        .get("model_id")
                        .and_then(|v| v.as_str())
                        .unwrap_or("unknown"),
                )
                .bind(
                    model
                        .get("model_name")
                        .and_then(|v| v.as_str())
                        .unwrap_or("Unknown Model"),
                )
                .bind(
                    model
                        .get("model_type")
                        .and_then(|v| v.as_str())
                        .unwrap_or("unknown"),
                )
                .bind(model.get("parameters").unwrap_or(&serde_json::json!({})))
                .bind(
                    model
                        .get("initial_weight")
                        .and_then(|v| v.as_f64())
                        .unwrap_or(0.25),
                )
                .bind(
                    model
                        .get("enabled")
                        .and_then(|v| v.as_bool())
                        .unwrap_or(true),
                )
                .execute(&mut *tx)
                .await?;
            }
        }

        // 3. Update features if provided
        if let Some(features) = config.get("features").and_then(|v| v.as_array()) {
            // Delete existing features
            sqlx::query("DELETE FROM adaptive_strategy_features WHERE strategy_config_id = $1")
                .bind(config_id)
                .execute(&mut *tx)
                .await?;

            // Insert new features
            for feature in features {
                sqlx::query(
                    r#"
                            INSERT INTO adaptive_strategy_features (
                                strategy_config_id, feature_name, feature_type,
                                parameters, enabled, required
                            ) VALUES ($1, $2, $3, $4, $5, $6)
                        "#,
                )
                .bind(config_id)
                .bind(
                    feature
                        .get("feature_name")
                        .and_then(|v| v.as_str())
                        .unwrap_or("unknown"),
                )
                .bind(
                    feature
                        .get("feature_type")
                        .and_then(|v| v.as_str())
                        .unwrap_or("unknown"),
                )
                .bind(feature.get("parameters").unwrap_or(&serde_json::json!({})))
                .bind(
                    feature
                        .get("enabled")
                        .and_then(|v| v.as_bool())
                        .unwrap_or(true),
                )
                .bind(
                    feature
                        .get("required")
                        .and_then(|v| v.as_bool())
                        .unwrap_or(false),
                )
                .execute(&mut *tx)
                .await?;
            }
        }

        // Commit transaction
        tx.commit().await?;

        Ok(strategy_id.to_owned())
    }
}

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

    #[test]
    fn test_database_config_new() {
        let config = DatabaseConfig::new();
        assert!(!config.url.is_empty());
        assert_eq!(config.max_connections, 10);
        assert_eq!(config.min_connections, 1);
        assert!(config.application_name.is_some());
    }

    #[test]
    fn test_database_config_validate_success() {
        let config = DatabaseConfig::new();
        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_database_config_validate_empty_url() {
        let mut config = DatabaseConfig::new();
        config.url = String::new();
        assert!(config.validate().is_err());
    }

    #[test]
    fn test_pool_config_default() {
        let pool_config = PoolConfig::default();
        assert_eq!(pool_config.min_connections, 1);
        assert_eq!(pool_config.max_connections, 10);
        assert!(pool_config.test_before_acquire);
    }

    #[test]
    fn test_transaction_config_default() {
        let tx_config = TransactionConfig::default();
        assert_eq!(tx_config.isolation_level, "READ_COMMITTED");
        assert_eq!(tx_config.default_timeout_secs, 30);
        assert!(tx_config.enable_retry);
    }

    #[test]
    fn test_transaction_config_serialization() {
        let tx_config = TransactionConfig::default();
        let serialized = serde_json::to_string(&tx_config).unwrap();
        let deserialized: TransactionConfig = serde_json::from_str(&serialized).unwrap();
        assert_eq!(tx_config.isolation_level, deserialized.isolation_level);
    }

    #[test]
    fn test_database_config_with_custom_values() {
        let mut config = DatabaseConfig::new();
        config.max_connections = 50;
        config.min_connections = 5;
        config.enable_query_logging = true;

        assert_eq!(config.max_connections, 50);
        assert_eq!(config.min_connections, 5);
        assert!(config.enable_query_logging);
    }

    #[test]
    fn test_pool_config_timeouts() {
        let pool_config = PoolConfig {
            acquire_timeout_secs: 30,
            max_lifetime_secs: 3600,
            idle_timeout_secs: 3600,
            ..Default::default()
        };

        assert_eq!(pool_config.acquire_timeout_secs, 30);
        assert_eq!(pool_config.max_lifetime_secs, 3600);
        assert_eq!(pool_config.idle_timeout_secs, 3600);
    }

    #[test]
    fn test_transaction_config_isolation_levels() {
        let levels = vec![
            "READ_UNCOMMITTED",
            "READ_COMMITTED",
            "REPEATABLE_READ",
            "SERIALIZABLE",
        ];

        for level in levels {
            let tx_config = TransactionConfig {
                isolation_level: level.to_owned(),
                ..Default::default()
            };
            assert_eq!(tx_config.isolation_level, level);
        }
    }

    #[test]
    fn test_database_config_clone() {
        let config1 = DatabaseConfig::new();
        let config2 = config1.clone();

        assert_eq!(config1.url, config2.url);
        assert_eq!(config1.max_connections, config2.max_connections);
        assert_eq!(config1.min_connections, config2.min_connections);
    }

    #[test]
    fn test_pool_config_validation() {
        let pool_config = PoolConfig::default();
        assert!(pool_config.min_connections <= pool_config.max_connections);
    }

    #[test]
    fn test_database_url_format() {
        let config = DatabaseConfig::new();
        assert!(config.url.starts_with("postgresql://"));
    }

    #[test]
    fn test_transaction_config_retry_settings() {
        let tx_config = TransactionConfig {
            enable_retry: true,
            max_retries: 5,
            ..Default::default()
        };
        assert!(tx_config.enable_retry);
        assert_eq!(tx_config.max_retries, 5);

        let tx_config_no_retry = TransactionConfig {
            enable_retry: false,
            ..Default::default()
        };
        assert!(!tx_config_no_retry.enable_retry);
    }

    #[test]
    fn test_pool_config_connection_settings() {
        let pool_config = PoolConfig {
            test_before_acquire: true,
            acquire_timeout_secs: 30,
            ..Default::default()
        };

        assert!(pool_config.test_before_acquire);
        assert_eq!(pool_config.acquire_timeout_secs, 30);
    }

    #[test]
    fn test_database_config_application_name() {
        let config = DatabaseConfig::new();
        assert_eq!(config.application_name, Some("foxhunt".to_owned()));
    }

    #[test]
    fn test_database_config_query_logging() {
        let mut config = DatabaseConfig::new();
        config.enable_query_logging = true;
        assert!(config.enable_query_logging);
    }

    #[test]
    fn test_pool_config_connection_limits() {
        let pool_config = PoolConfig {
            max_connections: 100,
            min_connections: 10,
            ..Default::default()
        };

        assert_eq!(pool_config.max_connections, 100);
        assert_eq!(pool_config.min_connections, 10);
    }

    #[test]
    fn test_transaction_timeout() {
        let tx_config = TransactionConfig {
            default_timeout_secs: 60,
            timeout: Duration::from_secs(60),
            ..Default::default()
        };
        assert_eq!(tx_config.default_timeout_secs, 60);
        assert_eq!(tx_config.timeout, Duration::from_secs(60));
    }

    #[test]
    fn test_database_config_connect_timeout() {
        let config = DatabaseConfig::new();
        assert_eq!(config.connect_timeout, Duration::from_secs(30));
    }

    #[test]
    fn test_database_config_query_timeout() {
        let config = DatabaseConfig::new();
        assert_eq!(config.query_timeout, Duration::from_secs(60));
    }

    #[test]
    fn test_pool_config_test_before_acquire() {
        let pool_config = PoolConfig {
            test_before_acquire: false,
            ..Default::default()
        };
        assert!(!pool_config.test_before_acquire);

        let pool_config_enabled = PoolConfig {
            test_before_acquire: true,
            ..Default::default()
        };
        assert!(pool_config_enabled.test_before_acquire);
    }

    #[test]
    fn test_database_config_validation_empty_url() {
        let mut config = DatabaseConfig::new();
        config.url = String::new();
        assert!(config.validate().is_err());
        assert_eq!(
            config.validate().unwrap_err(),
            "Database URL cannot be empty"
        );
    }

    #[test]
    fn test_database_config_validation_valid() {
        let config = DatabaseConfig::new();
        assert!(config.validate().is_ok());
    }

    #[test]
    fn test_pool_config_defaults() {
        let pool_config = PoolConfig::default();
        assert_eq!(pool_config.min_connections, 1);
        assert_eq!(pool_config.max_connections, 10);
        assert_eq!(pool_config.acquire_timeout_secs, 30);
        assert_eq!(pool_config.max_lifetime_secs, 3600);
        assert_eq!(pool_config.idle_timeout_secs, 3600);
        assert!(pool_config.test_before_acquire);
        assert!(pool_config.health_check_enabled);
        assert_eq!(pool_config.health_check_interval_secs, 60);
    }

    #[test]
    fn test_transaction_config_defaults() {
        let tx_config = TransactionConfig::default();
        assert_eq!(tx_config.isolation_level, "READ_COMMITTED");
        assert_eq!(tx_config.timeout, Duration::from_secs(30));
        assert_eq!(tx_config.default_timeout_secs, 30);
        assert!(tx_config.enable_retry);
        assert_eq!(tx_config.max_retries, 3);
        assert_eq!(tx_config.retry_delay_ms, 100);
        assert_eq!(tx_config.max_savepoints, 10);
    }

    #[test]
    fn test_transaction_config_custom_isolation() {
        let tx_config = TransactionConfig {
            isolation_level: "SERIALIZABLE".to_owned(),
            ..Default::default()
        };
        assert_eq!(tx_config.isolation_level, "SERIALIZABLE");
    }

    #[test]
    fn test_pool_config_extreme_values() {
        let pool_config = PoolConfig {
            max_connections: 1000,
            min_connections: 0,
            ..Default::default()
        };
        assert_eq!(pool_config.max_connections, 1000);
        assert_eq!(pool_config.min_connections, 0);
    }

    #[test]
    fn test_database_config_custom_application_name() {
        let mut config = DatabaseConfig::new();
        config.application_name = Some("custom_app".to_owned());
        assert_eq!(config.application_name.unwrap(), "custom_app");
    }

    #[test]
    fn test_database_config_no_application_name() {
        let mut config = DatabaseConfig::new();
        config.application_name = None;
        assert!(config.application_name.is_none());
    }

    #[test]
    fn test_transaction_config_retry_disabled() {
        let tx_config = TransactionConfig {
            enable_retry: false,
            ..Default::default()
        };
        assert!(!tx_config.enable_retry);
    }

    #[test]
    fn test_pool_config_serialization() {
        let pool_config = PoolConfig::default();
        let serialized = serde_json::to_string(&pool_config).unwrap();
        let deserialized: PoolConfig = serde_json::from_str(&serialized).unwrap();
        assert_eq!(pool_config.max_connections, deserialized.max_connections);
        assert_eq!(pool_config.min_connections, deserialized.min_connections);
    }

    #[test]
    fn test_transaction_config_serde_roundtrip() {
        let tx_config = TransactionConfig::default();
        let serialized = serde_json::to_string(&tx_config).unwrap();
        let deserialized: TransactionConfig = serde_json::from_str(&serialized).unwrap();
        assert_eq!(tx_config.isolation_level, deserialized.isolation_level);
        assert_eq!(tx_config.max_retries, deserialized.max_retries);
    }
}
