//! Fixtures and Helpers Integration Tests
//!
//! Tests the cached data loading and validation utilities.

mod fixtures;
mod helpers;

use anyhow::Result;
use fixtures::get_multi_symbol_bars;
use num_traits::ToPrimitive;
use fixtures::{get_bars_for_date, get_regime_sample, RegimeType};
use fixtures::{get_cl_fut_bars, get_es_fut_bars, get_nq_fut_bars};
use helpers::{assert_chronological, assert_price_range, assert_valid_ohlcv};
use helpers::{assert_no_large_gaps, calculate_volatility, generate_quality_report};
use std::time::Instant;

// ============================================================================
// Cache Performance Tests
// ============================================================================

#[tokio::test]
async fn test_es_fut_cache_performance() -> Result<()> {
    println!("\n=== ES.FUT Cache Performance Test ===");

    // First call (cold cache)
    let start = Instant::now();
    let bars1 = get_es_fut_bars().await?;
    let cold_duration = start.elapsed();

    println!("Cold cache: {:?} ({} bars)", cold_duration, bars1.len());

    assert!(!bars1.is_empty(), "Should load ES.FUT bars");
    assert!(
        bars1.len() > 350 && bars1.len() < 450,
        "Expected ~390 bars, got {}",
        bars1.len()
    );

    // Second call (warm cache)
    let start = Instant::now();
    let bars2 = get_es_fut_bars().await?;
    let warm_duration = start.elapsed();

    println!("Warm cache: {:?} ({} bars)", warm_duration, bars2.len());

    assert_eq!(bars1.len(), bars2.len(), "Cache should return same data");

    // Calculate speedup
    let speedup = cold_duration.as_nanos() as f64 / warm_duration.as_nanos().max(1) as f64;
    println!("Speedup: {:.1}x faster", speedup);

    // Warm should be significantly faster (at least 10x)
    assert!(
        speedup > 10.0,
        "Cached access should be >10x faster (got {:.1}x)",
        speedup
    );

    Ok(())
}

#[tokio::test]
async fn test_nq_fut_cache_performance() -> Result<()> {
    println!("\n=== NQ.FUT Cache Performance Test ===");

    let start = Instant::now();
    let bars = get_nq_fut_bars().await?;
    let duration = start.elapsed();

    println!("Loaded: {:?} ({} bars)", duration, bars.len());

    assert!(!bars.is_empty());
    assert_eq!(bars[0].symbol, "NQ.FUT");

    Ok(())
}

#[tokio::test]
async fn test_cl_fut_cache_performance() -> Result<()> {
    println!("\n=== CL.FUT Cache Performance Test ===");

    let start = Instant::now();
    let bars = get_cl_fut_bars().await?;
    let duration = start.elapsed();

    println!("Loaded: {:?} ({} bars)", duration, bars.len());

    assert!(!bars.is_empty());
    assert!(
        bars.len() > 1400,
        "CL.FUT has 24-hour trading, expected >1400 bars"
    );
    assert_eq!(bars[0].symbol, "CL.FUT");

    Ok(())
}

// ============================================================================
// Data Validation Tests
// ============================================================================

#[tokio::test]
async fn test_es_fut_data_validation() -> Result<()> {
    println!("\n=== ES.FUT Data Validation ===");

    let bars = get_es_fut_bars().await?;

    // OHLCV validation
    assert_valid_ohlcv(&bars);
    println!("✓ OHLCV validation passed");

    // Chronological ordering
    assert_chronological(&bars);
    println!("✓ Chronological validation passed");

    // Price range (ES.FUT typical range)
    assert_price_range(&bars, "ES.FUT");
    println!("✓ Price range validation passed");

    // No large gaps (max 5 minutes for 1-minute data)
    assert_no_large_gaps(&bars, 5);
    println!("✓ Gap validation passed");

    Ok(())
}

#[tokio::test]
async fn test_nq_fut_data_validation() -> Result<()> {
    println!("\n=== NQ.FUT Data Validation ===");

    let bars = get_nq_fut_bars().await?;

    assert_valid_ohlcv(&bars);
    assert_chronological(&bars);
    assert_price_range(&bars, "NQ.FUT");
    assert_no_large_gaps(&bars, 5);

    println!("✓ All validations passed");

    Ok(())
}

