//! Extended Ensemble Coordinator with 6-Model Support
//!
//! This module extends the coordinator to support all 6 models (DQN, PPO, TFT, MAMBA-2, Liquid, TLOB)
//! with dynamic weighting, correlation analysis, and performance attribution.

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

/// Minimum weight threshold per model (5%)
const MIN_WEIGHT_THRESHOLD: f64 = 0.05;

/// Maximum weight per model (40%) to prevent dominance
const MAX_WEIGHT_THRESHOLD: f64 = 0.40;

/// Supported model identifiers
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SupportedModel {
    DQN,
    PPO,
    TFT,
    MAMBA2,
    Liquid,
    TLOB,
}

impl SupportedModel {
    /// Convert from string identifier
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_uppercase().as_str() {
            "DQN" => Some(Self::DQN),
            "PPO" => Some(Self::PPO),
            "TFT" => Some(Self::TFT),
            "MAMBA2" | "MAMBA-2" | "MAMBA_2" => Some(Self::MAMBA2),
            "LIQUID" | "LNN" => Some(Self::Liquid),
            "TLOB" => Some(Self::TLOB),
            _ => None,
        }
    }

    /// Get model name as string
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::DQN => "DQN",
            Self::PPO => "PPO",
            Self::TFT => "TFT",
            Self::MAMBA2 => "MAMBA-2",
            Self::Liquid => "Liquid",
            Self::TLOB => "TLOB",
        }
    }
}

/// Model diversity analyzer for correlation and disagreement metrics
#[derive(Debug)]
pub struct DiversityAnalyzer {
    /// Recent predictions per model (sliding window)
    prediction_history: HashMap<String, Vec<f64>>,

    /// Correlation matrix between models
    correlation_matrix: HashMap<(String, String), f64>,

    /// Disagreement rates per model pair
    disagreement_rates: HashMap<(String, String), f64>,

    /// Maximum history size
    max_history_size: usize,
}

impl DiversityAnalyzer {
    /// Create new diversity analyzer
    pub fn new(max_history_size: usize) -> Self {
        Self {
            prediction_history: HashMap::new(),
            correlation_matrix: HashMap::new(),
            disagreement_rates: HashMap::new(),
            max_history_size,
        }
    }

    /// Record predictions from all models
    pub fn record_predictions(&mut self, predictions: &[ModelPrediction]) {
        for pred in predictions {
            let history = self
                .prediction_history
                .entry(pred.model_id.clone())
                .or_default();

            history.push(pred.value);

            // Maintain sliding window
            if history.len() > self.max_history_size {
                history.remove(0);
            }
        }

        // Update correlation matrix
        self.update_correlation_matrix();
        self.update_disagreement_rates(predictions);
    }

    /// Calculate pairwise correlation coefficients
    fn update_correlation_matrix(&mut self) {
        let model_ids: Vec<String> = self.prediction_history.keys().cloned().collect();

        for i in 0..model_ids.len() {
            for j in (i + 1)..model_ids.len() {
                let model_a = &model_ids[i];
                let model_b = &model_ids[j];

                if let (Some(history_a), Some(history_b)) = (
                    self.prediction_history.get(model_a),
                    self.prediction_history.get(model_b),
                ) {
                    if history_a.len() >= 10 && history_b.len() >= 10 {
                        let correlation = Self::pearson_correlation(history_a, history_b);
                        self.correlation_matrix
                            .insert((model_a.clone(), model_b.clone()), correlation);
                        self.correlation_matrix
                            .insert((model_b.clone(), model_a.clone()), correlation);
                    }
                }
            }
        }
    }

    /// Calculate Pearson correlation coefficient
    fn pearson_correlation(x: &[f64], y: &[f64]) -> f64 {
        let n = x.len().min(y.len());
        if n < 2 {
            return 0.0;
        }

        let x_slice = &x[x.len() - n..];
        let y_slice = &y[y.len() - n..];

        let mean_x: f64 = x_slice.iter().sum::<f64>() / n as f64;
        let mean_y: f64 = y_slice.iter().sum::<f64>() / n as f64;

        let mut numerator = 0.0;
        let mut sum_sq_x = 0.0;
        let mut sum_sq_y = 0.0;

        for i in 0..n {
            let dx = x_slice[i] - mean_x;
            let dy = y_slice[i] - mean_y;
            numerator += dx * dy;
            sum_sq_x += dx * dx;
            sum_sq_y += dy * dy;
        }

        let denominator = (sum_sq_x * sum_sq_y).sqrt();
        if denominator < 1e-10 {
            0.0
        } else {
            numerator / denominator
        }
    }

