#![allow(
    clippy::assertions_on_constants,
    clippy::assertions_on_result_states,
    clippy::clone_on_copy,
    clippy::decimal_literal_representation,
    clippy::doc_markdown,
    clippy::empty_line_after_doc_comments,
    clippy::field_reassign_with_default,
    clippy::get_unwrap,
    clippy::identity_op,
    clippy::inconsistent_digit_grouping,
    clippy::indexing_slicing,
    clippy::integer_division,
    clippy::len_zero,
    clippy::let_underscore_must_use,
    clippy::manual_div_ceil,
    clippy::manual_let_else,
    clippy::manual_range_contains,
    clippy::modulo_arithmetic,
    clippy::needless_range_loop,
    clippy::non_ascii_literal,
    clippy::redundant_clone,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::single_match_else,
    clippy::str_to_string,
    clippy::string_slice,
    clippy::tests_outside_test_module,
    clippy::too_many_lines,
    clippy::unnecessary_wraps,
    clippy::unseparated_literal_suffix,
    clippy::use_debug,
    clippy::useless_vec,
    clippy::wildcard_enum_match_arm,
    clippy::else_if_without_else,
    clippy::expect_used,
    clippy::missing_const_for_fn,
    clippy::similar_names,
    clippy::type_complexity,
    clippy::collapsible_else_if,
    clippy::doc_lazy_continuation,
    clippy::items_after_test_module,
    clippy::map_clone,
    clippy::multiple_unsafe_ops_per_block,
    clippy::unwrap_or_default,
    clippy::assign_op_pattern,
    clippy::needless_borrow,
    clippy::println_empty_string,
    clippy::unnecessary_cast,
    clippy::used_underscore_binding,
    clippy::create_dir,
    clippy::implicit_saturating_sub,
    clippy::exit,
    clippy::expect_fun_call,
    clippy::too_many_arguments,
    clippy::unnecessary_map_or,
    clippy::unwrap_used,
    dead_code,
    unused_imports,
    unused_variables,
    clippy::cloned_ref_to_slice_refs,
    clippy::neg_multiply,
    clippy::while_let_loop,
    clippy::bool_assert_comparison,
    clippy::excessive_precision,
    clippy::trivially_copy_pass_by_ref,
    clippy::op_ref,
    clippy::redundant_closure,
    clippy::unnecessary_lazy_evaluations,
    clippy::if_then_some_else_none,
    clippy::unnecessary_to_owned,
    clippy::single_component_path_imports,
)]
/// TDD Tests for Imbalance Bars Implementation
///
/// Imbalance bars emit when cumulative buy/sell imbalance exceeds threshold.
/// Expected improvement: +15-20% Sharpe vs time bars due to better information capture.

#[cfg(test)]
mod imbalance_bars_tests {
    use chrono::{DateTime, Utc};

    // Implemented in ml/src/features/alternative_bars.rs
    use ml::features::alternative_bars::ImbalanceBarSampler;

    fn timestamp(secs: i64) -> DateTime<Utc> {
        DateTime::from_timestamp(secs, 0).unwrap()
    }

    #[test]
    fn test_buy_tick_classification() {
        // Buy tick: price increases
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        // First tick at $100 (no direction yet)
        assert!(sampler.update(100.0, 10.0, timestamp(0)).is_none());

        // Price increases to $101 -> buy tick
        assert!(sampler.update(101.0, 10.0, timestamp(1)).is_none());

        // Verify imbalance increased (buy side)
        assert!(
            sampler.get_imbalance() > 0.0,
            "Buy tick should increase imbalance"
        );
    }

    #[test]
    fn test_sell_tick_classification() {
        // Sell tick: price decreases
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        // First tick at $100
        assert!(sampler.update(100.0, 10.0, timestamp(0)).is_none());

        // Price decreases to $99 -> sell tick
        assert!(sampler.update(99.0, 10.0, timestamp(1)).is_none());

        // Verify imbalance decreased (sell side)
        assert!(
            sampler.get_imbalance() < 0.0,
            "Sell tick should decrease imbalance"
        );
    }

