#![allow(unexpected_cfgs)]
#![cfg(feature = "__trading_service_integration")]
//! TDD E2E Integration Tests for ML Trading Pipeline
//!
//! **Mission**: Comprehensive end-to-end tests for ML trading pipeline using strict TDD methodology
//! **Methodology**: RED (Failing Tests) → GREEN (Minimal Implementation) → REFACTOR (Quality)
//!
//! ## Test Coverage
//! 1. End-to-end ML trading pipeline (data → features → prediction → order → tracking)
//! 2. Ensemble consensus voting with disagreement handling
//! 3. Fallback to rule-based on low confidence
//! 4. Multi-symbol trading with ML predictions
//! 5. Performance tracking (accuracy, Sharpe ratio)
//! 6. Risk limits override ML signals
//! 7. Model comparison across 4 models
//!
//! ## TDD Protocol
//! - **RED Phase**: All tests are `#[ignore]` and WILL FAIL
//! - **GREEN Phase**: Remove `#[ignore]` and implement minimal code to pass
//! - **REFACTOR Phase**: Improve code quality without changing behavior

#![allow(unused_imports)]

use anyhow::{anyhow, Result};
use ml_core::native_types::NativeDevice;
use common::{CommonError, OrderSide, OrderType};
use sqlx::PgPool;
use std::collections::HashMap;
use std::path::PathBuf;
use uuid::Uuid;

// Import trading service ML components
use trading_service::{
    ml_performance_metrics::MLMetricsStore, Action, EnsembleCoordinator, Order,
    PaperTradingExecutor, SignalSource, TradingSignal,
};

// Import rand for random testing
use rand;

// ============================================================================
// Test Infrastructure & Helper Functions
// ============================================================================

/// Create test database pool
async fn get_test_db_pool() -> PgPool {
    let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
        "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
    });

    PgPool::connect(&database_url)
        .await
        .expect("Failed to connect to test database")
}

/// Create test ensemble coordinator with all 4 models (DQN, PPO, MAMBA2, TFT)
fn create_test_ensemble() -> std::sync::Arc<EnsembleCoordinator> {
    use std::sync::Arc;

    let coordinator = Arc::new(EnsembleCoordinator::new());

    // Note: In real usage, models would be loaded and registered with the coordinator
    // For tests, we create a minimal ensemble coordinator without loaded models

    coordinator
}

/// Create test ensemble with low confidence (for fallback testing)
fn create_test_ensemble_low_confidence() -> std::sync::Arc<EnsembleCoordinator> {
    // Same as above, but prediction will be mocked to return low confidence
    create_test_ensemble()
}

/// Create single-model coordinator (for model comparison tests)
fn create_single_model_coordinator(model: &str) -> std::sync::Arc<EnsembleCoordinator> {
    use std::sync::Arc;

    let coordinator = Arc::new(EnsembleCoordinator::new());

    // Note: In real usage, only the specified model would be loaded
    // For tests, we create a minimal ensemble coordinator

    coordinator
}