#[tokio::test]
async fn test_cl_fut_data_validation() -> Result<()> {
    println!("\n=== CL.FUT Data Validation ===");

    let bars = get_cl_fut_bars().await?;

    assert_valid_ohlcv(&bars);
    assert_chronological(&bars);
    assert_price_range(&bars, "CL.FUT");
    assert_no_large_gaps(&bars, 2); // CL.FUT has tighter gaps (24-hour trading)

    println!("✓ All validations passed");

    Ok(())
}

// ============================================================================
// Filtered Data Access Tests
// ============================================================================

#[tokio::test]
async fn test_bars_for_date() -> Result<()> {
    println!("\n=== Date Filtering Test ===");

    use chrono::NaiveDate;

    let date = NaiveDate::from_ymd_opt(2024, 1, 2)
        .unwrap()
        .and_hms_opt(0, 0, 0)
        .unwrap()
        .and_utc();

    let bars = get_bars_for_date("ES.FUT", date).await?;

    assert!(!bars.is_empty(), "Should find bars for 2024-01-02");
    println!("Found {} bars for 2024-01-02", bars.len());

    // All bars should be from requested date
    for bar in &bars {
        assert_eq!(bar.timestamp.date_naive(), date.date_naive());
    }

    println!("✓ All bars from correct date");

    Ok(())
}

#[tokio::test]
async fn test_regime_trending() -> Result<()> {
    println!("\n=== Trending Regime Test ===");

    let bars = get_regime_sample(RegimeType::Trending).await?;

    assert!(!bars.is_empty(), "Should find trending sample");
    assert!(bars.len() >= 50, "Should have sufficient bars");

    println!("Trending sample: {} bars", bars.len());

    // Calculate price movement
    let first_price = bars[0].close.to_f64().unwrap_or(0.0);
    let last_price = bars[bars.len() - 1].close.to_f64().unwrap_or(0.0);
    let change_pct = ((last_price - first_price) / first_price).abs() * 100.0;

    println!("Price change: {:.2}%", change_pct);
    println!("✓ Trending regime detected");

    Ok(())
}

#[tokio::test]
async fn test_regime_ranging() -> Result<()> {
    println!("\n=== Ranging Regime Test ===");

    let bars = get_regime_sample(RegimeType::Ranging).await?;

    assert!(!bars.is_empty(), "Should find ranging sample");
    println!("Ranging sample: {} bars", bars.len());

    Ok(())
}

#[tokio::test]
async fn test_regime_volatile() -> Result<()> {
    println!("\n=== Volatile Regime Test ===");

    let bars = get_regime_sample(RegimeType::Volatile).await?;

    assert!(!bars.is_empty(), "Should find volatile sample");

    let volatility = calculate_volatility(&bars);
    println!("Volatile sample: {} bars", bars.len());
    println!("Annualized volatility: {:.2}%", volatility);

    Ok(())
}

#[tokio::test]
async fn test_regime_stable() -> Result<()> {
    println!("\n=== Stable Regime Test ===");

    let bars = get_regime_sample(RegimeType::Stable).await?;

    assert!(!bars.is_empty(), "Should find stable sample");

    let volatility = calculate_volatility(&bars);
    println!("Stable sample: {} bars", bars.len());
    println!("Annualized volatility: {:.2}%", volatility);

    Ok(())
}

// ============================================================================
// Multi-Symbol Tests
// ============================================================================

#[tokio::test]
async fn test_multi_symbol_loading() -> Result<()> {
    println!("\n=== Multi-Symbol Loading Test ===");

    let start = Instant::now();
    let symbols = vec!["ES.FUT", "NQ.FUT", "CL.FUT"];
    let data = get_multi_symbol_bars(&symbols).await?;
    let duration = start.elapsed();

    println!("Loaded {} symbols in {:?}", data.len(), duration);

    assert_eq!(data.len(), 3, "Should load all 3 symbols");
    assert!(data.contains_key("ES.FUT"));
    assert!(data.contains_key("NQ.FUT"));
    assert!(data.contains_key("CL.FUT"));

    // Verify data quality for each symbol
    for (symbol, bars) in &data {
        assert!(!bars.is_empty(), "Symbol {} should have bars", symbol);
        assert_eq!(bars[0].symbol, *symbol);
        println!("  {}: {} bars", symbol, bars.len());
    }

    println!("✓ All symbols loaded successfully");

    Ok(())
}

