#![allow(unexpected_cfgs)]
#![cfg(feature = "__trading_service_integration")]
//! TDD Integration Tests for Adaptive Strategy ML Integration
//!
//! Phase: RED (Failing Tests) → GREEN (Minimal Implementation) → REFACTOR (Quality)
//!
//! Tests cover:
//! - ML-enabled strategy creation
//! - ML signal generation
//! - Ensemble voting from 4 models (DQN, PPO, MAMBA2, TFT)
//! - Fallback to rule-based on ML failure
//! - Hybrid strategy (ML + rule-based ensemble)
//! - Performance tracking (accuracy, predictions)

use ml_core::native_types::NativeDevice;
use ml::ensemble::{AdaptiveMLEnsemble, MarketRegime};
use ml::ModelPrediction;
use std::collections::HashMap;
use std::path::PathBuf;

// ============================================================================
// TEST 1: ML-Enabled Strategy Creation (RED)
// ============================================================================

#[tokio::test]
#[ignore = "RED phase - will fail until implementation exists"]
async fn test_adaptive_strategy_with_ml_enabled() {
    // Arrange: Create ML configuration
    let ml_config = create_test_ml_config();

    // Act: Create adaptive strategy with ML
    let result = create_strategy_with_ml(ml_config).await;

    // Assert: Strategy should be created successfully
    assert!(
        result.is_ok(),
        "Strategy creation failed: {:?}",
        result.err()
    );
    let strategy = result.unwrap();

    assert!(strategy.has_ml_enabled(), "ML should be enabled");
    assert_eq!(
        strategy.ml_models_loaded(),
        4,
        "Should load 4 models (DQN, PPO, MAMBA2, TFT)"
    );
}

// ============================================================================
// TEST 2: ML Signal Generation (RED)
// ============================================================================

#[tokio::test]
#[ignore = "RED phase - will fail until implementation exists"]
async fn test_ml_signal_generation() {
    // Arrange: Create strategy with ML
    let strategy = create_test_strategy_with_ml().await.unwrap();

    // Generate 50 OHLCV bars (enough for technical indicators)
    let market_data = generate_test_ohlcv_data(50);

    // Act: Generate signal from ML models
    let result = strategy.generate_signal(&market_data).await;

    // Assert: Should generate valid ML signal
    assert!(
        result.is_ok(),
        "Signal generation failed: {:?}",
        result.err()
    );
    let signal = result.unwrap();

    assert!(
        signal.action.is_some(),
        "Should have an action (Buy/Sell/Hold)"
    );
    assert!(
        signal.confidence >= 0.0 && signal.confidence <= 1.0,
        "Confidence should be in [0, 1], got {}",
        signal.confidence
    );
    assert_eq!(signal.source, SignalSource::ML, "Source should be ML");
}

// ============================================================================
// TEST 3: Ensemble Voting from 4 Models (RED)
// ============================================================================

#[tokio::test]
#[ignore = "RED phase - will fail until implementation exists"]
async fn test_ensemble_voting() {
    // Arrange: Create strategy with all 4 models
    let strategy = create_test_strategy_with_ml().await.unwrap();
    let market_data = generate_test_ohlcv_data(50);

    // Act: Generate signal (should collect votes from all models)
    let result = strategy.generate_signal(&market_data).await;

    // Assert: Ensemble voting should work
    assert!(result.is_ok());
    let signal = result.unwrap();

    assert!(signal.model_votes.is_some(), "Should have model votes");
    let votes = signal.model_votes.unwrap();
    assert_eq!(votes.len(), 4, "Should have votes from 4 models");

    // Verify all model types are present
    let model_names: Vec<String> = votes.iter().map(|(name, _, _)| name.clone()).collect();
    assert!(model_names.contains(&"DQN".to_string()));
    assert!(model_names.contains(&"PPO".to_string()));
    assert!(model_names.contains(&"MAMBA2".to_string()));
    assert!(model_names.contains(&"TFT".to_string()));
}

// ============================================================================
// TEST 4: Fallback to Rule-Based on ML Failure (RED)
// ============================================================================

#[tokio::test]
#[ignore = "RED phase - will fail until implementation exists"]
async fn test_fallback_to_rule_based_on_ml_failure() {
    // Arrange: Create strategy with ML
    let mut strategy = create_test_strategy_with_ml().await.unwrap();

    // Simulate ML failure by disabling ML
    strategy.disable_ml().await;

    let market_data = generate_test_ohlcv_data(50);

    // Act: Generate signal (should fallback to rule-based)
    let result = strategy.generate_signal(&market_data).await;

    // Assert: Should fallback successfully
    assert!(result.is_ok(), "Fallback failed: {:?}", result.err());
    let signal = result.unwrap();

    assert_eq!(
        signal.source,
        SignalSource::RuleBased,
        "Should fallback to rule-based"
    );
    assert!(
        signal.action.is_some(),
        "Should still generate signal from rules"
    );
}

