//! VALIDATION TEST 3/8: Kelly Criterion Regime Multipliers
//!
//! Validates that kelly_criterion_regime_adaptive() applies regime-specific
//! position multipliers correctly.
//!
//! Test Strategy:
//! 1. Insert known regime states (Trending = 1.5x, Crisis = 0.2x)
//! 2. Run kelly_criterion_regime_adaptive() allocation
//! 3. Verify ES.FUT (Trending) gets ~7.5x more capital than NQ.FUT (Crisis)
//!
//! Expected Result: PASS if multipliers are applied correctly

use anyhow::Result;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use sqlx::PgPool;
use trading_agent_service::allocation::{AllocationMethod, AssetInfo, PortfolioAllocator};

// ============================================================================
// Test Setup
// ============================================================================

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

    let pool = PgPool::connect(&database_url)
        .await
        .expect("Failed to connect to database");

    // Run migrations (includes migration 045 for regime_states)
    sqlx::migrate!("../../migrations")
        .run(&pool)
        .await
        .expect("Failed to run migrations");

    pool
}

/// Insert regime state into database
async fn insert_regime_state(
    pool: &PgPool,
    symbol: &str,
    regime: &str,
    confidence: f64,
) -> Result<()> {
    // Add small delay to ensure unique timestamps
    tokio::time::sleep(tokio::time::Duration::from_millis(2)).await;

    sqlx::query(
        r#"
        INSERT INTO regime_states (symbol, event_timestamp, regime, confidence)
        VALUES ($1, NOW(), $2, $3)
        ON CONFLICT (symbol, event_timestamp)
        DO UPDATE SET regime = EXCLUDED.regime, confidence = EXCLUDED.confidence
        "#,
    )
    .bind(symbol)
    .bind(regime)
    .bind(confidence)
    .execute(pool)
    .await?;

    Ok(())
}

/// Clean up regime states for testing
async fn cleanup_regime_states(pool: &PgPool) -> Result<()> {
    sqlx::query("DELETE FROM regime_states")
        .execute(pool)
        .await?;
    Ok(())
}

// ============================================================================
// VALIDATION TEST 3/8: Kelly Applies Regime Multipliers
// ============================================================================

#[tokio::test]
async fn test_kelly_applies_regime_multipliers() {
    let pool = create_test_pool().await;
    cleanup_regime_states(&pool).await.unwrap();

    // Step 1: Insert known regime states
    // ES.FUT: Trending (1.5x position multiplier)
    // NQ.FUT: Crisis (0.2x position multiplier)
    insert_regime_state(&pool, "ES.FUT", "Trending", 0.95)
        .await
        .unwrap();
    insert_regime_state(&pool, "NQ.FUT", "Crisis", 0.90)
        .await
        .unwrap();

    // Step 2: Create test assets with IDENTICAL Kelly parameters
    // This ensures any difference in allocation is purely from regime multipliers
    let assets = vec![
        AssetInfo {
            symbol: "ES.FUT".to_string(),
            expected_return: 0.10,
            volatility: 0.20,
            ml_score: 0.65,
            win_rate: 0.55,
            avg_win: 150.0,
            avg_loss: 100.0,
        },
        AssetInfo {
            symbol: "NQ.FUT".to_string(),
            expected_return: 0.10, // Same as ES
            volatility: 0.20,      // Same as ES
            ml_score: 0.65,        // Same as ES
            win_rate: 0.55,        // Same as ES
            avg_win: 150.0,        // Same as ES
            avg_loss: 100.0,       // Same as ES
        },
    ];

    // Step 3: Run allocation using kelly_criterion_regime_adaptive
    let allocator = PortfolioAllocator::new(AllocationMethod::KellyCriterion { fraction: 0.25 });
    let total_capital = Decimal::from(100_000);

    let allocations = allocator
        .kelly_criterion_regime_adaptive(&assets, total_capital, 0.25, &pool)
        .await
        .unwrap();

    // Step 4: Verify ES.FUT > NQ.FUT (trending gets 1.5x, crisis gets 0.2x)
    let es_allocation = allocations.get("ES.FUT").expect("INVARIANT: Key should exist in map").to_f64().unwrap();
    let nq_allocation = allocations.get("NQ.FUT").expect("INVARIANT: Key should exist in map").to_f64().unwrap();

    // Expected ratio: 1.5x / 0.2x = 7.5x
    // Using >3.0 threshold for safety margin (accounting for normalization)
    assert!(
        es_allocation > nq_allocation * 3.0,
        "❌ VALIDATION 3/8 FAILED: Trending (ES.FUT) should get ~7.5x more than Crisis (NQ.FUT). ES: ${:.2}, NQ: ${:.2}, Ratio: {:.2}x",
        es_allocation,
        nq_allocation,
        es_allocation / nq_allocation
    );

    // Verify total allocation doesn't exceed capital
    let total: Decimal = allocations.values().sum();
    assert!(
        total <= total_capital,
        "❌ VALIDATION 3/8 FAILED: Total allocation {} exceeds capital {}",
        total,
        total_capital
    );

    // Print results
    println!("✅ VALIDATION 3/8 PASSED: Kelly applies regime multipliers correctly");
    println!("  ES.FUT (Trending 1.5x): ${:.2}", es_allocation);
    println!("  NQ.FUT (Crisis 0.2x):   ${:.2}", nq_allocation);
    println!(
        "  Ratio:                  {:.2}x (expected ~7.5x)",
        es_allocation / nq_allocation
    );
    println!(
        "  Total allocated:        ${:.2} / ${:.2}",
        total.to_f64().unwrap(),
        total_capital.to_f64().unwrap()
    );

    cleanup_regime_states(&pool).await.unwrap();
}