/// Load test OHLCV data (50 bars for feature extraction)
fn load_test_ohlcv_data(_symbol: &str, num_bars: usize) -> Vec<(f64, f64, f64, f64, f64)> {
    // Generate synthetic OHLCV data with realistic pattern
    let mut data = Vec::new();
    let mut base_price = 4500.0; // ES.FUT starting price

    for i in 0..num_bars {
        let trend = (i as f64 * 0.1).sin(); // Add sine wave trend
        let open = base_price + trend * 10.0;
        let high = open + (i as f64 % 5.0) + 5.0;
        let low = open - (i as f64 % 3.0) - 3.0;
        let close = open + trend * 5.0;
        let volume = 1000.0 + (i as f64 * 10.0);

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

    data
}

/// Load test data with model disagreement (divergent trends)
fn load_test_data_with_disagreement() -> Vec<(f64, f64, f64, f64, f64)> {
    // Generate data that creates model disagreement
    let mut data = Vec::new();
    let mut base_price = 4500.0;

    for i in 0..50 {
        // Create choppy market with no clear trend
        let noise = ((i * 7) % 13) as f64 * 2.0 - 13.0;
        let open = base_price + noise;
        let high = open + (i as f64 % 3.0) + 3.0;
        let low = open - (i as f64 % 2.0) - 2.0;
        let close = open + noise * 0.3;
        let volume = 1000.0 + (i as f64 * 5.0);

        data.push((open, high, low, close, volume));
        base_price = close;
    }

    data
}

// ============================================================================
// TEST 1: End-to-End ML Trading Pipeline (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_e2e_ml_trading_pipeline() {
    // RED: End-to-end test from feature extraction to order execution
    let pool = get_test_db_pool().await;

    // 1. Load real market data
    let market_data = load_test_ohlcv_data("ES.FUT", 50);
    assert_eq!(market_data.len(), 50, "Need 50 OHLCV bars");

    // 2. Create ML engine (feature extraction happens inside PaperTradingExecutor)
    let ensemble = create_test_ensemble();

    // Note: Feature extraction is now handled internally by PaperTradingExecutor
    // using ml::features::UnifiedFeatureExtractor (256-dim features)

    // 3. Execute paper trading order
    let mut executor = PaperTradingExecutor::new_with_ml(pool.clone(), ensemble)
        .await
        .expect("Failed to create executor with ML");

    // Generate ML signal (includes feature extraction internally)
    let signal = executor
        .generate_ml_signal(&market_data)
        .await
        .expect("Failed to generate ML signal");
    assert!(
        signal.confidence >= 0.0,
        "Signal should have valid confidence"
    );

    // 4. Execute order based on signal
    let order = executor
        .execute_ml_signal(&signal, "ES.FUT")
        .await
        .expect("Failed to execute ML signal");

    // Verify order created
    assert_ne!(order.id, Uuid::nil());
    assert_eq!(order.symbol, "ES.FUT");

    // 5. Verify prediction stored in database
    let prediction = sqlx::query!(
        "SELECT * FROM ml_predictions WHERE order_id = $1 ORDER BY id DESC LIMIT 1",
        order.id
    )
    .fetch_one(&pool)
    .await
    .expect("Failed to fetch prediction");

    assert_eq!(prediction.symbol, "ES.FUT");
    assert!((prediction.confidence as f64 - ensemble.confidence).abs() < 0.01);

    // 6. Simulate outcome and record
    executor
        .record_outcome(order.id, 150.0)
        .await
        .expect("Failed to record outcome"); // +$150 profit

    // 7. Verify performance metrics updated
    let metrics_store = MLMetricsStore::new(pool);
    let stats = metrics_store
        .get_accuracy_stats("Ensemble")
        .await
        .expect("Failed to get accuracy stats");

    assert_eq!(stats.total_predictions, 1);
    assert_eq!(stats.correct_predictions, 1);
    assert!((stats.accuracy - 1.0).abs() < 0.01);
}

// ============================================================================
// TEST 2: Ensemble Consensus Voting with Disagreement (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_ml_ensemble_consensus() {
    // RED: Test ensemble voting with disagreement
    let pool = get_test_db_pool().await;
    let ensemble = create_test_ensemble();

    // Load market data where models disagree
    let market_data = load_test_data_with_disagreement();

    let mut executor = PaperTradingExecutor::new_with_ml(pool, ensemble)
        .await
        .expect("Failed to create executor");

    let signal = executor
        .generate_ml_signal(&market_data)
        .await
        .expect("Failed to generate signal");

    // Ensemble should use weighted voting
    assert!(signal.model_votes.is_some(), "Should have model votes");
    let votes = signal.model_votes.unwrap();

    // At least 3/4 models should agree for high confidence
    let action_val = signal.action.expect("Should have action") as usize;
    let consensus_count = votes
        .iter()
        .filter(|(_, action, _)| *action == action_val)
        .count();

    if signal.confidence > 0.8 {
        assert!(
            consensus_count >= 3,
            "High confidence requires 3+ model agreement, got {}/{}",
            consensus_count,
            votes.len()
        );
    }
}

// ============================================================================
// TEST 3: Fallback to Rule-Based on Low Confidence (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_ml_fallback_on_low_confidence() {
    // RED: Test fallback to rule-based when confidence < 0.6
    let pool = get_test_db_pool().await;

    let ensemble = create_test_ensemble();
    let mut executor = PaperTradingExecutor::new_with_ml(pool, ensemble)
        .await
        .expect("Failed to create executor");

    // Disable ML to force fallback
    executor.disable_ml().await;

    let market_data = load_test_ohlcv_data("ES.FUT", 50);
    let signal = executor
        .generate_signal(&market_data)
        .await
        .expect("Failed to generate signal");

    assert_eq!(
        signal.source,
        SignalSource::RuleBased,
        "Source should be RuleBased"
    );
    assert!(signal.action.is_some(), "Should still generate signal");
}

