#![allow(clippy::inconsistent_digit_grouping, dead_code, unused_variables, unexpected_cfgs)]
//! DBN Multi-Symbol Integration Tests
//!
//! Tests for multi-symbol loading with diverse asset classes:
//! - ES.FUT (E-mini S&P 500) - Equity index futures
//! - NQ.FUT (E-mini NASDAQ) - Tech index futures
//! - GC (Gold) - Commodity futures
//! - ZN.FUT (10-Year Treasury) - Fixed income futures
//! - 6E.FUT (Euro FX) - Currency futures

use anyhow::Result;
use num_traits::ToPrimitive;
use std::collections::HashMap;

mod mock_repositories;

use backtesting_service::dbn_data_source::DbnDataSource;
use backtesting_service::dbn_repository::DbnMarketDataRepository;
use backtesting_service::repositories::MarketDataRepository;

/// Helper to get multi-symbol file mappings
fn get_multi_symbol_file_mapping() -> HashMap<String, String> {
    let root = mock_repositories::get_project_root();
    let mut mapping = HashMap::new();

    // Equity indices
    mapping.insert(
        "ES.FUT".to_string(),
        format!(
            "{}/test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn",
            root
        ),
    );
    mapping.insert(
        "NQ.FUT".to_string(),
        format!(
            "{}/test_data/real/databento/NQ.FUT_ohlcv-1m_2024-01-02.dbn",
            root
        ),
    );

    // Commodities (Gold) - uncompressed version
    mapping.insert(
        "GC".to_string(),
        format!("{}/test_data/real/databento/GC_continuous_ohlcv-1m_2024-01-02_to_2024-01-31.uncompressed.dbn", root),
    );

    // Fixed income (Treasuries) - uncompressed version
    mapping.insert(
        "ZN.FUT".to_string(),
        format!(
            "{}/test_data/real/databento/ZN.FUT_ohlcv-1m_2024-01-02_to_2024-01-31.uncompressed.dbn",
            root
        ),
    );

    // Currencies (Euro FX) - uncompressed version
    mapping.insert(
        "6E.FUT".to_string(),
        format!(
            "{}/test_data/real/databento/6E.FUT_ohlcv-1m_2024-01-02_to_2024-01-31.uncompressed.dbn",
            root
        ),
    );

    mapping
}

#[tokio::test]
async fn test_load_all_symbols() -> Result<()> {
    let file_mapping = get_multi_symbol_file_mapping();
    let data_source = DbnDataSource::new(file_mapping).await?;

    // Test each symbol individually
    let symbols = vec!["ES.FUT", "NQ.FUT", "GC", "ZN.FUT", "6E.FUT"];

    for symbol in symbols {
        let bars = data_source.load_ohlcv_bars(symbol).await?;
        assert!(!bars.is_empty(), "{} should have data", symbol);
        println!("✅ {}: {} bars loaded", symbol, bars.len());

        // Validate all bars have correct symbol
        for bar in &bars {
            assert_eq!(bar.symbol, symbol, "Bar symbol mismatch");
        }
    }

    println!("✅ All 5 symbols loaded successfully");
    Ok(())
}

#[tokio::test]
async fn test_multi_symbol_loading() -> Result<()> {
    let file_mapping = get_multi_symbol_file_mapping();
    let data_source = DbnDataSource::new(file_mapping).await?;

    // Load multiple symbols together
    let symbols = vec![
        "ES.FUT".to_string(),
        "ZN.FUT".to_string(),
        "6E.FUT".to_string(),
    ];
    let bars = data_source.load_multi_symbol_bars(&symbols).await?;

    assert!(!bars.is_empty(), "Should load multi-symbol data");

    // Count bars per symbol
    let mut counts: HashMap<String, usize> = HashMap::new();
    for bar in &bars {
        *counts.entry(bar.symbol.clone()).or_insert(0) += 1;
    }

    println!("✅ Multi-symbol loading results:");
    for (symbol, count) in &counts {
        println!("   {}: {} bars", symbol, count);
    }

    // Verify all requested symbols are present
    for symbol in &symbols {
        assert!(
            counts.contains_key(symbol),
            "Symbol {} should be present",
            symbol
        );
        assert!(
            *counts.get(symbol).expect("INVARIANT: Key should exist in map") > 0,
            "Symbol {} should have bars",
            symbol
        );
    }

    Ok(())
}

