//! Adaptive ML Ensemble Integration
//!
//! Combines 6-model ML ensemble with adaptive trading strategy for regime-aware trading.
//! This module bridges the ML ensemble coordinator with market regime detection to
//! dynamically adjust model weights based on current market conditions.

use crate::EnsembleDecision;
use crate::{MLError, MLResult, ModelPrediction};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info};

use super::coordinator_extended::{EnsembleConfig, ExtendedEnsembleCoordinator};

/// Market regime types for adaptive weighting
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MarketRegime {
    /// Normal market conditions with typical volatility and volume
    Normal,
    /// Strong directional movement with clear trends
    Trending,
    /// Bull market - upward trending with moderate volatility
    Bull,
    /// Bear market - downward trending with moderate volatility
    Bear,
    /// Sideways market - low volatility, range-bound
    Sideways,
    /// High volatility - significant price swings
    HighVolatility,
    /// Crisis conditions with extreme volatility and risk
    Crisis,
    /// Unknown/transitioning regime
    Unknown,
}

/// Adaptive ML Ensemble combining ensemble coordinator with regime detection
#[derive(Debug)]
pub struct AdaptiveMLEnsemble {
    /// Extended ensemble coordinator (6 models)
    coordinator: Arc<ExtendedEnsembleCoordinator>,

    /// Current market regime
    current_regime: Arc<RwLock<MarketRegime>>,

    /// Regime detection parameters
    regime_config: RegimeConfig,

    /// Price history for regime detection (VecDeque for O(1) front removal)
    price_history: Arc<RwLock<VecDeque<PricePoint>>>,

    /// Volatility history for regime detection (VecDeque for O(1) front removal)
    volatility_history: Arc<RwLock<VecDeque<f64>>>,

    /// Performance metrics
    metrics: Arc<RwLock<AdaptiveMetrics>>,
}

/// Configuration for regime detection
#[derive(Debug, Clone)]
pub struct RegimeConfig {
    /// Lookback window for trend detection (bars)
    pub trend_lookback: usize,

    /// Volatility window for regime classification (bars)
    pub volatility_window: usize,

    /// Bull/Bear threshold (% change)
    pub trend_threshold: f64,

    /// High volatility threshold (multiple of average)
    pub volatility_threshold: f64,

    /// Minimum data points for regime detection
    pub min_data_points: usize,
}

impl Default for RegimeConfig {
    fn default() -> Self {
        Self {
            trend_lookback: 20,
            volatility_window: 20,
            trend_threshold: 0.02,     // 2% trend
            volatility_threshold: 1.5, // 1.5x average volatility
            min_data_points: 20,
        }
    }
}

/// Price point for regime detection
#[derive(Debug, Clone)]
pub struct PricePoint {
    pub timestamp: u64,
    pub price: f64,
    pub volume: f64,
}

/// Performance metrics for adaptive ensemble
#[derive(Debug, Clone)]
pub struct AdaptiveMetrics {
    /// Total predictions made
    pub total_predictions: u64,

    /// Predictions per regime
    pub predictions_per_regime: HashMap<MarketRegime, u64>,

    /// Sharpe ratio per regime
    pub sharpe_per_regime: HashMap<MarketRegime, f64>,

    /// Cumulative returns
    pub cumulative_return: f64,

    /// Maximum drawdown
    pub max_drawdown: f64,

    /// Win rate
    pub win_rate: f64,

    /// Regime transitions
    pub regime_transitions: u64,
}

impl Default for AdaptiveMetrics {
    fn default() -> Self {
        Self {
            total_predictions: 0,
            predictions_per_regime: HashMap::new(),
            sharpe_per_regime: HashMap::new(),
            cumulative_return: 0.0,
            max_drawdown: 0.0,
            win_rate: 0.0,
            regime_transitions: 0,
        }
    }
}