    /// Update disagreement rates for current predictions
    fn update_disagreement_rates(&mut self, predictions: &[ModelPrediction]) {
        for i in 0..predictions.len() {
            for j in (i + 1)..predictions.len() {
                let model_a = &predictions[i].model_id;
                let model_b = &predictions[j].model_id;

                // Models disagree if they have opposite signs
                let disagree = (predictions[i].value * predictions[j].value) < 0.0;

                let key = (model_a.clone(), model_b.clone());
                let rate_value = {
                    let rate = self.disagreement_rates.entry(key.clone()).or_insert(0.0);
                    // Exponential moving average with alpha = 0.1
                    *rate = 0.9 * (*rate) + 0.1 * (if disagree { 1.0 } else { 0.0 });
                    *rate
                };

                // Mirror entry
                let key_reverse = (model_b.clone(), model_a.clone());
                self.disagreement_rates.insert(key_reverse, rate_value);
            }
        }
    }

    /// Get correlation between two models
    pub fn get_correlation(&self, model_a: &str, model_b: &str) -> f64 {
        self.correlation_matrix
            .get(&(model_a.to_string(), model_b.to_string()))
            .copied()
            .unwrap_or(0.0)
    }

    /// Get average correlation for a model with all others
    pub fn get_average_correlation(&self, model_id: &str) -> f64 {
        let correlations: Vec<f64> = self
            .correlation_matrix
            .iter()
            .filter(|&((a, _), _corr)| (a == model_id)).map(|((_a, _), corr)| corr.abs())
            .collect();

        if correlations.is_empty() {
            0.0
        } else {
            correlations.iter().sum::<f64>() / correlations.len() as f64
        }
    }

    /// Get disagreement rate between two models
    pub fn get_disagreement_rate(&self, model_a: &str, model_b: &str) -> f64 {
        self.disagreement_rates
            .get(&(model_a.to_string(), model_b.to_string()))
            .copied()
            .unwrap_or(0.0)
    }

    /// Get diversity metrics for reporting
    pub fn get_diversity_metrics(&self) -> DiversityMetrics {
        let model_count = self.prediction_history.len();
        let correlation_count = self.correlation_matrix.len() / 2; // Symmetric matrix

        let avg_correlation = if correlation_count > 0 {
            self.correlation_matrix
                .values()
                .map(|c| c.abs())
                .sum::<f64>()
                / (correlation_count * 2) as f64
        } else {
            0.0
        };

        let avg_disagreement = if !self.disagreement_rates.is_empty() {
            self.disagreement_rates.values().sum::<f64>() / self.disagreement_rates.len() as f64
        } else {
            0.0
        };

        DiversityMetrics {
            model_count,
            avg_correlation,
            avg_disagreement,
            correlation_matrix: self.correlation_matrix.clone(),
        }
    }

    /// Generate correlation heatmap data
    pub fn get_correlation_heatmap(&self) -> Vec<(String, String, f64)> {
        self.correlation_matrix
            .iter()
            .map(|((a, b), corr)| (a.clone(), b.clone(), *corr))
            .collect()
    }
}

/// Diversity metrics for ensemble analysis
#[derive(Debug, Clone)]
pub struct DiversityMetrics {
    pub model_count: usize,
    pub avg_correlation: f64,
    pub avg_disagreement: f64,
    pub correlation_matrix: HashMap<(String, String), f64>,
}

/// Performance tracker for model evaluation
#[derive(Debug)]
pub struct PerformanceTracker {
    /// Recent returns per model
    model_returns: HashMap<String, Vec<f64>>,

    /// Sharpe ratios (annualized)
    sharpe_ratios: HashMap<String, f64>,

    /// Win rates
    win_rates: HashMap<String, f64>,

    /// Prediction counts
    prediction_counts: HashMap<String, u64>,

    /// Performance window size
    window_size: usize,
}