#[tokio::test]
async fn test_asset_class_price_ranges() -> Result<()> {
    let file_mapping = get_multi_symbol_file_mapping();
    let data_source = DbnDataSource::new(file_mapping).await?;

    // Expected price ranges for each asset class
    let price_validations = vec![
        ("ES.FUT", 3000.0, 6000.0, "S&P 500 E-mini"),
        ("NQ.FUT", 10000.0, 20000.0, "NASDAQ E-mini"),
        ("GC", 1800.0, 2200.0, "Gold"),
        ("ZN.FUT", 100.0, 130.0, "10-Year Treasury"),
        ("6E.FUT", 1.00, 1.20, "Euro FX"),
    ];

    for (symbol, min_price, max_price, name) in price_validations {
        let bars = data_source.load_ohlcv_bars(symbol).await?;
        assert!(!bars.is_empty(), "{} should have data", symbol);

        for (i, bar) in bars.iter().enumerate() {
            let close_f64 = bar.close.to_f64().unwrap_or(0.0);
            assert!(
                close_f64 >= min_price && close_f64 <= max_price,
                "{} bar {}: price {} outside expected range [{}, {}]",
                name,
                i,
                close_f64,
                min_price,
                max_price
            );
        }

        println!(
            "✅ {}: Price range validation passed ({} bars)",
            name,
            bars.len()
        );
    }

    Ok(())
}

#[tokio::test]
async fn test_repository_multi_symbol() -> Result<()> {
    let file_mapping = get_multi_symbol_file_mapping();
    let repo = DbnMarketDataRepository::new(file_mapping).await?;

    // Test with 3 symbols
    let symbols = vec![
        "ES.FUT".to_string(),
        "ZN.FUT".to_string(),
        "6E.FUT".to_string(),
    ];

    // 2024-01-02 00:00:00 to 2024-01-03 00:00:00
    let start_time = 1704153600_000_000_000i64;
    let end_time = 1704240000_000_000_000i64;

    let data = repo
        .load_historical_data(&symbols, start_time, end_time)
        .await?;

    assert!(!data.is_empty(), "Repository should load multi-symbol data");

    // Verify symbol distribution
    let mut symbol_counts: HashMap<String, usize> = HashMap::new();
    for bar in &data {
        *symbol_counts.entry(bar.symbol.clone()).or_insert(0) += 1;
    }

    println!("✅ Repository multi-symbol loading:");
    for (symbol, count) in &symbol_counts {
        println!("   {}: {} bars", symbol, count);
    }

    // All symbols should have data
    assert_eq!(symbol_counts.len(), 3, "Should have 3 symbols");

    Ok(())
}

// Gated: check_data_availability method was removed from DbnMarketDataRepository
#[cfg(feature = "__backtesting_integration")]
#[tokio::test]
async fn test_data_availability_multi_symbol() -> Result<()> {
    let file_mapping = get_multi_symbol_file_mapping();
    let repo = DbnMarketDataRepository::new(file_mapping).await?;

    let symbols = vec![
        "ES.FUT".to_string(),
        "NQ.FUT".to_string(),
        "GC".to_string(),
        "ZN.FUT".to_string(),
        "6E.FUT".to_string(),
        "NONEXISTENT".to_string(),
    ];

    let start_time = 1704153600_000_000_000i64;
    let end_time = 1704240000_000_000_000i64;

    let availability = repo
        .check_data_availability(&symbols, start_time, end_time)
        .await?;

    // Verify availability
    assert_eq!(
        availability.get("ES.FUT"),
        Some(&true),
        "ES.FUT should be available"
    );
    assert_eq!(
        availability.get("NQ.FUT"),
        Some(&true),
        "NQ.FUT should be available"
    );
    assert_eq!(
        availability.get("GC"),
        Some(&true),
        "GC should be available"
    );
    assert_eq!(
        availability.get("ZN.FUT"),
        Some(&true),
        "ZN.FUT should be available"
    );
    assert_eq!(
        availability.get("6E.FUT"),
        Some(&true),
        "6E.FUT should be available"
    );
    assert_eq!(
        availability.get("NONEXISTENT"),
        Some(&false),
        "NONEXISTENT should not be available"
    );

    println!("✅ Data availability check:");
    for (symbol, available) in &availability {
        println!("   {}: {}", symbol, if *available { "✅" } else { "❌" });
    }

    Ok(())
}