impl AdaptiveMLEnsemble {
    /// Create new adaptive ML ensemble
    pub fn new(regime_config: Option<RegimeConfig>) -> Self {
        let ensemble_config = EnsembleConfig {
            adaptive_weighting: true,
            min_correlation_threshold: 0.7,
            diversity_adjustment_factor: 0.2,
            performance_window_size: 1000,
            min_weight: 0.05,
            max_weight: 0.50,
        };

        let coordinator = Arc::new(ExtendedEnsembleCoordinator::new(ensemble_config));

        Self {
            coordinator,
            current_regime: Arc::new(RwLock::new(MarketRegime::Unknown)),
            regime_config: regime_config.unwrap_or_default(),
            price_history: Arc::new(RwLock::new(VecDeque::new())),
            volatility_history: Arc::new(RwLock::new(VecDeque::new())),
            metrics: Arc::new(RwLock::new(AdaptiveMetrics::default())),
        }
    }

    /// Register all 6 models with initial weights
    pub async fn register_models(&self) -> MLResult<()> {
        // Register all 6 models with equal initial weights
        self.coordinator
            .register_model("DQN".to_owned(), 0.167)
            .await?;
        self.coordinator
            .register_model("PPO".to_owned(), 0.167)
            .await?;
        self.coordinator
            .register_model("TFT".to_owned(), 0.167)
            .await?;
        self.coordinator
            .register_model("MAMBA-2".to_owned(), 0.166)
            .await?;
        self.coordinator
            .register_model("Liquid".to_owned(), 0.166)
            .await?;
        self.coordinator
            .register_model("TLOB".to_owned(), 0.167)
            .await?;

        info!("Registered 6 models in adaptive ensemble");
        Ok(())
    }

    /// Update market regime based on price data
    pub async fn update_regime(&self, price: f64, volume: f64) -> MLResult<MarketRegime> {
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        // Add to history
        {
            let mut history = self.price_history.write().await;
            history.push_back(PricePoint {
                timestamp,
                price,
                volume,
            });

            // Keep only required history -- O(1) pop_front via VecDeque
            let max_history = self
                .regime_config
                .trend_lookback
                .max(self.regime_config.volatility_window)
                * 2;
            while history.len() > max_history {
                history.pop_front();
            }
        }

        // Detect regime
        let new_regime = self.detect_regime().await?;

        // Update current regime if changed
        {
            let mut current = self.current_regime.write().await;
            if *current != new_regime {
                info!("Regime transition: {:?} -> {:?}", *current, new_regime);
                *current = new_regime;

                // Update metrics
                let mut metrics = self.metrics.write().await;
                metrics.regime_transitions += 1;
            }
        }

        Ok(new_regime)
    }

    /// Detect current market regime from price history
    async fn detect_regime(&self) -> MLResult<MarketRegime> {
        let history = self.price_history.read().await;

        if history.len() < self.regime_config.min_data_points {
            return Ok(MarketRegime::Unknown);
        }

        // Calculate trend
        let lookback = self.regime_config.trend_lookback.min(history.len());
        let prices: Vec<f64> = history
            .iter()
            .rev()
            .take(lookback)
            .map(|p| p.price)
            .collect();

        let first_price = prices.last().copied().unwrap_or(0.0);
        let last_price = prices.first().copied().unwrap_or(0.0);
        let trend = if first_price > 0.0 {
            (last_price - first_price) / first_price
        } else {
            0.0
        };

        // Calculate volatility
        let returns: Vec<f64> = prices.windows(2).map(|w| (w[0] - w[1]) / w[1]).collect();

        let volatility = if !returns.is_empty() {
            let mean: f64 = returns.iter().sum::<f64>() / returns.len() as f64;
            let variance: f64 =
                returns.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / returns.len() as f64;
            variance.sqrt()
        } else {
            0.0
        };

        // Update volatility history
        {
            let mut vol_history = self.volatility_history.write().await;
            vol_history.push_back(volatility);
            if vol_history.len() > self.regime_config.volatility_window {
                vol_history.pop_front();
            }
        }

        // Calculate average volatility
        let vol_history = self.volatility_history.read().await;
        let avg_volatility = if !vol_history.is_empty() {
            vol_history.iter().sum::<f64>() / vol_history.len() as f64
        } else {
            volatility
        };

        // Classify regime
        let regime = if volatility > avg_volatility * self.regime_config.volatility_threshold {
            MarketRegime::HighVolatility
        } else if trend > self.regime_config.trend_threshold {
            MarketRegime::Bull
        } else if trend < -self.regime_config.trend_threshold {
            MarketRegime::Bear
        } else {
            MarketRegime::Sideways
        };

        debug!(
            "Regime detection: trend={:.4}, volatility={:.4}, avg_vol={:.4}, regime={:?}",
            trend, volatility, avg_volatility, regime
        );

        Ok(regime)
    }