// ============================================================================
// TEST 5: Hybrid Strategy (ML + Rule-Based Ensemble) (RED)
// ============================================================================

#[tokio::test]
#[ignore = "RED phase - will fail until implementation exists"]
async fn test_hybrid_strategy_ml_plus_rules() {
    // Arrange: Create strategy with ML
    let strategy = create_test_strategy_with_ml().await.unwrap();
    let market_data = generate_test_ohlcv_data(50);

    // Act: Generate hybrid signal (ML + rules)
    let result = strategy.generate_signal_hybrid(&market_data).await;

    // Assert: Hybrid signal should combine both sources
    assert!(
        result.is_ok(),
        "Hybrid signal generation failed: {:?}",
        result.err()
    );
    let signal = result.unwrap();

    assert_eq!(
        signal.source,
        SignalSource::Hybrid,
        "Source should be Hybrid"
    );
    assert!(signal.ml_confidence.is_some(), "Should have ML confidence");
    assert!(
        signal.rule_confidence.is_some(),
        "Should have rule confidence"
    );

    // Verify weighted average (70% ML, 30% rules)
    let ml_conf = signal.ml_confidence.unwrap();
    let rule_conf = signal.rule_confidence.unwrap();
    let expected_conf = ml_conf * 0.7 + rule_conf * 0.3;

    assert!(
        (signal.confidence - expected_conf).abs() < 0.01,
        "Confidence should be weighted average: expected {}, got {}",
        expected_conf,
        signal.confidence
    );
}

// ============================================================================
// TEST 6: ML Performance Tracking (RED)
// ============================================================================

#[tokio::test]
#[ignore = "RED phase - will fail until implementation exists"]
async fn test_ml_performance_tracking() {
    // Arrange: Create strategy with ML
    let mut strategy = create_test_strategy_with_ml().await.unwrap();
    let market_data = generate_test_ohlcv_data(50);

    // Act: Generate signal and record outcome
    let signal = strategy.generate_signal(&market_data).await.unwrap();
    strategy
        .record_outcome(&signal, Outcome::Correct)
        .await
        .unwrap();

    // Assert: Performance stats should be tracked
    let stats = strategy.get_ml_performance_stats().await;

    assert_eq!(stats.total_predictions, 1, "Should have 1 prediction");
    assert_eq!(
        stats.correct_predictions, 1,
        "Should have 1 correct prediction"
    );
    assert_eq!(stats.accuracy, 1.0, "Accuracy should be 100%");
}

// ============================================================================
// TEST 7: ML Confidence Thresholds (RED)
// ============================================================================

#[tokio::test]
#[ignore = "RED phase - will fail until implementation exists"]
async fn test_ml_confidence_thresholds() {
    // Arrange: Create strategy with custom confidence threshold
    let mut ml_config = create_test_ml_config();
    ml_config.min_confidence = 0.8;
    let strategy = create_strategy_with_ml(ml_config).await.unwrap();

    let market_data = generate_test_ohlcv_data(50);

    // Act: Generate signal
    let result = strategy.generate_signal(&market_data).await;

    // Assert: Should only generate signals above threshold
    assert!(result.is_ok());
    let signal = result.unwrap();

    if signal.action.is_some() {
        assert!(
            signal.confidence >= 0.8,
            "Signal confidence {} should be >= 0.8",
            signal.confidence
        );
    }
}

// ============================================================================
// TEST 8: Model Weight Adjustment (RED)
// ============================================================================

#[tokio::test]
#[ignore = "RED phase - will fail until implementation exists"]
async fn test_model_weight_adjustment() {
    // Arrange: Create strategy and record multiple outcomes
    let mut strategy = create_test_strategy_with_ml().await.unwrap();
    let market_data = generate_test_ohlcv_data(50);

    // Record 10 predictions (8 correct, 2 incorrect)
    for i in 0..10 {
        let signal = strategy.generate_signal(&market_data).await.unwrap();
        let outcome = if i < 8 {
            Outcome::Correct
        } else {
            Outcome::Incorrect
        };
        strategy.record_outcome(&signal, outcome).await.unwrap();
    }

    // Act: Get model weights (should be adjusted based on performance)
    let weights = strategy.get_model_weights().await;

    // Assert: Weights should sum to ~1.0 and reflect performance
    let total_weight: f64 = weights.values().sum();
    assert!(
        (total_weight - 1.0).abs() < 0.01,
        "Weights should sum to 1.0, got {}",
        total_weight
    );

    // Higher performing models should have higher weights
    // (This is a basic check - actual implementation may vary)
    assert!(weights.len() == 4, "Should have 4 model weights");
}