impl PerformanceTracker {
    /// Create new performance tracker
    pub fn new(window_size: usize) -> Self {
        Self {
            model_returns: HashMap::new(),
            sharpe_ratios: HashMap::new(),
            win_rates: HashMap::new(),
            prediction_counts: HashMap::new(),
            window_size,
        }
    }

    /// Record prediction outcome for a model
    pub fn record_outcome(&mut self, model_id: &str, return_value: f64) {
        // Update returns history
        let returns = self
            .model_returns
            .entry(model_id.to_string())
            .or_default();

        returns.push(return_value);

        // Maintain sliding window
        if returns.len() > self.window_size {
            returns.remove(0);
        }

        // Update prediction count
        *self
            .prediction_counts
            .entry(model_id.to_string())
            .or_insert(0) += 1;

        // Recalculate metrics
        self.update_metrics(model_id);
    }

    /// Update performance metrics for a model
    fn update_metrics(&mut self, model_id: &str) {
        if let Some(returns) = self.model_returns.get(model_id) {
            if returns.len() < 5 {
                return; // Need minimum samples
            }

            // Calculate Sharpe ratio
            let mean_return = returns.iter().sum::<f64>() / returns.len() as f64;
            let variance = returns
                .iter()
                .map(|r| (r - mean_return).powi(2))
                .sum::<f64>()
                / returns.len() as f64;
            let std_dev = variance.sqrt();

            let sharpe = if std_dev > 1e-10 {
                let annualization_factor = common::thresholds::time::ANNUALIZATION_FACTOR;
                (mean_return / std_dev) * annualization_factor
            } else {
                // Handle constant returns: if mean is positive, use large positive Sharpe
                // if mean is negative, use large negative Sharpe
                // if mean is zero, use 0.0
                if mean_return.abs() < 1e-10 {
                    0.0
                } else if mean_return > 0.0 {
                    10.0 // Maximum clamped Sharpe
                } else {
                    -10.0 // Minimum clamped Sharpe
                }
            };

            self.sharpe_ratios
                .insert(model_id.to_string(), sharpe.max(-10.0).min(10.0));

            // Calculate win rate
            let wins = returns.iter().filter(|&&r| r > 0.0).count();
            let win_rate = wins as f64 / returns.len() as f64;
            self.win_rates.insert(model_id.to_string(), win_rate);
        }
    }

    /// Get Sharpe ratio for a model
    pub fn get_sharpe_ratio(&self, model_id: &str) -> f64 {
        self.sharpe_ratios.get(model_id).copied().unwrap_or(1.0)
    }

    /// Get win rate for a model
    pub fn get_win_rate(&self, model_id: &str) -> f64 {
        self.win_rates.get(model_id).copied().unwrap_or(0.5)
    }

    /// Get prediction count for a model
    pub fn get_prediction_count(&self, model_id: &str) -> u64 {
        self.prediction_counts.get(model_id).copied().unwrap_or(0)
    }

    /// Get performance attribution report
    pub fn get_performance_attribution(&self) -> PerformanceAttribution {
        let total_predictions: u64 = self.prediction_counts.values().sum();

        let model_performance: HashMap<String, ModelPerformance> = self
            .sharpe_ratios
            .iter()
            .map(|(model_id, sharpe)| {
                let win_rate = self.get_win_rate(model_id);
                let pred_count = self.get_prediction_count(model_id);

                (
                    model_id.clone(),
                    ModelPerformance {
                        sharpe_ratio: *sharpe,
                        win_rate,
                        prediction_count: pred_count,
                    },
                )
            })
            .collect();

        PerformanceAttribution {
            total_predictions,
            model_performance,
        }
    }
}

/// Model performance metrics
#[derive(Debug, Clone)]
pub struct ModelPerformance {
    pub sharpe_ratio: f64,
    pub win_rate: f64,
    pub prediction_count: u64,
}

/// Performance attribution report
#[derive(Debug, Clone)]
pub struct PerformanceAttribution {
    pub total_predictions: u64,
    pub model_performance: HashMap<String, ModelPerformance>,
}

/// Extended ensemble coordinator with 6-model support
#[derive(Debug)]
pub struct ExtendedEnsembleCoordinator {
    /// Model weights with dynamic adjustment
    model_weights: Arc<RwLock<HashMap<String, f64>>>,

