//! MBP-10 Parser Tests
//!
//! Test suite for Market By Price (10 levels) order book parsing and snapshot aggregation.
//! Uses TDD methodology: tests written first, implementation follows.

#![allow(
    clippy::absurd_extreme_comparisons,
    clippy::assertions_on_constants,
    clippy::assertions_on_result_states,
    clippy::clone_on_copy,
    clippy::decimal_literal_representation,
    clippy::doc_lazy_continuation,
    clippy::doc_markdown,
    clippy::double_comparisons,
    clippy::else_if_without_else,
    clippy::empty_drop,
    clippy::expect_used,
    clippy::field_reassign_with_default,
    clippy::format_push_string,
    clippy::if_then_some_else_none,
    clippy::indexing_slicing,
    clippy::integer_division,
    clippy::len_zero,
    clippy::let_and_return,
    clippy::let_underscore_must_use,
    clippy::manual_let_else,
    clippy::manual_range_contains,
    clippy::map_err_ignore,
    clippy::missing_const_for_fn,
    clippy::modulo_arithmetic,
    clippy::needless_range_loop,
    clippy::new_without_default,
    clippy::non_ascii_literal,
    clippy::nonminimal_bool,
    clippy::octal_escapes,
    clippy::overly_complex_bool_expr,
    clippy::redundant_clone,
    clippy::shadow_reuse,
    clippy::shadow_unrelated,
    clippy::similar_names,
    clippy::single_component_path_imports,
    clippy::single_match_else,
    clippy::str_to_string,
    clippy::string_slice,
    clippy::tests_outside_test_module,
    clippy::unnecessary_cast,
    clippy::unnecessary_get_then_check,
    clippy::unnecessary_unwrap,
    clippy::unseparated_literal_suffix,
    clippy::unwrap_or_default,
    clippy::unwrap_used,
    clippy::use_debug,
    clippy::useless_vec,
    clippy::wildcard_enum_match_arm,
    dead_code,
    deprecated,
    unreachable_pub,
    unused_assignments,
    unused_comparisons,
    unused_imports,
    unused_variables
)]

use anyhow::Result;
use data::providers::databento::{
    dbn_parser::{DbnParser, ProcessedMessage},
    mbp10::{BidAskPair, Mbp10Snapshot, OrderBookAction},
};

/// Test MBP-10 snapshot creation from single update
#[tokio::test]
async fn test_mbp10_snapshot_creation() -> Result<()> {
    // Create test BidAskPair data
    let levels = vec![
        BidAskPair {
            bid_px: 150000000000000, // 150.000000 (scaled by 1e9)
            bid_sz: 100,
            bid_ct: 5,
            ask_px: 150010000000000, // 150.010000
            ask_sz: 120,
            ask_ct: 6,
        },
        BidAskPair {
            bid_px: 149990000000000,
            bid_sz: 200,
            bid_ct: 8,
            ask_px: 150020000000000,
            ask_sz: 180,
            ask_ct: 7,
        },
    ];

    let snapshot = Mbp10Snapshot::new(
        "ES.FUT".to_string(),
        1640995200000000000, // timestamp in nanoseconds
        levels.clone(),
        0,
        100,
    );

    assert_eq!(snapshot.symbol, "ES.FUT");
    assert_eq!(snapshot.levels.len(), 2);
    assert_eq!(snapshot.levels[0].bid_sz, 100);
    assert_eq!(snapshot.levels[0].ask_sz, 120);

    Ok(())
}

/// Test fixed-point price conversion
#[test]
fn test_mbp10_price_conversion() {
    let pair = BidAskPair {
        bid_px: 150000000000000, // 150.000000 * 1e9
        bid_sz: 100,
        bid_ct: 5,
        ask_px: 150010000000000, // 150.010000 * 1e9
        ask_sz: 120,
        ask_ct: 6,
    };

    let bid_price = BidAskPair::price_to_f64(pair.bid_px);
    let ask_price = BidAskPair::price_to_f64(pair.ask_px);

    assert!((bid_price - 150.0).abs() < 0.001);
    assert!((ask_price - 150.01).abs() < 0.001);
}

/// Test best bid/ask extraction
#[test]
fn test_mbp10_best_bid_ask() {
    let levels = vec![
        BidAskPair {
            bid_px: 150000000000000,
            bid_sz: 100,
            bid_ct: 5,
            ask_px: 150010000000000,
            ask_sz: 120,
            ask_ct: 6,
        },
        BidAskPair {
            bid_px: 149990000000000, // Lower bid
            bid_sz: 200,
            bid_ct: 8,
            ask_px: 150020000000000, // Higher ask
            ask_sz: 180,
            ask_ct: 7,
        },
    ];

    let snapshot = Mbp10Snapshot::new("ES.FUT".to_string(), 1640995200000000000, levels, 0, 100);

    let (best_bid, best_ask) = snapshot.get_best_bid_ask();
    assert!((best_bid - 150.0).abs() < 0.001);
    assert!((best_ask - 150.01).abs() < 0.001);
}

/// Test mid price calculation
#[test]
fn test_mbp10_mid_price() {
    let levels = vec![BidAskPair {
        bid_px: 150000000000000,
        bid_sz: 100,
        bid_ct: 5,
        ask_px: 150010000000000,
        ask_sz: 120,
        ask_ct: 6,
    }];

    let snapshot = Mbp10Snapshot::new("ES.FUT".to_string(), 1640995200000000000, levels, 0, 100);

    let mid = snapshot.mid_price();
    assert!((mid - 150.005).abs() < 0.001);
}

