//! Integration tests for parallel aggTrade fetching against live Binance API (Issue #257).
//!
//! These tests hit the real Binance REST API. They are included in the
//! `fromid_integration` test binary pattern.
//!
//! Run with: cargo nextest run -p opendeviationbar-providers --test parallel_fetch_integration

use std::sync::Arc;

use opendeviationbar_providers::binance::parallel_fetch::fetch_aggtrades_parallel;
use opendeviationbar_providers::binance::{AdaptiveRateLimiter, HistoricalDataLoader};

/// Helper: fetch a known starting trade ID for BTCUSDT via REST.
async fn get_anchor_trade_id() -> i64 {
    let loader = HistoricalDataLoader::new("BTCUSDT");
    let trade = loader.fetch_latest_aggtrade().await.unwrap();
    // Go back ~5000 trades for a decent test range
    trade.agg_trade_id - 5000
}

#[tokio::test]
async fn test_parallel_fetch_contiguity() {
    // Fetch 2000 trades via parallel fetch and verify trade-ID contiguity
    let from_id = get_anchor_trade_id().await;
    let to_id = from_id + 2000;
    let limiter = Arc::new(AdaptiveRateLimiter::binance_default());

    let trades = fetch_aggtrades_parallel("BTCUSDT", from_id, to_id, Some(limiter), None)
        .await
        .unwrap();

    assert!(!trades.is_empty(), "should fetch trades");

    // Verify strict contiguity: trades[i+1].agg_trade_id == trades[i].agg_trade_id + 1
    for window in trades.windows(2) {
        assert_eq!(
            window[1].agg_trade_id,
            window[0].agg_trade_id + 1,
            "trade-ID contiguity broken at {} -> {}",
            window[0].agg_trade_id,
            window[1].agg_trade_id
        );
    }

    // Verify range boundaries
    assert_eq!(trades.first().unwrap().agg_trade_id, from_id);
    assert!(trades.last().unwrap().agg_trade_id < to_id);
}

#[tokio::test]
async fn test_parallel_vs_sequential_identity() {
    // Parallel fetch should return identical trades as sequential fetch
    let from_id = get_anchor_trade_id().await;
    let to_id = from_id + 1500; // ~1.5 pages

    let limiter = Arc::new(AdaptiveRateLimiter::binance_default());

    // Parallel fetch
    let parallel_trades =
        fetch_aggtrades_parallel("BTCUSDT", from_id, to_id, Some(limiter.clone()), None)
            .await
            .unwrap();

    // Sequential fetch via HistoricalDataLoader (fromId pagination, 1000 per page)
    let loader = HistoricalDataLoader::new("BTCUSDT").with_rate_limiter(limiter);
    let mut sequential_trades = Vec::new();
    let mut cursor = from_id;
    while cursor < to_id {
        let page = loader
            .fetch_aggtrades_by_id(cursor, 1000)
            .await
            .unwrap();
        if page.is_empty() {
            break;
        }
        cursor = page.last().unwrap().agg_trade_id + 1;
        for t in page {
            if t.agg_trade_id < to_id {
                sequential_trades.push(t);
            }
        }
    }

    // Same count
    assert_eq!(
        parallel_trades.len(),
        sequential_trades.len(),
        "parallel ({}) vs sequential ({}) trade count mismatch",
        parallel_trades.len(),
        sequential_trades.len()
    );

    // Same trade IDs
    for (p, s) in parallel_trades.iter().zip(sequential_trades.iter()) {
        assert_eq!(
            p.agg_trade_id, s.agg_trade_id,
            "trade ID mismatch at position"
        );
    }
}

#[tokio::test]
async fn test_parallel_fetch_rate_limiter_not_exhausted() {
    // Even with parallel requests, the rate limiter should not be exhausted
    let from_id = get_anchor_trade_id().await;
    let to_id = from_id + 3000; // 3 pages
    let limiter = Arc::new(AdaptiveRateLimiter::binance_default());

    let _trades = fetch_aggtrades_parallel("BTCUSDT", from_id, to_id, Some(limiter.clone()), None)
        .await
        .unwrap();

    // Rate limiter should still have budget remaining (3 pages = 12 weight out of 1920)
    let utilization = limiter.utilization();
    assert!(
        utilization < 0.95,
        "rate limiter utilization too high: {utilization:.2}"
    );
}

#[tokio::test]
async fn test_parallel_fetch_small_gap_single_request() {
    // Gaps <= 1000 trades should use single fetch (no JoinSet overhead)
    let from_id = get_anchor_trade_id().await;
    let to_id = from_id + 500; // < 1000 — single page

    let trades = fetch_aggtrades_parallel("BTCUSDT", from_id, to_id, None, None)
        .await
        .unwrap();

    assert!(!trades.is_empty());
    assert_eq!(trades.first().unwrap().agg_trade_id, from_id);

    // Verify contiguity even for small gaps
    for window in trades.windows(2) {
        assert_eq!(window[1].agg_trade_id, window[0].agg_trade_id + 1);
    }
}