    /// Make prediction with regime-adaptive model weighting
    pub async fn predict(&self, predictions: Vec<ModelPrediction>) -> MLResult<EnsembleDecision> {
        if predictions.is_empty() {
            return Err(MLError::ValidationError {
                message: "No predictions provided".to_owned(),
            });
        }

        // Adjust weights based on current regime
        let regime = *self.current_regime.read().await;
        self.apply_regime_weights(regime).await?;

        // Get ensemble decision
        let decision = self.coordinator.predict(predictions).await?;

        // Update metrics
        {
            let mut metrics = self.metrics.write().await;
            metrics.total_predictions += 1;
            *metrics.predictions_per_regime.entry(regime).or_insert(0) += 1;
        }

        Ok(decision)
    }

    /// Apply regime-conditional model weights
    async fn apply_regime_weights(&self, regime: MarketRegime) -> MLResult<()> {
        // Define regime-specific weights
        let weights: HashMap<String, f64> = match regime {
            MarketRegime::Bull => {
                // Bull market: Weight trend-following models higher (DQN, PPO)
                [
                    ("DQN".to_owned(), 0.30),     // Trend follower
                    ("PPO".to_owned(), 0.25),     // Reinforcement learning
                    ("TFT".to_owned(), 0.15),     // Time-series forecasting
                    ("MAMBA-2".to_owned(), 0.15), // State-space model
                    ("Liquid".to_owned(), 0.10),  // Adaptive time constants
                    ("TLOB".to_owned(), 0.05),    // Order book (less relevant)
                ]
                .iter()
                .cloned()
                .collect()
            },
            MarketRegime::Bear => {
                // Bear market: Weight risk-aware models higher (PPO, TFT)
                [
                    ("PPO".to_owned(), 0.30),     // Risk-aware RL
                    ("TFT".to_owned(), 0.25),     // Forecasting
                    ("DQN".to_owned(), 0.15),     // Q-learning
                    ("MAMBA-2".to_owned(), 0.15), // State-space
                    ("Liquid".to_owned(), 0.10),  // Adaptive
                    ("TLOB".to_owned(), 0.05),    // Order book
                ]
                .iter()
                .cloned()
                .collect()
            },
            MarketRegime::Sideways => {
                // Sideways: Equal weights, focus on mean reversion
                [
                    ("TLOB".to_owned(), 0.25),    // Order book microstructure
                    ("Liquid".to_owned(), 0.20),  // Adaptive dynamics
                    ("TFT".to_owned(), 0.20),     // Pattern recognition
                    ("MAMBA-2".to_owned(), 0.15), // State transitions
                    ("DQN".to_owned(), 0.10),     // Reduced trend
                    ("PPO".to_owned(), 0.10),     // Reduced trend
                ]
                .iter()
                .cloned()
                .collect()
            },
            MarketRegime::HighVolatility => {
                // High volatility: Weight robust models higher
                [
                    ("PPO".to_owned(), 0.35),     // Robust RL
                    ("MAMBA-2".to_owned(), 0.25), // State-space handles chaos
                    ("TFT".to_owned(), 0.20),     // Forecasting
                    ("Liquid".to_owned(), 0.10),  // Adaptive
                    ("DQN".to_owned(), 0.05),     // Reduce Q-learning
                    ("TLOB".to_owned(), 0.05),    // Order book noise
                ]
                .iter()
                .cloned()
                .collect()
            },
            MarketRegime::Normal | MarketRegime::Trending => {
                // Normal/Trending: Balanced weights with slight trend bias
                [
                    ("DQN".to_owned(), 0.20),
                    ("PPO".to_owned(), 0.20),
                    ("TFT".to_owned(), 0.20),
                    ("MAMBA-2".to_owned(), 0.20),
                    ("Liquid".to_owned(), 0.10),
                    ("TLOB".to_owned(), 0.10),
                ]
                .iter()
                .cloned()
                .collect()
            },
            MarketRegime::Crisis => {
                // Crisis: Maximum risk aversion, weight PPO heavily
                [
                    ("PPO".to_owned(), 0.50),     // Maximum risk control
                    ("MAMBA-2".to_owned(), 0.20), // State transitions
                    ("TFT".to_owned(), 0.15),     // Forecasting
                    ("Liquid".to_owned(), 0.10),  // Adaptive
                    ("DQN".to_owned(), 0.03),     // Minimal risk-taking
                    ("TLOB".to_owned(), 0.02),    // Minimal exposure
                ]
                .iter()
                .cloned()
                .collect()
            },
            MarketRegime::Unknown => {
                // Unknown: Equal weights
                [
                    ("DQN".to_owned(), 0.167),
                    ("PPO".to_owned(), 0.167),
                    ("TFT".to_owned(), 0.167),
                    ("MAMBA-2".to_owned(), 0.166),
                    ("Liquid".to_owned(), 0.166),
                    ("TLOB".to_owned(), 0.167),
                ]
                .iter()
                .cloned()
                .collect()
            },
        };

        // Apply weights to coordinator
        for (model_id, weight) in weights {
            self.coordinator.register_model(model_id, weight).await?;
        }

        debug!("Applied regime-specific weights for {:?}", regime);
        Ok(())
    }