// ============================================================================
// Additional Validation: All Regime Multipliers
// ============================================================================

#[tokio::test]
async fn test_all_regime_multipliers() {
    let pool = create_test_pool().await;
    cleanup_regime_states(&pool).await.unwrap();

    // Test all regime types
    let regime_tests = vec![
        ("ES.FUT", "Normal", 1.0),
        ("NQ.FUT", "Trending", 1.5),
        ("ZN.FUT", "Ranging", 0.8),
        ("6E.FUT", "Volatile", 0.5),
        ("CL.FUT", "Crisis", 0.2),
    ];

    // Insert all regime states
    for (symbol, regime, _expected_mult) in &regime_tests {
        insert_regime_state(&pool, symbol, regime, 0.90)
            .await
            .unwrap();
    }

    // Create identical assets
    let mut assets = Vec::new();
    for (symbol, _regime, _mult) in &regime_tests {
        assets.push(AssetInfo {
            symbol: symbol.to_string(),
            expected_return: 0.10,
            volatility: 0.20,
            ml_score: 0.65,
            win_rate: 0.55,
            avg_win: 150.0,
            avg_loss: 100.0,
        });
    }

    // Run allocation
    let allocator = PortfolioAllocator::new(AllocationMethod::KellyCriterion { fraction: 0.25 });
    let total_capital = Decimal::from(500_000);

    let allocations = allocator
        .kelly_criterion_regime_adaptive(&assets, total_capital, 0.25, &pool)
        .await
        .unwrap();

    // Verify allocations match expected multiplier order
    // Trending (1.5x) > Normal (1.0x) > Ranging (0.8x) > Volatile (0.5x) > Crisis (0.2x)
    let trending_alloc = allocations.get("NQ.FUT").expect("INVARIANT: Key should exist in map").to_f64().unwrap();
    let normal_alloc = allocations.get("ES.FUT").expect("INVARIANT: Key should exist in map").to_f64().unwrap();
    let ranging_alloc = allocations.get("ZN.FUT").expect("INVARIANT: Key should exist in map").to_f64().unwrap();
    let volatile_alloc = allocations.get("6E.FUT").expect("INVARIANT: Key should exist in map").to_f64().unwrap();
    let crisis_alloc = allocations.get("CL.FUT").expect("INVARIANT: Key should exist in map").to_f64().unwrap();

    assert!(
        trending_alloc > normal_alloc,
        "Trending should get more than Normal"
    );
    assert!(
        normal_alloc > ranging_alloc,
        "Normal should get more than Ranging"
    );
    assert!(
        ranging_alloc > volatile_alloc,
        "Ranging should get more than Volatile"
    );
    assert!(
        volatile_alloc > crisis_alloc,
        "Volatile should get more than Crisis"
    );

    println!("✅ All regime multipliers validated:");
    println!("  Trending (NQ.FUT 1.5x): ${:.2}", trending_alloc);
    println!("  Normal   (ES.FUT 1.0x): ${:.2}", normal_alloc);
    println!("  Ranging  (ZN.FUT 0.8x): ${:.2}", ranging_alloc);
    println!("  Volatile (6E.FUT 0.5x): ${:.2}", volatile_alloc);
    println!("  Crisis   (CL.FUT 0.2x): ${:.2}", crisis_alloc);

    cleanup_regime_states(&pool).await.unwrap();
}
