//! Autonomous Capital-Based Asset Scaling
//!
//! Implements intelligent universe scaling based on available capital,
//! system constraints, and performance metrics.
//!
//! # Design Principles
//!
//! - Start conservative (3-6 highly liquid symbols)
//! - Expand gradually as capital/performance proves out
//! - Respect system limits (latency, compute, risk)
//! - Maintain diversification
//! - Auto-downgrade on performance degradation

use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::OnceCell;
use tonic::transport::Channel;
use uuid::Uuid;

use crate::universe::{Instrument, UniverseError};
use common::Symbol;

/// Error types for autonomous scaling
#[derive(Debug, thiserror::Error)]
pub enum ScalingError {
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("Universe error: {0}")]
    Universe(#[from] UniverseError),

    #[error("System constraint violation: {0}")]
    ConstraintViolation(String),

    #[error("Invalid capital amount: {0}")]
    InvalidCapital(f64),

    #[error("Performance below threshold: {0}")]
    PerformanceBelowThreshold(String),

    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),

    #[error("Scaling not enabled")]
    NotEnabled,
}

/// Position sizing modes for different capital tiers
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum PositionSizingMode {
    /// Simple equal weighting across all positions
    EqualWeight,

    /// ML-optimized weights based on model confidence
    MLOptimized,

    /// Risk parity allocation (equal risk contribution)
    RiskParity,

    /// Mean-variance optimization (Markowitz)
    MeanVariance,

    /// Kelly criterion for optimal bet sizing
    Kelly,

    /// Black-Litterman model (views + market equilibrium)
    BlackLitterman,
}

/// Capital scaling tier definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapitalScalingTier {
    /// Tier number (1-6)
    pub tier: u32,

    /// Minimum capital required for this tier
    pub min_capital: f64,

    /// Maximum number of symbols to trade
    pub max_symbols: usize,

    /// Minimum daily liquidity (USD)
    pub min_liquidity: f64,

    /// Maximum correlation threshold (0.0-1.0)
    pub max_correlation: f64,

    /// Position sizing mode for this tier
    pub position_sizing: PositionSizingMode,

    /// Minimum Sharpe ratio required to maintain tier
    pub min_sharpe_ratio: f64,

    /// Description of tier characteristics
    pub description: String,
}

impl CapitalScalingTier {
    /// Get all predefined scaling tiers
    pub fn all_tiers() -> Vec<Self> {
        vec![
            // Tier 1: Beginner (start here)
            Self {
                tier: 1,
                min_capital: 10_000.0,
                max_symbols: 3,
                min_liquidity: 5_000_000.0, // $5M daily volume
                max_correlation: 0.7,
                position_sizing: PositionSizingMode::EqualWeight,
                min_sharpe_ratio: 0.5,
                description: "Beginner tier: 3 highly liquid symbols, equal weighting".to_string(),
            },
            // Tier 2: Growing
            Self {
                tier: 2,
                min_capital: 50_000.0,
                max_symbols: 6,
                min_liquidity: 2_000_000.0,
                max_correlation: 0.75,
                position_sizing: PositionSizingMode::MLOptimized,
                min_sharpe_ratio: 0.7,
                description: "Growing tier: 6 symbols, ML-optimized allocation".to_string(),
            },
            // Tier 3: Intermediate
            Self {
                tier: 3,
                min_capital: 100_000.0,
                max_symbols: 12,
                min_liquidity: 1_000_000.0,
                max_correlation: 0.80,
                position_sizing: PositionSizingMode::RiskParity,
                min_sharpe_ratio: 0.9,
                description: "Intermediate tier: 12 symbols, risk parity allocation".to_string(),
            },
            // Tier 4: Advanced
            Self {
                tier: 4,
                min_capital: 250_000.0,
                max_symbols: 20,
                min_liquidity: 500_000.0,
                max_correlation: 0.85,
                position_sizing: PositionSizingMode::MeanVariance,
                min_sharpe_ratio: 1.0,
                description: "Advanced tier: 20 symbols, mean-variance optimization".to_string(),
            },
            // Tier 5: Professional
            Self {
                tier: 5,
                min_capital: 500_000.0,
                max_symbols: 30,
                min_liquidity: 200_000.0,
                max_correlation: 0.90,
                position_sizing: PositionSizingMode::Kelly,
                min_sharpe_ratio: 1.2,
                description: "Professional tier: 30 symbols, Kelly criterion".to_string(),
            },
            // Tier 6: Institutional
            Self {
                tier: 6,
                min_capital: 1_000_000.0,
                max_symbols: 50,
                min_liquidity: 100_000.0,
                max_correlation: 0.92,
                position_sizing: PositionSizingMode::BlackLitterman,
                min_sharpe_ratio: 1.5,
                description: "Institutional tier: 50 symbols, Black-Litterman model".to_string(),
            },
        ]
    }