// ============================================================================
// TEST 4: Multi-Symbol ML Trading (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_ml_multi_symbol_trading() {
    // RED: Test ML predictions for multiple symbols
    let pool = get_test_db_pool().await;
    let ensemble = create_test_ensemble();

    let mut executor = PaperTradingExecutor::new_with_ml(pool.clone(), ensemble)
        .await
        .expect("Failed to create executor");

    let symbols = vec!["ES.FUT", "NQ.FUT", "ZN.FUT"];

    for symbol in &symbols {
        let market_data = load_test_ohlcv_data(symbol, 50);
        let signal = executor
            .generate_ml_signal(&market_data)
            .await
            .expect("Failed to generate signal");

        if signal.confidence >= 0.6 {
            let order = executor
                .execute_ml_signal(&signal, symbol)
                .await
                .expect("Failed to execute signal");
            assert_eq!(order.symbol, *symbol);
        }
    }

    // Verify predictions for all symbols
    let predictions =
        sqlx::query!("SELECT symbol, COUNT(*) as count FROM ml_predictions GROUP BY symbol")
            .fetch_all(&pool)
            .await
            .expect("Failed to fetch predictions");

    assert!(
        predictions.len() >= 1,
        "At least 1 symbol should have predictions"
    );
}

// ============================================================================
// TEST 5: ML Performance Tracking - Accuracy Calculation (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_ml_performance_tracking_accuracy() {
    // RED: Test accuracy calculation with mixed outcomes
    let pool = get_test_db_pool().await;
    let ensemble = create_test_ensemble();

    let mut executor = PaperTradingExecutor::new_with_ml(pool.clone(), ensemble)
        .await
        .expect("Failed to create executor");

    // Execute 10 ML trades
    for i in 0..10 {
        let market_data = load_test_ohlcv_data("ES.FUT", 50);
        let signal = executor
            .generate_ml_signal(&market_data)
            .await
            .expect("Failed to generate signal");

        let order = executor
            .execute_ml_signal(&signal, "ES.FUT")
            .await
            .expect("Failed to execute signal");

        // Record outcome: 7 correct, 3 incorrect
        let pnl = if i < 7 { 100.0 } else { -50.0 };
        executor
            .record_outcome(order.id, pnl)
            .await
            .expect("Failed to record outcome");
    }

    // Verify accuracy metrics
    let metrics_store = MLMetricsStore::new(pool);
    let stats = metrics_store
        .get_accuracy_stats("Ensemble")
        .await
        .expect("Failed to get accuracy stats");

    assert_eq!(stats.total_predictions, 10);
    assert_eq!(stats.correct_predictions, 7);
    assert!((stats.accuracy - 0.7).abs() < 0.01);
}

// ============================================================================
// TEST 6: Sharpe Ratio Calculation (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_ml_sharpe_ratio_calculation() {
    // RED: Test Sharpe ratio with profit/loss series
    let pool = get_test_db_pool().await;
    let ensemble = create_test_ensemble();

    let mut executor = PaperTradingExecutor::new_with_ml(pool.clone(), ensemble)
        .await
        .expect("Failed to create executor");

    // Execute trades with varying P&L
    let pnls = vec![100.0, -50.0, 200.0, -30.0, 150.0, 80.0, -20.0, 120.0];

    for pnl in pnls {
        let market_data = load_test_ohlcv_data("ES.FUT", 50);
        let signal = executor
            .generate_ml_signal(&market_data)
            .await
            .expect("Failed to generate signal");

        let order = executor
            .execute_ml_signal(&signal, "ES.FUT")
            .await
            .expect("Failed to execute signal");

        executor
            .record_outcome(order.id, pnl)
            .await
            .expect("Failed to record outcome");
    }

    // Calculate Sharpe ratio
    let metrics_store = MLMetricsStore::new(pool);
    let sharpe = metrics_store
        .calculate_sharpe_ratio("Ensemble")
        .await
        .expect("Failed to calculate Sharpe ratio");

    // Sharpe > 0 means profitable with controlled risk
    assert!(sharpe > 0.0, "Sharpe ratio should be positive");

    // Annualized Sharpe > 1.0 is good
    if sharpe > 1.0 {
        println!("✅ Good Sharpe ratio: {:.2}", sharpe);
    }
}