    /// Calculate volatility-adjusted position size using Kelly Criterion
    pub async fn calculate_position_size(
        &self,
        signal: f64,
        confidence: f64,
        account_equity: f64,
        _current_volatility: f64,
    ) -> f64 {
        // Kelly Criterion: f = (bp - q) / b
        // where b = odds, p = win probability, q = 1 - p

        // Estimate win probability from confidence (0.5 to 0.8 range)
        let win_prob = 0.5 + (confidence * 0.3);
        let lose_prob = 1.0 - win_prob;

        // Estimate odds from signal strength (1:1 to 3:1)
        let odds = 1.0 + (signal.abs() * 2.0);

        // Kelly fraction
        let kelly_fraction = ((odds * win_prob) - lose_prob) / odds;

        // Apply fractional Kelly (25% of full Kelly for safety)
        let fractional_kelly = kelly_fraction * 0.25;

        // Adjust for volatility (reduce position in high volatility)
        let regime = *self.current_regime.read().await;
        let volatility_adjustment = match regime {
            MarketRegime::HighVolatility => 0.5,            // 50% reduction
            MarketRegime::Crisis => 0.3,                    // 70% reduction (max risk control)
            MarketRegime::Bull | MarketRegime::Bear => 0.8, // 20% reduction
            MarketRegime::Sideways => 1.0,                  // No reduction
            MarketRegime::Normal | MarketRegime::Trending => 0.9, // 10% reduction
            MarketRegime::Unknown => 0.7,                   // 30% reduction
        };

        // Calculate position size
        let position_fraction = fractional_kelly.max(0.0).min(0.25) * volatility_adjustment;
        let position_size = account_equity * position_fraction;

        debug!(
            "Position sizing: signal={:.3}, confidence={:.3}, kelly={:.3}, adj={:.3}, size=${:.2}",
            signal, confidence, fractional_kelly, volatility_adjustment, position_size
        );

        position_size
    }