    /// Find appropriate tier for given capital
    pub fn for_capital(capital: f64) -> Option<Self> {
        Self::all_tiers()
            .into_iter()
            .rev() // Start from highest tier
            .find(|tier| capital >= tier.min_capital)
    }
}

/// System constraint monitoring
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemConstraints {
    /// Max inference latency (ms)
    pub max_ml_latency: u64,

    /// Max order generation time (ms)
    pub max_order_gen_time: u64,

    /// Max memory usage (GB)
    pub max_memory_gb: f64,

    /// Max concurrent model inferences
    pub max_concurrent_inferences: usize,

    /// Max database connections
    pub max_db_connections: usize,

    /// Max symbols per rebalance cycle
    pub max_rebalance_symbols: usize,
}

impl Default for SystemConstraints {
    fn default() -> Self {
        Self {
            max_ml_latency: 100,           // 100ms
            max_order_gen_time: 50,        // 50ms
            max_memory_gb: 8.0,            // 8GB (RTX 3050 Ti)
            max_concurrent_inferences: 36, // 6 models * 6 symbols
            max_db_connections: 50,        // PostgreSQL limit
            max_rebalance_symbols: 30,     // Avoid overwhelming system
        }
    }
}

impl SystemConstraints {
    /// Check if system can handle the given number of symbols
    pub fn can_handle_symbols(&self, num_symbols: usize) -> Result<(), ScalingError> {
        // Check latency budget: empirical 15ms per symbol
        let estimated_latency = num_symbols as u64 * 15;
        if estimated_latency > self.max_ml_latency {
            return Err(ScalingError::ConstraintViolation(format!(
                "Latency budget exceeded: estimated {}ms > max {}ms",
                estimated_latency, self.max_ml_latency
            )));
        }

        // Check memory: 6 models * num_symbols * 50MB per model
        let estimated_memory = (6 * num_symbols * 50) as f64 / 1024.0;
        if estimated_memory > self.max_memory_gb {
            return Err(ScalingError::ConstraintViolation(format!(
                "Memory budget exceeded: estimated {:.2}GB > max {:.2}GB",
                estimated_memory, self.max_memory_gb
            )));
        }

        // Check database load
        if num_symbols > self.max_rebalance_symbols {
            return Err(ScalingError::ConstraintViolation(format!(
                "Rebalance load exceeded: {} symbols > max {}",
                num_symbols, self.max_rebalance_symbols
            )));
        }

        Ok(())
    }
}

/// Performance metrics for auto-adjustment decisions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
    /// Sharpe ratio (annualized risk-adjusted returns)
    pub sharpe_ratio: f64,

    /// Total return percentage
    pub total_return_pct: f64,

    /// Maximum drawdown percentage
    pub max_drawdown_pct: f64,

    /// Win rate (0.0-1.0)
    pub win_rate: f64,

    /// Capital growth rate over period
    pub capital_growth_rate: f64,

    /// Number of trades executed
    pub num_trades: u64,

    /// Period start
    pub period_start: DateTime<Utc>,

    /// Period end
    pub period_end: DateTime<Utc>,
}