    #[test]
    fn test_cumulative_imbalance_calculation() {
        let mut sampler = ImbalanceBarSampler::new(100.0, 1000.0, timestamp(0));

        // Sequence of ticks with known direction
        sampler.update(100.0, 10.0, timestamp(0)); // Baseline
        sampler.update(101.0, 20.0, timestamp(1)); // Buy: +20
        sampler.update(102.0, 15.0, timestamp(2)); // Buy: +15
        sampler.update(101.0, 10.0, timestamp(3)); // Sell: -10
        sampler.update(102.0, 25.0, timestamp(4)); // Buy: +25

        // Expected cumulative imbalance: +20 +15 -10 +25 = +50
        let imbalance = sampler.get_imbalance();
        assert!(
            (imbalance - 50.0).abs() < 0.01,
            "Cumulative imbalance should be +50, got {}",
            imbalance
        );
    }

    #[test]
    fn test_bar_formation_at_positive_threshold() {
        // Threshold = 100, emit bar when imbalance >= 100
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        sampler.update(100.0, 10.0, timestamp(0)); // Baseline
        assert!(sampler.update(101.0, 50.0, timestamp(1)).is_none()); // +50
        assert!(sampler.update(102.0, 40.0, timestamp(2)).is_none()); // +90

        // Next buy tick should trigger bar (90 + 20 = 110 >= 100)
        let bar = sampler.update(103.0, 20.0, timestamp(3));
        assert!(
            bar.is_some(),
            "Bar should emit when imbalance exceeds threshold"
        );

        let bar = bar.unwrap();
        assert_eq!(bar.open, 100.0);
        assert_eq!(bar.high, 103.0);
        assert_eq!(bar.low, 100.0);
        assert_eq!(bar.close, 103.0);
        assert_eq!(bar.volume, 120.0); // 10+50+40+20

        // Imbalance should reset after bar emission
        assert_eq!(sampler.get_imbalance(), 0.0);
    }

    #[test]
    fn test_bar_formation_at_negative_threshold() {
        // Test sell-side imbalance triggering bar
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        sampler.update(100.0, 10.0, timestamp(0)); // Baseline
        assert!(sampler.update(99.0, 50.0, timestamp(1)).is_none()); // -50
        assert!(sampler.update(98.0, 40.0, timestamp(2)).is_none()); // -90

        // Next sell tick should trigger bar (-90 - 20 = -110, abs >= 100)
        let bar = sampler.update(97.0, 20.0, timestamp(3));
        assert!(
            bar.is_some(),
            "Bar should emit when negative imbalance exceeds threshold"
        );

        let bar = bar.unwrap();
        assert_eq!(bar.open, 100.0);
        assert_eq!(bar.high, 100.0);
        assert_eq!(bar.low, 97.0);
        assert_eq!(bar.close, 97.0);
    }

    #[test]
    fn test_balanced_market_no_bar() {
        // Balanced buy/sell should not trigger bars
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        sampler.update(100.0, 10.0, timestamp(0)); // Baseline

        // Alternating buy/sell of equal volume
        for i in 1..20 {
            let price = if i % 2 == 0 { 101.0 } else { 99.0 };
            let result = sampler.update(price, 10.0, timestamp(i));
            assert!(result.is_none(), "Balanced market should not emit bars");
        }

        // Imbalance should be near zero
        assert!(
            sampler.get_imbalance().abs() < 50.0,
            "Balanced market should have low imbalance"
        );
    }

    #[test]
    fn test_one_sided_flow() {
        // Strong directional flow should emit multiple bars
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        sampler.update(100.0, 10.0, timestamp(0)); // Baseline

        let mut bars_emitted = 0;
        for i in 1..=20 {
            let price = 100.0 + i as f64; // Monotonic price increase
            if let Some(_bar) = sampler.update(price, 30.0, timestamp(i)) {
                bars_emitted += 1;
            }
        }

        // With threshold=100 and 30 volume per tick, expect ~6 bars (600 total imbalance / 100)
        assert!(
            bars_emitted >= 5,
            "Strong directional flow should emit multiple bars, got {}",
            bars_emitted
        );
    }