    /// Record outcome for performance tracking
    pub async fn record_outcome(&self, model_id: &str, return_value: f64) -> MLResult<()> {
        self.coordinator
            .record_outcome(model_id, return_value)
            .await?;

        // Update metrics
        {
            let mut metrics = self.metrics.write().await;

            // Calculate win rate before incrementing total_predictions
            let total_before = metrics.total_predictions;
            if total_before > 0 {
                if return_value > 0.0 {
                    let wins = (metrics.win_rate * total_before as f64) + 1.0;
                    metrics.win_rate = wins / (total_before + 1) as f64;
                } else {
                    let wins = metrics.win_rate * total_before as f64;
                    metrics.win_rate = wins / (total_before + 1) as f64;
                }
            } else {
                // First outcome
                metrics.win_rate = if return_value > 0.0 { 1.0 } else { 0.0 };
            }

            metrics.total_predictions += 1;
            metrics.cumulative_return += return_value;

            // Update drawdown
            if return_value < 0.0 && return_value.abs() > metrics.max_drawdown {
                metrics.max_drawdown = return_value.abs();
            }
        }

        Ok(())
    }

    /// Get current metrics
    pub async fn get_metrics(&self) -> AdaptiveMetrics {
        self.metrics.read().await.clone()
    }

    /// Get current regime
    pub async fn get_regime(&self) -> MarketRegime {
        *self.current_regime.read().await
    }

    /// Get diversity metrics
    pub async fn get_diversity_metrics(&self) -> super::coordinator_extended::DiversityMetrics {
        self.coordinator.get_diversity_metrics().await
    }