impl Default for PerformanceMetrics {
    fn default() -> Self {
        Self {
            sharpe_ratio: 0.0,
            total_return_pct: 0.0,
            max_drawdown_pct: 0.0,
            win_rate: 0.5,
            capital_growth_rate: 0.0,
            num_trades: 0,
            period_start: Utc::now(),
            period_end: Utc::now(),
        }
    }
}

/// Autonomous scaling configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScalingConfig {
    pub config_id: Uuid,
    pub enabled: bool,
    pub current_tier: u32,
    pub current_capital: f64,
    pub current_symbols: usize,
    pub last_rebalance: DateTime<Utc>,
    pub performance_30d: PerformanceMetrics,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Scaling tier change event
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TierChangeEvent {
    pub event_id: Uuid,
    pub from_tier: Option<u32>,
    pub to_tier: u32,
    pub capital: f64,
    pub reason: String,
    pub timestamp: DateTime<Utc>,
}

/// Symbol scoring result for ML-driven selection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SymbolScore {
    pub symbol: Symbol,
    pub ml_confidence: f64,
    pub liquidity_score: f64,
    pub volatility_score: f64,
    pub diversification_score: f64,
    pub composite_score: f64,
}

impl SymbolScore {
    /// Calculate composite score from individual components
    pub fn calculate_composite(
        symbol: Symbol,
        ml_confidence: f64,
        liquidity_score: f64,
        volatility_score: f64,
        diversification_score: f64,
    ) -> Self {
        // Weighted average: ML 40%, Liquidity 25%, Volatility 20%, Diversification 15%
        let composite_score = ml_confidence * 0.40
            + liquidity_score * 0.25
            + volatility_score * 0.20
            + diversification_score * 0.15;

        Self {
            symbol,
            ml_confidence,
            liquidity_score,
            volatility_score,
            diversification_score,
            composite_score,
        }
    }
}

/// Autonomous universe manager
pub struct AutonomousUniverseManager {
    constraints: SystemConstraints,
    pool: PgPool,
    ml_client: Arc<OnceCell<crate::proto::ml::ml_service_client::MlServiceClient<Channel>>>,
}

impl AutonomousUniverseManager {
    /// Create a new autonomous universe manager
    pub fn new(pool: PgPool) -> Self {
        Self {
            constraints: SystemConstraints::default(),
            pool,
            ml_client: Arc::new(OnceCell::new()),
        }
    }

    /// Create with custom constraints
    pub fn with_constraints(pool: PgPool, constraints: SystemConstraints) -> Self {
        Self {
            constraints,
            pool,
            ml_client: Arc::new(OnceCell::new()),
        }
    }

    /// Get or create scaling configuration
    pub async fn get_or_create_config(&self) -> Result<ScalingConfig, ScalingError> {
        // Try to get existing config
        if let Some(config) = self.get_latest_config().await? {
            return Ok(config);
        }

        // Create initial config (Tier 1, $10K starting capital)
        let config = ScalingConfig {
            config_id: Uuid::new_v4(),
            enabled: true,
            current_tier: 1,
            current_capital: 10_000.0,
            current_symbols: 3,
            last_rebalance: Utc::now(),
            performance_30d: PerformanceMetrics::default(),
            created_at: Utc::now(),
            updated_at: Utc::now(),
        };

        self.store_config(&config).await?;

        Ok(config)
    }

    /// Get latest scaling configuration
    pub async fn get_latest_config(&self) -> Result<Option<ScalingConfig>, ScalingError> {
        let row = sqlx::query!(
            r#"
            SELECT config_id, enabled, current_tier, current_capital,
                   current_symbols, last_rebalance, performance_30d,
                   created_at, updated_at
            FROM autonomous_scaling_config
            ORDER BY created_at DESC
            LIMIT 1
            "#
        )
        .fetch_optional(&self.pool)
        .await?;

        match row {
            Some(row) => {
                let performance_30d: PerformanceMetrics =
                    serde_json::from_value(row.performance_30d)?;

                Ok(Some(ScalingConfig {
                    config_id: row.config_id,
                    enabled: row.enabled.unwrap_or(true),
                    current_tier: row.current_tier as u32,
                    current_capital: row.current_capital.to_string().parse().unwrap_or(0.0),
                    current_symbols: row.current_symbols as usize,
                    last_rebalance: row.last_rebalance,
                    performance_30d,
                    created_at: row.created_at.unwrap_or_else(Utc::now),
                    updated_at: row.updated_at.unwrap_or_else(Utc::now),
                }))
            },
            None => Ok(None),
        }
    }