    #[test]
    fn test_ewma_threshold_adaptation() {
        // EWMA threshold adapts to recent imbalance levels
        let mut sampler = ImbalanceBarSampler::new_with_ewma(100.0, 100.0, timestamp(0), 0.1);

        sampler.update(100.0, 10.0, timestamp(0)); // Baseline

        // Emit first bar
        for i in 1..=4 {
            sampler.update(100.0 + i as f64, 30.0, timestamp(i));
        }

        let initial_threshold = sampler.get_threshold();

        // Emit more bars with higher imbalance
        for i in 5..=20 {
            sampler.update(100.0 + i as f64, 50.0, timestamp(i));
        }

        let adapted_threshold = sampler.get_threshold();

        // Threshold should increase due to higher recent imbalance
        assert!(
            adapted_threshold > initial_threshold,
            "EWMA threshold should adapt upward with higher imbalance"
        );
    }

    #[test]
    fn test_multiple_bars_sequence() {
        // Test that multiple bars can be emitted in sequence
        let mut sampler = ImbalanceBarSampler::new(100.0, 50.0, timestamp(0));

        let mut bars = Vec::new();
        sampler.update(100.0, 10.0, timestamp(0));

        // Generate enough imbalance for 3 bars
        for i in 1..=10 {
            let price = 100.0 + i as f64;
            if let Some(bar) = sampler.update(price, 20.0, timestamp(i)) {
                bars.push(bar);
            }
        }

        assert!(
            bars.len() >= 3,
            "Should emit multiple bars in sequence, got {}",
            bars.len()
        );

        // Verify bars don't overlap
        for i in 1..bars.len() {
            assert!(
                bars[i].timestamp > bars[i - 1].timestamp,
                "Bars should be chronologically ordered"
            );
        }
    }

    #[test]
    fn test_zero_volume_tick() {
        // Zero volume ticks should not affect imbalance
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        sampler.update(100.0, 10.0, timestamp(0));
        sampler.update(101.0, 20.0, timestamp(1)); // +20 imbalance

        let imbalance_before = sampler.get_imbalance();

        sampler.update(102.0, 0.0, timestamp(2)); // Zero volume

        let imbalance_after = sampler.get_imbalance();

        assert_eq!(
            imbalance_before, imbalance_after,
            "Zero volume should not change imbalance"
        );
    }

    #[test]
    fn test_price_unchanged_tick() {
        // Price unchanged: use previous tick direction (MLFinLab convention)
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        sampler.update(100.0, 10.0, timestamp(0));
        sampler.update(101.0, 10.0, timestamp(1)); // Buy tick

        // Price unchanged -> should repeat last direction (buy)
        sampler.update(101.0, 10.0, timestamp(2));
        sampler.update(101.0, 10.0, timestamp(3));

        // Imbalance should continue increasing (all buy ticks)
        assert!(
            sampler.get_imbalance() >= 30.0,
            "Unchanged price should use previous direction"
        );
    }

    #[test]
    fn test_high_low_tracking() {
        // Verify high/low are correctly tracked within bar
        // Threshold=100, need cumulative imbalance of ±100 to emit bar
        let mut sampler = ImbalanceBarSampler::new(100.0, 100.0, timestamp(0));

        sampler.update(100.0, 10.0, timestamp(0)); // Open (imbalance=0, baseline)
        sampler.update(105.0, 20.0, timestamp(1)); // Buy: +20, total=+20 (high candidate)
        sampler.update(95.0, 15.0, timestamp(2)); // Sell: -15, total=+5 (low candidate)
        sampler.update(102.0, 25.0, timestamp(3)); // Buy: +25, total=+30
        sampler.update(108.0, 30.0, timestamp(4)); // Buy: +30, total=+60 (new high)
        sampler.update(103.0, 50.0, timestamp(5)); // Sell: -50, total=+10

        // Next buy tick pushes imbalance to +110 >= 100 → triggers bar
        let bar = sampler.update(104.0, 100.0, timestamp(6));
        assert!(
            bar.is_some(),
            "Bar should emit when imbalance reaches 110 (>= 100)"
        );

        let bar = bar.unwrap();
        assert_eq!(bar.open, 100.0);
        assert_eq!(bar.high, 108.0);
        assert_eq!(bar.low, 95.0);
        assert_eq!(bar.close, 104.0); // Last price before bar emission
    }
}