    /// Get performance attribution
    pub async fn get_performance_attribution(
        &self,
    ) -> super::coordinator_extended::PerformanceAttribution {
        self.coordinator.get_performance_attribution().await
    }
}

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

    #[tokio::test]
    async fn test_adaptive_ensemble_creation() {
        let ensemble = AdaptiveMLEnsemble::new(None);
        ensemble.register_models().await.unwrap();

        assert_eq!(ensemble.coordinator.model_count().await, 6);
    }

    #[tokio::test]
    async fn test_regime_detection_bull() {
        let ensemble = AdaptiveMLEnsemble::new(None);

        // Simulate bull market (rising prices)
        for i in 0..30 {
            let price = 100.0 + (i as f64);
            ensemble.update_regime(price, 1000.0).await.unwrap();
        }

        let regime = ensemble.get_regime().await;
        assert_eq!(regime, MarketRegime::Bull);
    }

    #[tokio::test]
    async fn test_regime_detection_bear() {
        let ensemble = AdaptiveMLEnsemble::new(None);

        // Simulate bear market (falling prices)
        for i in 0..30 {
            let price = 100.0 - (i as f64);
            ensemble.update_regime(price, 1000.0).await.unwrap();
        }

        let regime = ensemble.get_regime().await;
        assert_eq!(regime, MarketRegime::Bear);
    }

    #[tokio::test]
    async fn test_regime_detection_sideways() {
        let ensemble = AdaptiveMLEnsemble::new(None);

        // Simulate sideways market (oscillating prices)
        for i in 0..30 {
            let price = 100.0 + ((i % 2) as f64 * 0.1);
            ensemble.update_regime(price, 1000.0).await.unwrap();
        }

        let regime = ensemble.get_regime().await;
        assert_eq!(regime, MarketRegime::Sideways);
    }

    #[tokio::test]
    async fn test_regime_adaptive_weights() {
        let ensemble = AdaptiveMLEnsemble::new(None);
        ensemble.register_models().await.unwrap();

        // Set bull regime
        {
            let mut regime = ensemble.current_regime.write().await;
            *regime = MarketRegime::Bull;
        }

        // Apply regime weights
        ensemble
            .apply_regime_weights(MarketRegime::Bull)
            .await
            .unwrap();

        let weights = ensemble.coordinator.get_weights().await;

        // DQN should have higher weight in bull market
        assert!(weights.get("DQN").copied().unwrap_or(0.0) > 0.25);
        assert!(weights.get("PPO").copied().unwrap_or(0.0) > 0.20);
    }

    #[tokio::test]
    async fn test_position_sizing_kelly() {
        let ensemble = AdaptiveMLEnsemble::new(None);

        let position = ensemble
            .calculate_position_size(
                0.7,      // Strong signal
                0.8,      // High confidence
                100000.0, // $100k account
                0.02,     // 2% volatility
            )
            .await;

        // Position should be positive and reasonable (< 25% of equity)
        assert!(position > 0.0);
        assert!(position < 25000.0); // Max 25% of equity
    }

    #[tokio::test]
    async fn test_volatility_adjusted_position_sizing() {
        let ensemble = AdaptiveMLEnsemble::new(None);

        // Set high volatility regime
        {
            let mut regime = ensemble.current_regime.write().await;
            *regime = MarketRegime::HighVolatility;
        }

        let position = ensemble
            .calculate_position_size(
                0.7, 0.8, 100000.0, 0.05, // 5% volatility (high)
            )
            .await;

        // Position should be reduced due to high volatility
        assert!(position < 15000.0); // Should be less than normal
    }

    #[tokio::test]
    async fn test_ensemble_prediction_with_regime() {
        let ensemble = AdaptiveMLEnsemble::new(None);
        ensemble.register_models().await.unwrap();

        // Set regime
        ensemble.update_regime(100.0, 1000.0).await.unwrap();

        // Create predictions
        let predictions = vec![
            ModelPrediction::new("DQN".to_owned(), 0.5, 0.8),
            ModelPrediction::new("PPO".to_owned(), 0.6, 0.85),
            ModelPrediction::new("TFT".to_owned(), 0.4, 0.75),
            ModelPrediction::new("MAMBA-2".to_owned(), 0.55, 0.8),
            ModelPrediction::new("Liquid".to_owned(), 0.45, 0.7),
            ModelPrediction::new("TLOB".to_owned(), 0.3, 0.65),
        ];

        let decision = ensemble.predict(predictions).await.unwrap();

        assert!(decision.confidence > 0.0);
        assert!(decision.signal >= -1.0 && decision.signal <= 1.0);
        assert_eq!(decision.model_count(), 6);
    }

    #[tokio::test]
    async fn test_metrics_tracking() {
        let ensemble = AdaptiveMLEnsemble::new(None);
        ensemble.register_models().await.unwrap();

        // Record some outcomes
        ensemble.record_outcome("DQN", 0.02).await.unwrap();
        ensemble.record_outcome("PPO", 0.01).await.unwrap();
        ensemble.record_outcome("TFT", -0.01).await.unwrap();

        let metrics = ensemble.get_metrics().await;

        assert_eq!(metrics.total_predictions, 3);
        assert!(metrics.cumulative_return > 0.0);
        assert!(metrics.win_rate > 0.0);
    }

    #[tokio::test]
    async fn test_regime_transitions() {
        let ensemble = AdaptiveMLEnsemble::new(None);

        // Start with bull market
        for i in 0..30 {
            ensemble
                .update_regime(100.0 + i as f64, 1000.0)
                .await
                .unwrap();
        }

        assert_eq!(ensemble.get_regime().await, MarketRegime::Bull);

        // Transition to bear market
        for i in 0..30 {
            ensemble
                .update_regime(130.0 - i as f64, 1000.0)
                .await
                .unwrap();
        }

        assert_eq!(ensemble.get_regime().await, MarketRegime::Bear);

        let metrics = ensemble.get_metrics().await;
        assert!(metrics.regime_transitions >= 1);
    }
}