#[tokio::test]
async fn test_multi_symbol_quality() -> Result<()> {
    let file_mapping = get_multi_symbol_file_mapping();
    let data_source = DbnDataSource::new(file_mapping).await?;

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

    for symbol in symbols {
        let bars = data_source.load_ohlcv_bars(symbol).await?;
        assert!(!bars.is_empty(), "{} should have data", symbol);

        let mut violations = 0;

        for (i, bar) in bars.iter().enumerate() {
            // Check OHLCV relationships
            if !(bar.high >= bar.low
                && bar.high >= bar.open
                && bar.high >= bar.close
                && bar.low <= bar.open
                && bar.low <= bar.close)
            {
                violations += 1;
                if violations <= 3 {
                    eprintln!(
                        "{} bar {}: OHLCV violation (O={}, H={}, L={}, C={})",
                        symbol, i, bar.open, bar.high, bar.low, bar.close
                    );
                }
            }

            // Check positive values
            assert!(
                bar.open > rust_decimal::Decimal::ZERO,
                "{} bar {}: open must be positive",
                symbol,
                i
            );
            assert!(
                bar.close > rust_decimal::Decimal::ZERO,
                "{} bar {}: close must be positive",
                symbol,
                i
            );
            assert!(
                bar.volume >= rust_decimal::Decimal::ZERO,
                "{} bar {}: volume must be non-negative",
                symbol,
                i
            );
        }

        println!(
            "✅ {}: {} bars validated, {} violations",
            symbol,
            bars.len(),
            violations
        );

        // Allow minimal violations (<1% of data)
        assert!(
            violations < bars.len() / 100,
            "{}: too many violations ({}/{})",
            symbol,
            violations,
            bars.len()
        );
    }

    Ok(())
}

#[tokio::test]
async fn test_cross_asset_correlation() -> Result<()> {
    let file_mapping = get_multi_symbol_file_mapping();
    let data_source = DbnDataSource::new(file_mapping).await?;

    // Load equity indices (ES and NQ should be correlated)
    let es_bars = data_source.load_ohlcv_bars("ES.FUT").await?;
    let nq_bars = data_source.load_ohlcv_bars("NQ.FUT").await?;

    assert!(!es_bars.is_empty(), "ES.FUT should have data");
    assert!(!nq_bars.is_empty(), "NQ.FUT should have data");

    println!("✅ Cross-asset data loading:");
    println!("   ES.FUT: {} bars", es_bars.len());
    println!("   NQ.FUT: {} bars", nq_bars.len());

    // Both should have similar bar counts (same trading session)
    let ratio = es_bars.len() as f64 / nq_bars.len() as f64;
    assert!(
        ratio > 0.8 && ratio < 1.2,
        "ES and NQ bar counts should be similar (ratio: {:.2})",
        ratio
    );

    println!("   Ratio: {:.2} (within expected range 0.8-1.2)", ratio);

    Ok(())
}

#[tokio::test]
async fn test_multi_symbol_performance() -> Result<()> {
    use std::time::Instant;

    let file_mapping = get_multi_symbol_file_mapping();
    let data_source = DbnDataSource::new(file_mapping).await?;

    // Warm-up
    let _ = data_source.load_ohlcv_bars("ES.FUT").await?;

    // Time multi-symbol loading
    let symbols = vec![
        "ES.FUT".to_string(),
        "ZN.FUT".to_string(),
        "6E.FUT".to_string(),
    ];

    let start = Instant::now();
    let bars = data_source.load_multi_symbol_bars(&symbols).await?;
    let duration = start.elapsed();

    println!("✅ Multi-symbol performance:");
    println!("   Total bars: {}", bars.len());
    println!("   Duration: {:?}", duration);
    println!(
        "   Throughput: {:.0} bars/sec",
        bars.len() as f64 / duration.as_secs_f64()
    );

    // Should be reasonably fast (<1 second for ~60K bars)
    assert!(
        duration.as_secs() < 1,
        "Multi-symbol loading should be fast (<1s), got {:?}",
        duration
    );

    Ok(())
}