    /// Store scaling configuration
    async fn store_config(&self, config: &ScalingConfig) -> Result<(), ScalingError> {
        let performance_json = serde_json::to_value(&config.performance_30d)?;
        let capital_decimal = BigDecimal::from_str(&config.current_capital.to_string())
            .map_err(|e| ScalingError::ConstraintViolation(format!("Invalid capital: {}", e)))?;

        sqlx::query!(
            r#"
            INSERT INTO autonomous_scaling_config (
                config_id, enabled, current_tier, current_capital,
                current_symbols, last_rebalance, performance_30d,
                created_at, updated_at
            )
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
            ON CONFLICT (config_id) DO UPDATE
            SET enabled = EXCLUDED.enabled,
                current_tier = EXCLUDED.current_tier,
                current_capital = EXCLUDED.current_capital,
                current_symbols = EXCLUDED.current_symbols,
                last_rebalance = EXCLUDED.last_rebalance,
                performance_30d = EXCLUDED.performance_30d,
                updated_at = EXCLUDED.updated_at
            "#,
            config.config_id,
            config.enabled,
            config.current_tier as i32,
            capital_decimal,
            config.current_symbols as i32,
            config.last_rebalance,
            performance_json,
            config.created_at,
            config.updated_at,
        )
        .execute(&self.pool)
        .await?;

        Ok(())
    }

    /// Record tier change event
    pub async fn record_tier_change(
        &self,
        from_tier: Option<u32>,
        to_tier: u32,
        capital: f64,
        reason: &str,
    ) -> Result<(), ScalingError> {
        let event = TierChangeEvent {
            event_id: Uuid::new_v4(),
            from_tier,
            to_tier,
            capital,
            reason: reason.to_string(),
            timestamp: Utc::now(),
        };

        let capital_decimal = BigDecimal::from_str(&capital.to_string())
            .map_err(|e| ScalingError::ConstraintViolation(format!("Invalid capital: {}", e)))?;

        sqlx::query!(
            r#"
            INSERT INTO scaling_tier_history (
                event_id, from_tier, to_tier, capital, reason, timestamp
            )
            VALUES ($1, $2, $3, $4, $5, $6)
            "#,
            event.event_id,
            from_tier.map(|t| t as i32),
            to_tier as i32,
            capital_decimal,
            event.reason,
            event.timestamp,
        )
        .execute(&self.pool)
        .await?;

        Ok(())
    }

    /// Select optimal universe for given capital
    pub async fn select_optimal_universe(
        &self,
        capital: f64,
    ) -> Result<Vec<Instrument>, ScalingError> {
        // Validate capital
        if capital <= 0.0 {
            return Err(ScalingError::InvalidCapital(capital));
        }

        // Get appropriate tier
        let tier = CapitalScalingTier::for_capital(capital)
            .ok_or_else(|| ScalingError::InvalidCapital(capital))?;

        tracing::info!(
            "Selected tier {} for capital ${:.2}: {}",
            tier.tier,
            capital,
            tier.description
        );

        // Check system constraints
        self.constraints.can_handle_symbols(tier.max_symbols)?;

        // Get candidate instruments
        let candidates = self.get_candidate_instruments().await?;

        // Score symbols using ML ensemble confidence (falls back to liquidity heuristic)
        let scored = self.score_symbols(&candidates, &tier).await;

        // Select top N symbols
        let mut selected: Vec<_> = scored
            .into_iter()
            .take(tier.max_symbols)
            .filter_map(|score| {
                candidates
                    .iter()
                    .find(|inst| inst.symbol == score.symbol)
                    .cloned()
            })
            .collect();

        // Sort by liquidity descending
        selected.sort_by(|a, b| {
            b.liquidity_score
                .partial_cmp(&a.liquidity_score)
                .unwrap_or(std::cmp::Ordering::Equal)
        });

        tracing::info!(
            "Selected {} symbols for tier {}: {:?}",
            selected.len(),
            tier.tier,
            selected.iter().map(|i| &i.symbol).collect::<Vec<_>>()
        );

        Ok(selected)
    }