// ============================================================================
// TEST 7: Risk Limits Override ML Signals (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_ml_risk_limits_override() {
    // RED: Test that risk limits override ML signals
    let pool = get_test_db_pool().await;
    let ensemble = create_test_ensemble();

    let mut executor = PaperTradingExecutor::new_with_ml(pool, ensemble)
        .await
        .expect("Failed to create executor");

    // Set strict position limit
    executor
        .set_position_limit("ES.FUT", 5)
        .await
        .expect("Failed to set position limit");

    // Execute 5 trades (hit limit)
    for _ in 0..5 {
        let market_data = load_test_ohlcv_data("ES.FUT", 50);
        let signal = executor
            .generate_ml_signal(&market_data)
            .await
            .expect("Failed to generate signal");

        executor
            .execute_ml_signal(&signal, "ES.FUT")
            .await
            .expect("Failed to execute signal");
    }

    // 6th trade should be rejected
    let market_data = load_test_ohlcv_data("ES.FUT", 50);
    let signal = executor
        .generate_ml_signal(&market_data)
        .await
        .expect("Failed to generate signal");

    let result = executor.execute_ml_signal(&signal, "ES.FUT").await;

    assert!(
        result.is_err(),
        "6th trade should be rejected due to position limit"
    );

    let error_msg = result.unwrap_err().to_string();
    assert!(
        error_msg.to_lowercase().contains("position") || error_msg.to_lowercase().contains("limit"),
        "Error should mention position limit, got: {}",
        error_msg
    );
}

// ============================================================================
// TEST 8: Model Comparison Across 4 Models (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_ml_model_comparison() {
    // RED: Test comparing performance across 4 models
    let pool = get_test_db_pool().await;

    // Execute trades with each model individually
    for model in &["DQN", "PPO", "MAMBA2", "TFT"] {
        let ensemble = create_single_model_coordinator(model);
        let mut executor = PaperTradingExecutor::new_with_ml(pool.clone(), ensemble)
            .await
            .expect("Failed to create executor");

        for _ in 0..5 {
            let market_data = load_test_ohlcv_data("ES.FUT", 50);
            let signal = executor
                .generate_ml_signal(&market_data)
                .await
                .expect("Failed to generate signal");

            let order = executor
                .execute_ml_signal(&signal, "ES.FUT")
                .await
                .expect("Failed to execute signal");

            // Random outcome for testing
            let pnl = if rand::random::<f64>() > 0.5 {
                100.0
            } else {
                -50.0
            };
            executor
                .record_outcome(order.id, pnl)
                .await
                .expect("Failed to record outcome");
        }
    }

    // Compare model performance
    let metrics_store = MLMetricsStore::new(pool);
    let comparison = metrics_store
        .compare_model_accuracy()
        .await
        .expect("Failed to compare model accuracy");

    assert_eq!(comparison.len(), 4, "Should have all 4 models");

    // Models should be ranked by accuracy
    for i in 1..comparison.len() {
        assert!(
            comparison[i - 1].1 >= comparison[i].1,
            "Models should be sorted by accuracy"
        );
    }
}

// ============================================================================
// TEST 9: Position Sizing Based on Confidence (RED Phase)
// ============================================================================

#[tokio::test]
#[ignore = "RED: This test will fail until implementation is complete"]
async fn test_position_sizing_confidence_mapping() {
    // RED: Test position sizing scales with confidence
    let pool = get_test_db_pool().await;
    let ensemble = create_test_ensemble();

    let executor = PaperTradingExecutor::new_with_ml(pool, ensemble)
        .await
        .expect("Failed to create executor");

    use trading_service::paper_trading_executor::{Action, SignalSource, TradingSignal};

    // High confidence signal (0.9)
    let high_conf_signal = TradingSignal {
        action: Some(Action::Buy),
        confidence: 0.9,
        source: SignalSource::ML,
        model_votes: None,
    };

    // Low confidence signal (0.6)
    let low_conf_signal = TradingSignal {
        action: Some(Action::Buy),
        confidence: 0.6,
        source: SignalSource::ML,
        model_votes: None,
    };

    // Convert both to orders
    let high_conf_order = executor
        .convert_signal_to_order(&high_conf_signal, "ES.FUT")
        .await
        .expect("High confidence order failed");

    let low_conf_order = executor
        .convert_signal_to_order(&low_conf_signal, "ES.FUT")
        .await
        .expect("Low confidence order failed");

    // Higher confidence should result in larger position
    assert!(
        high_conf_order.quantity > low_conf_order.quantity,
        "High confidence ({}) should have larger position than low confidence ({})",
        high_conf_order.quantity,
        low_conf_order.quantity
    );
}