/// Test spread calculation
#[test]
fn test_mbp10_spread() {
    let levels = vec![BidAskPair {
        bid_px: 150000000000000,
        bid_sz: 100,
        bid_ct: 5,
        ask_px: 150010000000000,
        ask_sz: 120,
        ask_ct: 6,
    }];

    let snapshot = Mbp10Snapshot::new("ES.FUT".to_string(), 1640995200000000000, levels, 0, 100);

    let spread = snapshot.spread();
    assert!((spread - 0.01).abs() < 0.001);
}

/// Test total volume calculations
#[test]
fn test_mbp10_total_volumes() {
    let levels = vec![
        BidAskPair {
            bid_px: 150000000000000,
            bid_sz: 100,
            bid_ct: 5,
            ask_px: 150010000000000,
            ask_sz: 120,
            ask_ct: 6,
        },
        BidAskPair {
            bid_px: 149990000000000,
            bid_sz: 200,
            bid_ct: 8,
            ask_px: 150020000000000,
            ask_sz: 180,
            ask_ct: 7,
        },
    ];

    let snapshot = Mbp10Snapshot::new("ES.FUT".to_string(), 1640995200000000000, levels, 0, 100);

    assert_eq!(snapshot.total_bid_volume(), 300); // 100 + 200
    assert_eq!(snapshot.total_ask_volume(), 300); // 120 + 180
}

/// Test volume imbalance calculation
#[test]
fn test_mbp10_volume_imbalance() {
    let levels = vec![BidAskPair {
        bid_px: 150000000000000,
        bid_sz: 200, // More bid volume
        bid_ct: 5,
        ask_px: 150010000000000,
        ask_sz: 100,
        ask_ct: 6,
    }];

    let snapshot = Mbp10Snapshot::new("ES.FUT".to_string(), 1640995200000000000, levels, 0, 100);

    let imbalance = snapshot.volume_imbalance();
    // (200 - 100) / (200 + 100) = 100 / 300 ≈ 0.333
    assert!((imbalance - 0.333).abs() < 0.01);
}

/// Test order book depth
#[test]
fn test_mbp10_depth() {
    let levels = vec![
        BidAskPair {
            bid_px: 150000000000000,
            bid_sz: 100,
            bid_ct: 5,
            ask_px: 150010000000000,
            ask_sz: 120,
            ask_ct: 6,
        },
        BidAskPair {
            bid_px: 149990000000000,
            bid_sz: 200,
            bid_ct: 8,
            ask_px: 150020000000000,
            ask_sz: 180,
            ask_ct: 7,
        },
        BidAskPair {
            bid_px: 149980000000000,
            bid_sz: 150,
            bid_ct: 4,
            ask_px: 150030000000000,
            ask_sz: 160,
            ask_ct: 5,
        },
    ];

    let snapshot = Mbp10Snapshot::new("ES.FUT".to_string(), 1640995200000000000, levels, 0, 100);

    assert_eq!(snapshot.depth(), 3);
}

/// Test DBN parser MBP-10 file loading (integration test)
#[tokio::test]
#[ignore = "Requires real MBP-10 DBN file"]
async fn test_parse_mbp10_file() -> Result<()> {
    let parser = DbnParser::new()?;

    // This will be run with real MBP-10 test data
    let snapshots = parser
        .parse_mbp10_file("test_data/ES.FUT.mbp10.dbn")
        .await?;

    assert!(!snapshots.is_empty());
    assert_eq!(snapshots[0].levels.len(), 10);

    // Verify first snapshot has valid data
    let first = &snapshots[0];
    assert!(first.levels[0].bid_px > 0);
    assert!(first.levels[0].ask_px > first.levels[0].bid_px);
    assert!(first.levels[0].bid_sz > 0);
    assert!(first.levels[0].ask_sz > 0);

    Ok(())
}

/// Test snapshot aggregation from incremental MBP-10 updates
#[test]
fn test_mbp10_snapshot_aggregation() {
    // Test that we can aggregate multiple MBP-10 update messages
    // into a full 10-level order book snapshot
    let mut snapshot = Mbp10Snapshot::empty("ES.FUT".to_string());

    // Add first level (bid side)
    snapshot.update_level(
        0,
        OrderBookAction::Add,
        150000000000000,
        100,
        5,
        true, // is_bid
    );

    // Add first level (ask side)
    snapshot.update_level(
        0,
        OrderBookAction::Add,
        150010000000000,
        120,
        6,
        false, // is_ask
    );

    assert_eq!(snapshot.levels[0].bid_sz, 100);
    assert_eq!(snapshot.levels[0].ask_sz, 120);
}

/// Test performance: parse 1000 MBP-10 snapshots in <1ms
#[test]
#[ignore = "Performance benchmark"]
fn test_mbp10_parsing_performance() {
    use std::time::Instant;

    // Create 1000 test snapshots
    let test_level = BidAskPair {
        bid_px: 150000000000000,
        bid_sz: 100,
        bid_ct: 5,
        ask_px: 150010000000000,
        ask_sz: 120,
        ask_ct: 6,
    };

    let start = Instant::now();

    for _ in 0..1000 {
        let _snapshot = Mbp10Snapshot::new(
            "ES.FUT".to_string(),
            1640995200000000000,
            vec![test_level; 10],
            0,
            100,
        );
    }

    let elapsed = start.elapsed();
    println!("Parsed 1000 snapshots in {:?}", elapsed);

    // Target: <1ms total (1μs per snapshot)
    assert!(elapsed.as_micros() < 1000);
}