    /// Get candidate instruments (reuses universe selector logic)
    async fn get_candidate_instruments(&self) -> Result<Vec<Instrument>, ScalingError> {
        // For now, hardcoded candidates (same as universe selector)
        // In production, would query market data APIs
        Ok(vec![
            Instrument {
                symbol: "ES.FUT".into(),
                exchange: "CME".to_string(),
                asset_class: crate::universe::AssetClass::Futures,
                region: crate::universe::Region::NorthAmerica,
                liquidity_score: 0.95,
                volatility: 0.20,
                market_cap: Some(10_000_000_000.0),
                avg_daily_volume: 2_000_000.0,
                spread_bps: 0.5,
            },
            Instrument {
                symbol: "NQ.FUT".into(),
                exchange: "CME".to_string(),
                asset_class: crate::universe::AssetClass::Futures,
                region: crate::universe::Region::NorthAmerica,
                liquidity_score: 0.92,
                volatility: 0.25,
                market_cap: Some(8_000_000_000.0),
                avg_daily_volume: 1_500_000.0,
                spread_bps: 0.8,
            },
            Instrument {
                symbol: "ZN.FUT".into(),
                exchange: "CME".to_string(),
                asset_class: crate::universe::AssetClass::Futures,
                region: crate::universe::Region::NorthAmerica,
                liquidity_score: 0.88,
                volatility: 0.15,
                market_cap: Some(5_000_000_000.0),
                avg_daily_volume: 800_000.0,
                spread_bps: 1.0,
            },
            Instrument {
                symbol: "6E.FUT".into(),
                exchange: "CME".to_string(),
                asset_class: crate::universe::AssetClass::Currencies,
                region: crate::universe::Region::Global,
                liquidity_score: 0.85,
                volatility: 0.18,
                market_cap: Some(4_000_000_000.0),
                avg_daily_volume: 600_000.0,
                spread_bps: 1.2,
            },
            Instrument {
                symbol: "CL.FUT".into(),
                exchange: "CME".to_string(),
                asset_class: crate::universe::AssetClass::Commodities,
                region: crate::universe::Region::Global,
                liquidity_score: 0.90,
                volatility: 0.35,
                market_cap: Some(6_000_000_000.0),
                avg_daily_volume: 1_200_000.0,
                spread_bps: 0.6,
            },
            Instrument {
                symbol: "GC.FUT".into(),
                exchange: "CME".to_string(),
                asset_class: crate::universe::AssetClass::Commodities,
                region: crate::universe::Region::Global,
                liquidity_score: 0.87,
                volatility: 0.22,
                market_cap: Some(7_000_000_000.0),
                avg_daily_volume: 900_000.0,
                spread_bps: 0.9,
            },
        ])
    }