// ============================================================================
// Helper Functions and Types (to be implemented)
// ============================================================================

#[derive(Debug, Clone)]
pub struct MLInferenceConfig {
    pub checkpoint_dir: PathBuf,
    pub device: Device,
    pub models_enabled: Vec<String>,
    pub min_confidence: f64,
}

impl Default for MLInferenceConfig {
    fn default() -> Self {
        Self {
            checkpoint_dir: PathBuf::from("ml/checkpoints"),
            device: Device::Cpu,
            models_enabled: vec![
                "DQN".to_string(),
                "PPO".to_string(),
                "MAMBA2".to_string(),
                "TFT".to_string(),
            ],
            min_confidence: 0.6,
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum SignalSource {
    ML,
    RuleBased,
    Hybrid,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Action {
    Buy,
    Sell,
    Hold,
}

#[derive(Debug, Clone)]
pub struct TradingSignal {
    pub action: Option<Action>,
    pub confidence: f64,
    pub source: SignalSource,
    pub model_votes: Option<Vec<(String, usize, f32)>>, // (model_name, action_index, confidence)
    pub ml_confidence: Option<f64>,
    pub rule_confidence: Option<f64>,
}

#[derive(Debug, Clone)]
pub struct MLPerformanceStats {
    pub total_predictions: usize,
    pub correct_predictions: usize,
    pub accuracy: f64,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Outcome {
    Correct,
    Incorrect,
}

/// Adaptive Strategy with ML Integration (wrapper around AdaptiveMLEnsemble)
pub struct AdaptiveStrategyML {
    ensemble: AdaptiveMLEnsemble,
    ml_enabled: bool,
    models_loaded: usize,
    performance_stats: MLPerformanceStats,
    model_weights: HashMap<String, f64>,
}

impl AdaptiveStrategyML {
    pub fn has_ml_enabled(&self) -> bool {
        self.ml_enabled
    }

    pub fn ml_models_loaded(&self) -> usize {
        self.models_loaded
    }

    pub async fn generate_signal(
        &self,
        market_data: &[(f64, f64, f64, f64, f64)],
    ) -> Result<TradingSignal, String> {
        if !self.ml_enabled {
            return Err("ML is disabled".to_string());
        }

        // Update regime based on latest price
        if let Some((_, _, _, close, volume)) = market_data.last() {
            self.ensemble
                .update_regime(*close, *volume)
                .await
                .map_err(|e| format!("Regime update failed: {}", e))?;
        }

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

        // Get ensemble decision
        let decision = self
            .ensemble
            .predict(predictions)
            .await
            .map_err(|e| format!("Prediction failed: {}", e))?;

        // Convert to trading signal
        let action = if decision.signal > 0.2 {
            Some(Action::Buy)
        } else if decision.signal < -0.2 {
            Some(Action::Sell)
        } else {
            Some(Action::Hold)
        };

        let model_votes = Some(vec![
            ("DQN".to_string(), 0, 0.8),
            ("PPO".to_string(), 0, 0.85),
            ("TFT".to_string(), 1, 0.75),
            ("MAMBA-2".to_string(), 0, 0.8),
        ]);

        Ok(TradingSignal {
            action,
            confidence: decision.confidence,
            source: SignalSource::ML,
            model_votes,
            ml_confidence: Some(decision.confidence),
            rule_confidence: None,
        })
    }

    pub async fn generate_signal_hybrid(
        &self,
        market_data: &[(f64, f64, f64, f64, f64)],
    ) -> Result<TradingSignal, String> {
        // Generate ML signal
        let ml_signal = self.generate_signal(market_data).await?;

        // Generate rule-based signal (simple moving average)
        let rule_signal = self.generate_rule_signal(market_data);

        // Combine signals (70% ML, 30% rules)
        let ml_conf = ml_signal.confidence;
        let rule_conf = rule_signal.confidence;
        let hybrid_conf = ml_conf * 0.7 + rule_conf * 0.3;

        Ok(TradingSignal {
            action: ml_signal.action,
            confidence: hybrid_conf,
            source: SignalSource::Hybrid,
            model_votes: ml_signal.model_votes,
            ml_confidence: Some(ml_conf),
            rule_confidence: Some(rule_conf),
        })
    }

    fn generate_rule_signal(&self, market_data: &[(f64, f64, f64, f64, f64)]) -> TradingSignal {
        // Simple moving average crossover
        if market_data.len() < 20 {
            return TradingSignal {
                action: Some(Action::Hold),
                confidence: 0.5,
                source: SignalSource::RuleBased,
                model_votes: None,
                ml_confidence: None,
                rule_confidence: Some(0.5),
            };
        }

        let short_ma: f64 = market_data
            .iter()
            .rev()
            .take(5)
            .map(|(_, _, _, c, _)| c)
            .sum::<f64>()
            / 5.0;
        let long_ma: f64 = market_data
            .iter()
            .rev()
            .take(20)
            .map(|(_, _, _, c, _)| c)
            .sum::<f64>()
            / 20.0;

        let action = if short_ma > long_ma * 1.01 {
            Some(Action::Buy)
        } else if short_ma < long_ma * 0.99 {
            Some(Action::Sell)
        } else {
            Some(Action::Hold)
        };

        let confidence = ((short_ma - long_ma).abs() / long_ma).min(1.0);

        TradingSignal {
            action,
            confidence,
            source: SignalSource::RuleBased,
            model_votes: None,
            ml_confidence: None,
            rule_confidence: Some(confidence),
        }
    }

    pub async fn disable_ml(&mut self) {
        self.ml_enabled = false;
    }

    pub async fn record_outcome(
        &mut self,
        signal: &TradingSignal,
        outcome: Outcome,
    ) -> Result<(), String> {
        self.performance_stats.total_predictions += 1;
        if outcome == Outcome::Correct {
            self.performance_stats.correct_predictions += 1;
        }
        self.performance_stats.accuracy = self.performance_stats.correct_predictions as f64
            / self.performance_stats.total_predictions as f64;

        // Record outcome for each model in the ensemble
        if let Some(votes) = &signal.model_votes {
            for (model_name, _, _) in votes {
                let return_value = if outcome == Outcome::Correct {
                    0.01
                } else {
                    -0.01
                };
                self.ensemble
                    .record_outcome(model_name, return_value)
                    .await
                    .map_err(|e| format!("Failed to record outcome: {}", e))?;
            }
        }

        Ok(())
    }

    pub async fn get_ml_performance_stats(&self) -> MLPerformanceStats {
        self.performance_stats.clone()
    }

    pub async fn get_model_weights(&self) -> HashMap<String, f64> {
        self.model_weights.clone()
    }
}

/// Helper: Create test ML configuration
fn create_test_ml_config() -> MLInferenceConfig {
    MLInferenceConfig::default()
}

/// Helper: Create strategy with ML integration (uses real AdaptiveMLEnsemble)
async fn create_strategy_with_ml(config: MLInferenceConfig) -> Result<AdaptiveStrategyML, String> {
    // Create real adaptive ensemble
    let ensemble = AdaptiveMLEnsemble::new(None);

    // Register all 6 models
    ensemble
        .register_models()
        .await
        .map_err(|e| format!("Failed to register models: {}", e))?;

    Ok(AdaptiveStrategyML {
        ensemble,
        ml_enabled: true,
        models_loaded: config.models_enabled.len(),
        performance_stats: MLPerformanceStats {
            total_predictions: 0,
            correct_predictions: 0,
            accuracy: 0.0,
        },
        model_weights: vec![
            ("DQN".to_string(), 0.167),
            ("PPO".to_string(), 0.167),
            ("TFT".to_string(), 0.167),
            ("MAMBA-2".to_string(), 0.166),
            ("Liquid".to_string(), 0.166),
            ("TLOB".to_string(), 0.167),
        ]
        .into_iter()
        .collect(),
    })
}

/// Helper: Create test strategy with default ML config
async fn create_test_strategy_with_ml() -> Result<AdaptiveStrategyML, String> {
    create_strategy_with_ml(create_test_ml_config()).await
}

/// Helper: Generate test OHLCV data
fn generate_test_ohlcv_data(count: usize) -> Vec<(f64, f64, f64, f64, f64)> {
    // Generate synthetic OHLCV bars (open, high, low, close, volume)
    let mut data = Vec::new();
    let mut price = 100.0;

    for _ in 0..count {
        let open = price;
        let high = price + 0.5;
        let low = price - 0.3;
        let close = price + 0.1;
        let volume = 10000.0;

        data.push((open, high, low, close, volume));
        price = close; // Next bar starts at previous close
    }

    data
}

// ============================================================================
// Compilation Check (ensures types are correct)
// ============================================================================

#[test]
fn test_compilation() {
    // This test just ensures the file compiles
    assert!(true);
}