    /// Diversity analyzer
    diversity_analyzer: Arc<RwLock<DiversityAnalyzer>>,

    /// Performance tracker
    performance_tracker: Arc<RwLock<PerformanceTracker>>,

    /// Configuration
    config: EnsembleConfig,

    /// Weight evolution history
    weight_history: Arc<RwLock<Vec<WeightSnapshot>>>,
}

/// Configuration for ensemble behavior
#[derive(Debug, Clone)]
pub struct EnsembleConfig {
    /// Enable adaptive weighting
    pub adaptive_weighting: bool,

    /// Minimum correlation for diversity bonus
    pub min_correlation_threshold: f64,

    /// Diversity weight adjustment factor
    pub diversity_adjustment_factor: f64,

    /// Performance window size
    pub performance_window_size: usize,

    /// Minimum weight per model
    pub min_weight: f64,

    /// Maximum weight per model
    pub max_weight: f64,
}

impl Default for EnsembleConfig {
    fn default() -> Self {
        Self {
            adaptive_weighting: true,
            min_correlation_threshold: 0.7,
            diversity_adjustment_factor: 0.2,
            performance_window_size: 1000,
            min_weight: MIN_WEIGHT_THRESHOLD,
            max_weight: MAX_WEIGHT_THRESHOLD,
        }
    }
}

/// Weight snapshot for tracking evolution
#[derive(Debug, Clone)]
pub struct WeightSnapshot {
    pub timestamp: u64,
    pub weights: HashMap<String, f64>,
    pub ensemble_sharpe: f64,
}

impl ExtendedEnsembleCoordinator {
    /// Create new extended ensemble coordinator
    pub fn new(config: EnsembleConfig) -> Self {
        Self {
            model_weights: Arc::new(RwLock::new(HashMap::new())),
            diversity_analyzer: Arc::new(RwLock::new(DiversityAnalyzer::new(
                config.performance_window_size,
            ))),
            performance_tracker: Arc::new(RwLock::new(PerformanceTracker::new(
                config.performance_window_size,
            ))),
            config,
            weight_history: Arc::new(RwLock::new(Vec::new())),
        }
    }

    /// Register a model with initial weight
    pub async fn register_model(&self, model_id: String, initial_weight: f64) -> MLResult<()> {
        let mut weights = self.model_weights.write().await;

        // Validate weight
        let clamped_weight = initial_weight
            .max(self.config.min_weight)
            .min(self.config.max_weight);

        weights.insert(model_id.clone(), clamped_weight);

        info!(
            "Registered model {} with weight {:.3}",
            model_id, clamped_weight
        );

        Ok(())
    }

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

        // Record predictions for diversity analysis
        {
            let mut analyzer = self.diversity_analyzer.write().await;
            analyzer.record_predictions(&predictions);
        }

        // Update adaptive weights if enabled
        if self.config.adaptive_weighting {
            self.update_adaptive_weights().await?;
        }

        // Aggregate predictions
        let decision = self.aggregate_predictions(predictions).await?;

        // Record weight snapshot
        self.record_weight_snapshot(0.0).await; // Sharpe will be updated later