    /// Fetch ML confidence from ensemble service for each symbol.
    /// Falls back to empty map on failure (callers use liquidity heuristic).
    async fn get_ensemble_confidences(&self, symbols: &[String]) -> HashMap<String, f64> {
        let mut result = HashMap::new();

        let client_result: Result<&crate::proto::ml::ml_service_client::MlServiceClient<Channel>, String> = self
            .ml_client
            .get_or_try_init(|| async {
                let url = std::env::var("ML_SERVICE_URL")
                    .unwrap_or_else(|_| "http://trading-service:50051".to_string());
                let channel = Channel::from_shared(url)
                    .map_err(|e| format!("invalid ML service URL: {e}"))?
                    .connect()
                    .await
                    .map_err(|e| format!("ML service unreachable: {e}"))?;
                Ok(crate::proto::ml::ml_service_client::MlServiceClient::new(
                    channel,
                ))
            })
            .await;

        let client = match client_result {
            Ok(c) => c,
            Err(e) => {
                tracing::warn!(error = %e, "ML client init failed, using liquidity fallback");
                return result;
            }
        };

        // Fan out one RPC per symbol concurrently (service API is single-symbol)
        let futs: Vec<_> = symbols
            .iter()
            .map(|symbol| {
                let mut client = client.clone();
                let sym = symbol.clone();
                async move {
                    let request = crate::proto::ml::GetEnsembleVoteRequest {
                        symbol: sym.clone(),
                        horizon_minutes: None,
                        model_names: vec![],
                    };
                    match client
                        .get_ensemble_vote(tonic::Request::new(request))
                        .await
                    {
                        Ok(resp) => {
                            let inner = resp.into_inner();
                            Some((sym, inner.overall_confidence))
                        }
                        Err(e) => {
                            tracing::warn!(symbol = %sym, error = %e, "ensemble RPC failed for symbol");
                            None
                        }
                    }
                }
            })
            .collect();

        let results = futures::future::join_all(futs).await;
        for pair in results.into_iter().flatten() {
            result.insert(pair.0, pair.1);
        }

        if !result.is_empty() {
            tracing::info!(
                count = result.len(),
                "fetched ensemble confidence for {} symbols",
                result.len()
            );
        }

        result
    }

    /// Score symbols using ML ensemble confidence with liquidity-heuristic fallback.
    async fn score_symbols(
        &self,
        instruments: &[Instrument],
        tier: &CapitalScalingTier,
    ) -> Vec<SymbolScore> {
        // Batch-fetch ensemble confidences for all candidate symbols
        let symbols: Vec<String> = instruments.iter().map(|i| i.symbol.to_string()).collect();
        let ensemble_confidences = self.get_ensemble_confidences(&symbols).await;

        instruments
            .iter()
            .filter(|inst| {
                // Apply tier filters
                inst.liquidity_score >= (tier.min_liquidity / 5_000_000.0)
                    && inst.avg_daily_volume >= tier.min_liquidity
            })
            .map(|inst| {
                // Use ensemble confidence when available, fall back to liquidity heuristic
                let ml_confidence = ensemble_confidences
                    .get(&inst.symbol.to_string())
                    .copied()
                    .unwrap_or(inst.liquidity_score * 0.9 + 0.1);

                // Normalize scores
                let liquidity_score = inst.liquidity_score;
                let volatility_score = 1.0 - (inst.volatility / 0.5).min(1.0);

                // Compute diversification from instrument concentration using inverse HHI.
                // HHI (Herfindahl-Hirschman Index) = sum of squared market shares by volume.
                // Score: 1.0 = perfectly diversified (equal volumes), 0.0 = single instrument.
                let total_volume: f64 = instruments.iter().map(|i| i.avg_daily_volume).sum();
                let diversification_score = if total_volume > 0.0 && instruments.len() > 1 {
                    let hhi: f64 = instruments
                        .iter()
                        .map(|i| {
                            let share = i.avg_daily_volume / total_volume;
                            share * share
                        })
                        .sum();
                    // Normalize: HHI ranges from 1/n (perfect diversification) to 1.0 (monopoly).
                    // Map to [0..1] where 1.0 = most diversified.
                    let n = instruments.len() as f64;
                    let min_hhi = 1.0 / n;
                    ((1.0 - hhi) / (1.0 - min_hhi)).clamp(0.0, 1.0)
                } else {
                    // Single instrument or zero total volume = no diversification
                    0.0
                };

                SymbolScore::calculate_composite(
                    inst.symbol.clone(),
                    ml_confidence,
                    liquidity_score,
                    volatility_score,
                    diversification_score,
                )
            })
            .collect()
    }