// ============================================================================
// Quality Report Tests
// ============================================================================

#[tokio::test]
async fn test_es_fut_quality_report() -> Result<()> {
    println!("\n=== ES.FUT Quality Report ===");

    let bars = get_es_fut_bars().await?;
    let report = generate_quality_report(&bars);

    println!("{}", report);

    assert!(report.contains("ES.FUT"));
    assert!(report.contains("Total bars"));
    assert!(report.contains("Quality Checks"));

    Ok(())
}

#[tokio::test]
async fn test_nq_fut_quality_report() -> Result<()> {
    println!("\n=== NQ.FUT Quality Report ===");

    let bars = get_nq_fut_bars().await?;
    let report = generate_quality_report(&bars);

    println!("{}", report);

    Ok(())
}

#[tokio::test]
async fn test_cl_fut_quality_report() -> Result<()> {
    println!("\n=== CL.FUT Quality Report ===");

    let bars = get_cl_fut_bars().await?;
    let report = generate_quality_report(&bars);

    println!("{}", report);

    Ok(())
}

// ============================================================================
// Thread Safety Tests
// ============================================================================

#[tokio::test]
async fn test_concurrent_cache_access() -> Result<()> {
    println!("\n=== Concurrent Cache Access Test ===");

    let mut handles = vec![];

    // Spawn 10 concurrent reads
    for i in 0..10 {
        handles.push(tokio::spawn(async move {
            let bars = get_es_fut_bars().await.unwrap();
            (i, bars.len())
        }));
    }

    // All should succeed
    let mut results = vec![];
    for handle in handles {
        let (id, len) = handle.await?;
        results.push((id, len));
    }

    println!("Concurrent reads: {}", results.len());

    // All should return same data
    let first_len = results[0].1;
    for (id, len) in &results {
        assert_eq!(*len, first_len, "Task {} got different length", id);
    }

    println!("✓ All concurrent reads consistent");

    Ok(())
}

// ============================================================================
// Integration Tests
// ============================================================================

#[tokio::test]
async fn test_strategy_with_cached_data() -> Result<()> {
    println!("\n=== Strategy Integration Test ===");

    // Load cached data
    let bars = get_es_fut_bars().await?;

    // Validate data
    assert_valid_ohlcv(&bars);
    assert_chronological(&bars);

    // Simple moving average crossover strategy simulation
    let mut signals = 0;

    for i in 20..bars.len() {
        let window = &bars[i - 20..i];
        let avg: f64 = window
            .iter()
            .map(|b| b.close.to_f64().unwrap_or(0.0))
            .sum::<f64>()
            / 20.0;

        let current = bars[i].close.to_f64().unwrap_or(0.0);

        if current > avg * 1.001 {
            // 0.1% above average
            signals += 1;
        }
    }

    println!("Generated {} trading signals", signals);
    assert!(signals > 0, "Should generate some signals");

    println!("✓ Strategy integration successful");

    Ok(())
}

#[tokio::test]
async fn test_performance_comparison() -> Result<()> {
    println!("\n=== Performance Comparison ===");

    // Test 1: Single symbol (cached)
    let start = Instant::now();
    for _ in 0..100 {
        let _ = get_es_fut_bars().await?;
    }
    let cached_duration = start.elapsed();

    println!("100 cached reads: {:?}", cached_duration);
    println!("Average per read: {:?}", cached_duration / 100);

    // Should be very fast (< 1ms total for 100 reads)
    assert!(
        cached_duration.as_millis() < 100,
        "100 cached reads should take < 100ms"
    );

    println!("✓ Cache performance excellent");

    Ok(())
}