        Ok(decision)
    }

    /// Update adaptive weights based on performance and diversity
    async fn update_adaptive_weights(&self) -> MLResult<()> {
        let mut weights = self.model_weights.write().await;
        let analyzer = self.diversity_analyzer.read().await;
        let tracker = self.performance_tracker.read().await;

        let model_ids: Vec<String> = weights.keys().cloned().collect();

        // Calculate base weights from performance (Sharpe ratio)
        let mut new_weights: HashMap<String, f64> = HashMap::new();
        let mut total_score = 0.0;

        for model_id in &model_ids {
            let sharpe = tracker.get_sharpe_ratio(model_id);
            let win_rate = tracker.get_win_rate(model_id);

            // Normalize Sharpe ratio to [0, 1] range (assuming Sharpe in [-3, 3])
            let normalized_sharpe = ((sharpe + 3.0) / 6.0).max(0.0).min(1.0);

            // Combined performance score
            let performance_score = 0.7 * normalized_sharpe + 0.3 * win_rate;

            // Diversity bonus: reward low correlation with other models
            let avg_correlation = analyzer.get_average_correlation(model_id);
            let diversity_bonus = if avg_correlation < self.config.min_correlation_threshold {
                self.config.diversity_adjustment_factor * (1.0 - avg_correlation)
            } else {
                0.0
            };

            let total_score_model = performance_score + diversity_bonus;
            new_weights.insert(model_id.clone(), total_score_model);
            total_score += total_score_model;
        }

        // Normalize weights to sum to 1.0
        if total_score > 1e-10 {
            for (model_id, score) in &new_weights {
                let normalized_weight = (score / total_score)
                    .max(self.config.min_weight)
                    .min(self.config.max_weight);

                weights.insert(model_id.clone(), normalized_weight);
            }

            // Renormalize after clamping
            let sum: f64 = weights.values().sum();
            for weight in weights.values_mut() {
                *weight /= sum;
            }
        }

        debug!("Updated adaptive weights: {:?}", *weights);

        Ok(())
    }

    /// Aggregate predictions with current weights
    async fn aggregate_predictions(
        &self,
        predictions: Vec<ModelPrediction>,
    ) -> MLResult<EnsembleDecision> {
        let weights = self.model_weights.read().await;

        let mut weighted_sum = 0.0;
        let mut confidence_sum = 0.0;
        let mut total_weight = 0.0;
        let mut model_votes = HashMap::new();

        for pred in &predictions {
            let weight = weights
                .get(&pred.model_id)
                .copied()
                .unwrap_or(1.0 / predictions.len() as f64);

            weighted_sum += pred.value * pred.confidence * weight;
            confidence_sum += pred.confidence * weight;
            total_weight += weight;

            // Build model vote
            let vote = ModelVote::new(pred.model_id.clone(), pred.value, pred.confidence, weight);
            model_votes.insert(pred.model_id.clone(), vote);
        }

        let signal = if total_weight > 0.0 {
            weighted_sum / total_weight
        } else {
            0.0
        };

        let confidence = if total_weight > 0.0 {
            confidence_sum / total_weight
        } else {
            0.0
        };

        // Calculate disagreement rate
        let mean_signal: f64 =
            predictions.iter().map(|p| p.value).sum::<f64>() / predictions.len() as f64;
        let disagreements = predictions
            .iter()
            .filter(|p| (p.value * mean_signal) < 0.0)
            .count();
        let disagreement_rate = disagreements as f64 / predictions.len() as f64;

        // Determine action
        let action = TradingAction::from_signal(signal, 0.3);

        let decision =
            EnsembleDecision::new(action, confidence, signal, disagreement_rate, model_votes);

        Ok(decision)
    }

    /// Record outcome for performance tracking
    pub async fn record_outcome(&self, model_id: &str, return_value: f64) -> MLResult<()> {
        let mut tracker = self.performance_tracker.write().await;
        tracker.record_outcome(model_id, return_value);
        Ok(())
    }

    /// Record weight snapshot
    async fn record_weight_snapshot(&self, ensemble_sharpe: f64) {
        let weights = self.model_weights.read().await;
        let mut history = self.weight_history.write().await;

        let snapshot = WeightSnapshot {
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
            weights: weights.clone(),
            ensemble_sharpe,
        };

        history.push(snapshot);

        // Keep last 10,000 snapshots
        if history.len() > 10000 {
            history.remove(0);
        }
    }

    /// Get current weights
    pub async fn get_weights(&self) -> HashMap<String, f64> {
        self.model_weights.read().await.clone()
    }

    /// Get diversity metrics
    pub async fn get_diversity_metrics(&self) -> DiversityMetrics {
        self.diversity_analyzer.read().await.get_diversity_metrics()
    }

    /// Get performance attribution
    pub async fn get_performance_attribution(&self) -> PerformanceAttribution {
        self.performance_tracker
            .read()
            .await
            .get_performance_attribution()
    }

    /// Get weight evolution history
    pub async fn get_weight_history(&self) -> Vec<WeightSnapshot> {
        self.weight_history.read().await.clone()
    }

    /// Get correlation heatmap data
    pub async fn get_correlation_heatmap(&self) -> Vec<(String, String, f64)> {
        self.diversity_analyzer
            .read()
            .await
            .get_correlation_heatmap()
    }

    /// Get model count
    pub async fn model_count(&self) -> usize {
        self.model_weights.read().await.len()
    }
}

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

    #[tokio::test]
    async fn test_extended_coordinator_creation() {
        let config = EnsembleConfig::default();
        let coordinator = ExtendedEnsembleCoordinator::new(config);
        assert_eq!(coordinator.model_count().await, 0);
    }

    #[tokio::test]
    async fn test_register_six_models() {
        let config = EnsembleConfig::default();
        let coordinator = ExtendedEnsembleCoordinator::new(config);

        // Register all 6 models
        coordinator
            .register_model("DQN".to_owned(), 0.167)
            .await
            .unwrap();
        coordinator
            .register_model("PPO".to_owned(), 0.167)
            .await
            .unwrap();
        coordinator
            .register_model("TFT".to_owned(), 0.167)
            .await
            .unwrap();
        coordinator
            .register_model("MAMBA-2".to_owned(), 0.167)
            .await
            .unwrap();
        coordinator
            .register_model("Liquid".to_owned(), 0.167)
            .await
            .unwrap();
        coordinator
            .register_model("TLOB".to_owned(), 0.165)
            .await
            .unwrap();

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

        let weights = coordinator.get_weights().await;
        let total_weight: f64 = weights.values().sum();
        assert!((total_weight - 1.0).abs() < 0.01);
    }

    #[tokio::test]
    async fn test_diversity_analyzer() {
        let mut analyzer = DiversityAnalyzer::new(100);

        // Create correlated predictions (DQN and PPO)
        for i in 0..50 {
            let value = (i as f64) * 0.1;
            let predictions = vec![
                ModelPrediction::new("DQN".to_owned(), value, 0.8),
                ModelPrediction::new("PPO".to_owned(), value + 0.01, 0.8),
                ModelPrediction::new("TFT".to_owned(), -value, 0.7), // Opposite
            ];
            analyzer.record_predictions(&predictions);
        }

        // Check correlation
        let corr_dqn_ppo = analyzer.get_correlation("DQN", "PPO");
        let corr_dqn_tft = analyzer.get_correlation("DQN", "TFT");

        assert!(
            corr_dqn_ppo > 0.9,
            "DQN and PPO should be highly correlated"
        );
        assert!(
            corr_dqn_tft < -0.9,
            "DQN and TFT should be negatively correlated"
        );
    }

    #[tokio::test]
    async fn test_performance_tracker() {
        let mut tracker = PerformanceTracker::new(100);

        // Record winning trades for DQN
        for _ in 0..30 {
            tracker.record_outcome("DQN", 0.01); // 1% return
        }

        // Record losing trades for PPO
        for _ in 0..30 {
            tracker.record_outcome("PPO", -0.005); // -0.5% return
        }

        let sharpe_dqn = tracker.get_sharpe_ratio("DQN");
        let sharpe_ppo = tracker.get_sharpe_ratio("PPO");

        assert!(
            sharpe_dqn > sharpe_ppo,
            "DQN should have higher Sharpe than PPO"
        );

        let win_rate_dqn = tracker.get_win_rate("DQN");
        assert_eq!(win_rate_dqn, 1.0, "DQN should have 100% win rate");
    }

    #[tokio::test]
    async fn test_adaptive_weighting() {
        let config = EnsembleConfig::default();
        let coordinator = ExtendedEnsembleCoordinator::new(config);

        // Register models
        coordinator
            .register_model("DQN".to_owned(), 0.5)
            .await
            .unwrap();
        coordinator
            .register_model("PPO".to_owned(), 0.5)
            .await
            .unwrap();

        // Record superior performance for DQN
        for _ in 0..50 {
            coordinator.record_outcome("DQN", 0.02).await.unwrap();
            coordinator.record_outcome("PPO", -0.01).await.unwrap();
        }

        // Update weights
        coordinator.update_adaptive_weights().await.unwrap();

        let weights = coordinator.get_weights().await;
        let dqn_weight = weights.get("DQN").unwrap();
        let ppo_weight = weights.get("PPO").unwrap();

        assert!(
            dqn_weight > ppo_weight,
            "DQN should have higher weight due to better performance"
        );
    }
}