    /// Monitor performance and auto-adjust tier if needed
    pub async fn monitor_and_adjust(&self) -> Result<Option<TierChangeEvent>, ScalingError> {
        let mut config = self.get_or_create_config().await?;

        if !config.enabled {
            return Err(ScalingError::NotEnabled);
        }

        let Some(current_tier_def) = CapitalScalingTier::all_tiers()
            .into_iter()
            .find(|t| t.tier == config.current_tier)
        else {
            return Ok(None);
        };

        // Check for downgrade conditions
        if config.performance_30d.sharpe_ratio < current_tier_def.min_sharpe_ratio * 0.8 {
            // Performance degradation detected
            if config.current_tier > 1 {
                let new_tier = config.current_tier - 1;
                tracing::warn!(
                    "Performance degradation detected (Sharpe {:.2} < {:.2}), downgrading {} -> {}",
                    config.performance_30d.sharpe_ratio,
                    current_tier_def.min_sharpe_ratio * 0.8,
                    config.current_tier,
                    new_tier
                );

                self.record_tier_change(
                    Some(config.current_tier),
                    new_tier,
                    config.current_capital,
                    &format!(
                        "Performance degradation: Sharpe {:.2} < threshold {:.2}",
                        config.performance_30d.sharpe_ratio,
                        current_tier_def.min_sharpe_ratio * 0.8
                    ),
                )
                .await?;

                config.current_tier = new_tier;
                config.updated_at = Utc::now();
                self.store_config(&config).await?;

                return Ok(Some(TierChangeEvent {
                    event_id: Uuid::new_v4(),
                    from_tier: Some(config.current_tier + 1),
                    to_tier: new_tier,
                    capital: config.current_capital,
                    reason: "Performance degradation".to_string(),
                    timestamp: Utc::now(),
                }));
            }
        }

        // Check for upgrade conditions
        let next_tier_def = CapitalScalingTier::all_tiers()
            .into_iter()
            .find(|t| t.tier == config.current_tier + 1);

        if let Some(next_tier) = next_tier_def {
            let can_upgrade = config.current_capital >= next_tier.min_capital
                && config.performance_30d.sharpe_ratio > current_tier_def.min_sharpe_ratio * 1.2
                && config.performance_30d.capital_growth_rate > 0.10;

            if can_upgrade {
                tracing::info!(
                    "Strong performance detected (Sharpe {:.2}, growth {:.2}%), upgrading {} -> {}",
                    config.performance_30d.sharpe_ratio,
                    config.performance_30d.capital_growth_rate * 100.0,
                    config.current_tier,
                    next_tier.tier
                );

                self.record_tier_change(
                    Some(config.current_tier),
                    next_tier.tier,
                    config.current_capital,
                    &format!(
                        "Strong performance: Sharpe {:.2}, capital growth {:.2}%",
                        config.performance_30d.sharpe_ratio,
                        config.performance_30d.capital_growth_rate * 100.0
                    ),
                )
                .await?;

                config.current_tier = next_tier.tier;
                config.updated_at = Utc::now();
                self.store_config(&config).await?;

                return Ok(Some(TierChangeEvent {
                    event_id: Uuid::new_v4(),
                    from_tier: Some(config.current_tier - 1),
                    to_tier: next_tier.tier,
                    capital: config.current_capital,
                    reason: "Strong performance and capital growth".to_string(),
                    timestamp: Utc::now(),
                }));
            }
        }

        Ok(None)
    }

    /// Update capital and reselect universe if tier changes
    pub async fn update_capital(&self, new_capital: f64) -> Result<ScalingConfig, ScalingError> {
        let mut config = self.get_or_create_config().await?;

        let old_tier = config.current_tier;
        let new_tier = CapitalScalingTier::for_capital(new_capital)
            .map(|t| t.tier)
            .unwrap_or(1);

        config.current_capital = new_capital;

        if new_tier != old_tier {
            tracing::info!(
                "Capital change triggered tier change: {} -> {} (capital: ${:.2})",
                old_tier,
                new_tier,
                new_capital
            );

            self.record_tier_change(
                Some(old_tier),
                new_tier,
                new_capital,
                &format!("Capital updated to ${:.2}", new_capital),
            )
            .await?;

            config.current_tier = new_tier;
        }

        config.updated_at = Utc::now();
        self.store_config(&config).await?;

        Ok(config)
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;

    #[test]
    fn test_capital_tiers() {
        let tiers = CapitalScalingTier::all_tiers();
        assert_eq!(tiers.len(), 6);

        // Verify tier progression
        assert_eq!(tiers[0].tier, 1);
        assert_eq!(tiers[0].min_capital, 10_000.0);
        assert_eq!(tiers[0].max_symbols, 3);

        assert_eq!(tiers[5].tier, 6);
        assert_eq!(tiers[5].min_capital, 1_000_000.0);
        assert_eq!(tiers[5].max_symbols, 50);
    }

    #[test]
    fn test_tier_for_capital() {
        // Tier 1: $10K
        let tier = CapitalScalingTier::for_capital(15_000.0).unwrap();
        assert_eq!(tier.tier, 1);

        // Tier 2: $50K
        let tier = CapitalScalingTier::for_capital(75_000.0).unwrap();
        assert_eq!(tier.tier, 2);

        // Tier 3: $100K
        let tier = CapitalScalingTier::for_capital(150_000.0).unwrap();
        assert_eq!(tier.tier, 3);

        // Tier 6: $1M+
        let tier = CapitalScalingTier::for_capital(2_000_000.0).unwrap();
        assert_eq!(tier.tier, 6);

        // Below minimum
        assert!(CapitalScalingTier::for_capital(5_000.0).is_none());
    }

    #[test]
    fn test_system_constraints_latency() {
        let constraints = SystemConstraints::default();

        // 3 symbols: 45ms < 100ms ✓
        assert!(constraints.can_handle_symbols(3).is_ok());

        // 6 symbols: 90ms < 100ms ✓
        assert!(constraints.can_handle_symbols(6).is_ok());

        // 10 symbols: 150ms > 100ms ✗
        assert!(constraints.can_handle_symbols(10).is_err());
    }

    #[test]
    fn test_system_constraints_memory() {
        let constraints = SystemConstraints {
            max_ml_latency: 1000, // Disable latency check
            ..SystemConstraints::default()
        };

        // 6 models * 3 symbols * 50MB = 900MB = 0.88GB ✓
        assert!(constraints.can_handle_symbols(3).is_ok());

        // 6 models * 20 symbols * 50MB = 6GB ✓
        assert!(constraints.can_handle_symbols(20).is_ok());

        // 6 models * 30 symbols * 50MB = 9GB > 8GB ✗
        assert!(constraints.can_handle_symbols(30).is_err());
    }

    #[test]
    fn test_symbol_score_calculation() {
        let symbol = Symbol::from("ES.FUT");
        let score = SymbolScore::calculate_composite(
            symbol.clone(),
            0.9,  // ML confidence
            0.95, // Liquidity
            0.8,  // Volatility
            0.85, // Diversification
        );

        // Weighted: 0.9*0.4 + 0.95*0.25 + 0.8*0.2 + 0.85*0.15 = 0.885
        assert!((score.composite_score - 0.885).abs() < 0.001);
        assert_eq!(score.symbol, symbol);
    }

    #[test]
    fn test_position_sizing_modes() {
        let tiers = CapitalScalingTier::all_tiers();

        if let Some(tier1) = tiers.first() {
            assert_eq!(tier1.position_sizing, PositionSizingMode::EqualWeight);
        }

        if let Some(tier2) = tiers.get(1) {
            assert_eq!(tier2.position_sizing, PositionSizingMode::MLOptimized);
        }

        if let Some(tier6) = tiers.get(5) {
            assert_eq!(tier6.position_sizing, PositionSizingMode::BlackLitterman);
        }
    }
}
