#![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,
)]
//! Comprehensive tests for HFT barrier presets
//!
//! This test suite validates the barrier preset functionality for DQN training,
//! ensuring that scalping and mean-reversion presets produce appropriate episode
//! characteristics and trading behavior.

use ml::labeling::triple_barrier::{BarrierTracker, PricePoint};
use ml::labeling::types::{BarrierConfig, BarrierResult};

#[test]
fn test_scalping_preset_params() {
    // Scalping preset: 7 bps profit, 4 bps stop, 30 seconds
    let config = BarrierConfig {
        profit_target_bps: 7,
        stop_loss_bps: 4,
        max_holding_period_ns: 30_000_000_000, // 30 seconds in nanoseconds
        min_return_threshold_bps: 1,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };

    let entry_price_cents = 500_000; // $5,000.00 ES futures
    let entry_timestamp_ns = 1_700_000_000_000_000_000; // Nov 14, 2023

    let tracker = BarrierTracker::new(entry_price_cents, entry_timestamp_ns, config);

    // Verify barrier calculations
    // Profit target: 5000 * (7/10000) = $3.50 = 350 cents
    let expected_upper = entry_price_cents + (entry_price_cents * 7) / 10_000;
    assert_eq!(
        tracker.upper_barrier_cents, expected_upper,
        "Scalping profit target should be 7 bps above entry"
    );

    // Stop loss: 5000 * (4/10000) = $2.00 = 200 cents
    let expected_lower = entry_price_cents - (entry_price_cents * 4) / 10_000;
    assert_eq!(
        tracker.lower_barrier_cents, expected_lower,
        "Scalping stop loss should be 4 bps below entry"
    );

    // Time limit: 30 seconds
    let expected_expiry = entry_timestamp_ns + 30_000_000_000;
    assert_eq!(
        tracker.expiry_timestamp_ns, expected_expiry,
        "Scalping max hold time should be 30 seconds"
    );

    // Verify risk/reward ratio
    let profit_range = tracker.upper_barrier_cents - entry_price_cents;
    let loss_range = entry_price_cents - tracker.lower_barrier_cents;
    let rr_ratio = profit_range as f64 / loss_range as f64;
    assert!(
        (rr_ratio - 1.75).abs() < 0.1,
        "Scalping RR ratio should be ~1.75 (got {})",
        rr_ratio
    );
}

#[test]
fn test_mean_reversion_preset_params() {
    // Mean-reversion preset: 25 bps profit, 12 bps stop, 150 seconds
    let config = BarrierConfig {
        profit_target_bps: 25,
        stop_loss_bps: 12,
        max_holding_period_ns: 150_000_000_000, // 150 seconds (2.5 minutes)
        min_return_threshold_bps: 5,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };

    let entry_price_cents = 500_000; // $5,000.00
    let entry_timestamp_ns = 1_700_000_000_000_000_000;

    let tracker = BarrierTracker::new(entry_price_cents, entry_timestamp_ns, config);

    // Profit target: 5000 * (25/10000) = $12.50 = 1250 cents
    let expected_upper = entry_price_cents + (entry_price_cents * 25) / 10_000;
    assert_eq!(
        tracker.upper_barrier_cents, expected_upper,
        "Mean-reversion profit target should be 25 bps above entry"
    );

    // Stop loss: 5000 * (12/10000) = $6.00 = 600 cents
    let expected_lower = entry_price_cents - (entry_price_cents * 12) / 10_000;
    assert_eq!(
        tracker.lower_barrier_cents, expected_lower,
        "Mean-reversion stop loss should be 12 bps below entry"
    );

    // Time limit: 150 seconds
    let expected_expiry = entry_timestamp_ns + 150_000_000_000;
    assert_eq!(
        tracker.expiry_timestamp_ns, expected_expiry,
        "Mean-reversion max hold time should be 150 seconds"
    );

    // Verify risk/reward ratio
    let profit_range = tracker.upper_barrier_cents - entry_price_cents;
    let loss_range = entry_price_cents - tracker.lower_barrier_cents;
    let rr_ratio = profit_range as f64 / loss_range as f64;
    assert!(
        (rr_ratio - 2.08).abs() < 0.1,
        "Mean-reversion RR ratio should be ~2.08 (got {})",
        rr_ratio
    );
}

#[test]
fn test_generic_preset_params() {
    // Generic preset: 100 bps profit, 50 bps stop, 3600 seconds
    let config = BarrierConfig {
        profit_target_bps: 100,
        stop_loss_bps: 50,
        max_holding_period_ns: 3_600_000_000_000, // 1 hour
        min_return_threshold_bps: 10,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };

    let entry_price_cents = 500_000;
    let entry_timestamp_ns = 1_700_000_000_000_000_000;

    let tracker = BarrierTracker::new(entry_price_cents, entry_timestamp_ns, config);

    // Profit target: 5000 * (100/10000) = $50.00 = 5000 cents
    let expected_upper = entry_price_cents + (entry_price_cents * 100) / 10_000;
    assert_eq!(tracker.upper_barrier_cents, expected_upper);

    // Stop loss: 5000 * (50/10000) = $25.00 = 2500 cents
    let expected_lower = entry_price_cents - (entry_price_cents * 50) / 10_000;
    assert_eq!(tracker.lower_barrier_cents, expected_lower);

    // Time limit: 1 hour
    let expected_expiry = entry_timestamp_ns + 3_600_000_000_000;
    assert_eq!(tracker.expiry_timestamp_ns, expected_expiry);

    // Verify risk/reward ratio
    let profit_range = tracker.upper_barrier_cents - entry_price_cents;
    let loss_range = entry_price_cents - tracker.lower_barrier_cents;
    let rr_ratio = profit_range as f64 / loss_range as f64;
    assert!(
        (rr_ratio - 2.0).abs() < 0.01,
        "Generic RR ratio should be 2.0 (got {})",
        rr_ratio
    );
}

#[test]
fn test_scalping_episode_duration() {
    // Scalping episodes should terminate quickly (profit or stop hit within 30s)
    let config = BarrierConfig {
        profit_target_bps: 7,
        stop_loss_bps: 4,
        max_holding_period_ns: 30_000_000_000,
        min_return_threshold_bps: 1,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };

    let entry_price_cents = 500_000;
    let entry_timestamp_ns = 1_700_000_000_000_000_000;
    let mut tracker = BarrierTracker::new(entry_price_cents, entry_timestamp_ns, config.clone());

    // Test 1: Profit target hit after 5 seconds
    let profit_price = tracker.upper_barrier_cents;
    let profit_time = entry_timestamp_ns + 5_000_000_000; // 5 seconds
    let profit_point = PricePoint::new(profit_price, profit_time);

    let result = tracker.update(profit_point);
    assert!(result.is_some(), "Should hit profit target");
    let label = result.unwrap();
    assert!(matches!(label.barrier_result, BarrierResult::ProfitTarget));
    assert_eq!(label.label_value, 1); // Profitable exit

    // Test 2: Stop loss hit after 3 seconds
    let mut tracker2 = BarrierTracker::new(entry_price_cents, entry_timestamp_ns, config.clone());
    let stop_price = tracker2.lower_barrier_cents;
    let stop_time = entry_timestamp_ns + 3_000_000_000; // 3 seconds
    let stop_point = PricePoint::new(stop_price, stop_time);

    let result2 = tracker2.update(stop_point);
    assert!(result2.is_some(), "Should hit stop loss");
    let label2 = result2.unwrap();
    assert!(matches!(label2.barrier_result, BarrierResult::StopLoss));
    assert_eq!(label2.label_value, -1); // Loss exit

    // Test 3: Time expiry at 30 seconds
    let mut tracker3 = BarrierTracker::new(entry_price_cents, entry_timestamp_ns, config);
    let expiry_price = entry_price_cents; // No movement
    let expiry_time = entry_timestamp_ns + 30_000_000_000; // Exactly 30s
    let expiry_point = PricePoint::new(expiry_price, expiry_time);

    let result3 = tracker3.update(expiry_point);
    assert!(result3.is_some(), "Should expire at 30 seconds");
    let label3 = result3.unwrap();
    assert!(matches!(label3.barrier_result, BarrierResult::TimeExpiry));
}

#[test]
fn test_mean_reversion_episode_duration() {
    // Mean-reversion episodes should allow longer holds (up to 150s)
    let config = BarrierConfig {
        profit_target_bps: 25,
        stop_loss_bps: 12,
        max_holding_period_ns: 150_000_000_000,
        min_return_threshold_bps: 5,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };

    let entry_price_cents = 500_000;
    let entry_timestamp_ns = 1_700_000_000_000_000_000;

    // Test: Patient hold up to 150 seconds
    let mut tracker = BarrierTracker::new(entry_price_cents, entry_timestamp_ns, config);

    // Update at 100 seconds with no barrier hit (price unchanged)
    let mid_time = entry_timestamp_ns + 100_000_000_000;
    let mid_point = PricePoint::new(entry_price_cents, mid_time);
    let mid_result = tracker.update(mid_point);
    assert!(mid_result.is_none(), "Should not terminate at 100s");

    // Update at 150 seconds (expiry)
    let expiry_time = entry_timestamp_ns + 150_000_000_000;
    let expiry_point = PricePoint::new(entry_price_cents, expiry_time);
    let expiry_result = tracker.update(expiry_point);
    assert!(expiry_result.is_some(), "Should expire at 150s");
    let label = expiry_result.unwrap();
    assert!(matches!(label.barrier_result, BarrierResult::TimeExpiry));
}

#[test]
fn test_scalping_vs_mean_reversion_time_horizon() {
    // Compare episode duration expectations for different presets
    let scalping_config = BarrierConfig {
        profit_target_bps: 7,
        stop_loss_bps: 4,
        max_holding_period_ns: 30_000_000_000,
        min_return_threshold_bps: 1,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };

    let mean_rev_config = BarrierConfig {
        profit_target_bps: 25,
        stop_loss_bps: 12,
        max_holding_period_ns: 150_000_000_000,
        min_return_threshold_bps: 5,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };

    let entry_price_cents = 500_000;
    let entry_timestamp_ns = 1_700_000_000_000_000_000;

    let scalping_tracker =
        BarrierTracker::new(entry_price_cents, entry_timestamp_ns, scalping_config);
    let mean_rev_tracker =
        BarrierTracker::new(entry_price_cents, entry_timestamp_ns, mean_rev_config);

    // Mean-reversion should allow 5x longer holds
    let scalping_duration =
        scalping_tracker.expiry_timestamp_ns - scalping_tracker.entry_timestamp_ns;
    let mean_rev_duration =
        mean_rev_tracker.expiry_timestamp_ns - mean_rev_tracker.entry_timestamp_ns;

    assert_eq!(
        scalping_duration, 30_000_000_000,
        "Scalping max hold: 30s"
    );
    assert_eq!(
        mean_rev_duration, 150_000_000_000,
        "Mean-reversion max hold: 150s"
    );
    assert_eq!(
        mean_rev_duration / scalping_duration,
        5,
        "Mean-reversion allows 5x longer holds"
    );

    // Mean-reversion should have wider profit targets (3.6x)
    let scalping_profit_range =
        scalping_tracker.upper_barrier_cents - scalping_tracker.entry_price_cents;
    let mean_rev_profit_range =
        mean_rev_tracker.upper_barrier_cents - mean_rev_tracker.entry_price_cents;

    assert!(
        mean_rev_profit_range > scalping_profit_range * 3,
        "Mean-reversion profit target should be >3x wider (scalping: {} cents, mean-rev: {} cents)",
        scalping_profit_range,
        mean_rev_profit_range
    );
}

#[test]
fn test_preset_barrier_hit_probability() {
    // Validate that barrier parameters are realistic for HFT
    // ES futures typical tick: 0.25 points = 0.005% = 0.5 bps
    // Scalping targets should be reachable within a few ticks

    let entry_price_cents = 500_000; // $5,000
    let entry_timestamp_ns = 1_700_000_000_000_000_000;

    // Scalping: 7 bps = 14 ticks (0.25pt each), very reachable
    let scalping_config = BarrierConfig {
        profit_target_bps: 7,
        stop_loss_bps: 4,
        max_holding_period_ns: 30_000_000_000,
        min_return_threshold_bps: 1,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };
    let scalping_tracker =
        BarrierTracker::new(entry_price_cents, entry_timestamp_ns, scalping_config);

    // Calculate ticks to profit
    let profit_cents = scalping_tracker.upper_barrier_cents - entry_price_cents;
    let tick_size_cents = 25; // 0.25 points = 25 cents for ES
    let ticks_to_profit = profit_cents / tick_size_cents;

    assert!(
        ticks_to_profit >= 10 && ticks_to_profit <= 20,
        "Scalping profit should be 10-20 ticks (got {})",
        ticks_to_profit
    );

    // Mean-reversion: 25 bps = 50 ticks, reachable within 2-5 minutes
    let mean_rev_config = BarrierConfig {
        profit_target_bps: 25,
        stop_loss_bps: 12,
        max_holding_period_ns: 150_000_000_000,
        min_return_threshold_bps: 5,
        use_sample_weights: true,
        volatility_lookback_periods: Some(20),
    };
    let mean_rev_tracker =
        BarrierTracker::new(entry_price_cents, entry_timestamp_ns, mean_rev_config);

    let mean_rev_profit_cents = mean_rev_tracker.upper_barrier_cents - entry_price_cents;
    let mean_rev_ticks = mean_rev_profit_cents / tick_size_cents;

    assert!(
        mean_rev_ticks >= 40 && mean_rev_ticks <= 60,
        "Mean-reversion profit should be 40-60 ticks (got {})",
        mean_rev_ticks
    );
}

#[test]
fn test_barrier_config_conservative() {
    // Test BarrierConfig::conservative() helper method
    let config = BarrierConfig::conservative();

    // Conservative config should use generic preset values
    assert_eq!(config.profit_target_bps, 100, "Conservative profit: 100 bps");
    assert_eq!(config.stop_loss_bps, 50, "Conservative stop: 50 bps");
    
    // Max holding period should be 1 hour (3600 seconds)
    let expected_ns = 3_600_000_000_000; // 3600s in nanoseconds
    assert_eq!(
        config.max_holding_period_ns, expected_ns,
        "Conservative max hold: 1 hour"
    );
}

#[cfg(test)]
mod integration_tests {
    use super::*;

    #[test]
    fn test_scalping_realistic_scenario() {
        // Simulate 10 scalping trades over 5 minutes
        let config = BarrierConfig {
            profit_target_bps: 7,
            stop_loss_bps: 4,
            max_holding_period_ns: 30_000_000_000,
            min_return_threshold_bps: 1,
            use_sample_weights: true,
            volatility_lookback_periods: Some(20),
        };

        let mut successful_trades = 0;
        let mut total_return_bps = 0i64;

        // Entry price: $5,000.00
        let base_price_cents = 500_000;
        let base_timestamp_ns = 1_700_000_000_000_000_000;

        // Simulate 10 trades with realistic outcomes
        for i in 0..10 {
            let entry_time = base_timestamp_ns + (i * 30_000_000_000); // 30s apart
            let mut tracker = BarrierTracker::new(base_price_cents, entry_time, config.clone());

            // 60% hit profit, 30% hit stop, 10% time out
            let outcome = i % 10;
            let result = if outcome < 6 {
                // Profit target (7 bps gain)
                let profit_price = tracker.upper_barrier_cents;
                let profit_time = entry_time + 5_000_000_000; // 5s
                tracker.update(PricePoint::new(profit_price, profit_time))
            } else if outcome < 9 {
                // Stop loss (4 bps loss)
                let stop_price = tracker.lower_barrier_cents;
                let stop_time = entry_time + 8_000_000_000; // 8s
                tracker.update(PricePoint::new(stop_price, stop_time))
            } else {
                // Time expiry (neutral)
                let expiry_price = base_price_cents;
                let expiry_time = entry_time + 30_000_000_000;
                tracker.update(PricePoint::new(expiry_price, expiry_time))
            };

            if let Some(label) = result {
                if label.label_value == 1 {
                    successful_trades += 1;
                }
                total_return_bps += label.return_bps as i64;
            }
        }

        // Expected: 60% win rate, positive total return
        assert_eq!(
            successful_trades, 6,
            "Should have 6 profitable trades (60% win rate)"
        );

        // Net return: 6 trades × 7 bps - 3 trades × 4 bps = 42 - 12 = 30 bps
        assert!(
            total_return_bps > 0,
            "Scalping should be profitable with 60% win rate (got {} bps)",
            total_return_bps
        );
    }

    #[test]
    fn test_mean_reversion_realistic_scenario() {
        // Simulate mean-reversion trade with patient hold
        let config = BarrierConfig {
            profit_target_bps: 25,
            stop_loss_bps: 12,
            max_holding_period_ns: 150_000_000_000,
            min_return_threshold_bps: 5,
            use_sample_weights: true,
            volatility_lookback_periods: Some(20),
        };

        let entry_price_cents = 500_000;
        let entry_time_ns = 1_700_000_000_000_000_000;
        let mut tracker = BarrierTracker::new(entry_price_cents, entry_time_ns, config);

        // Price drifts down initially (15 bps loss), then reverts to profit
        // This tests the patience of mean-reversion strategy

        // Update 1: Price drops 10 bps (within stop loss tolerance)
        let down_price = entry_price_cents - (entry_price_cents * 10) / 10_000;
        let down_time = entry_time_ns + 30_000_000_000; // 30s
        let result1 = tracker.update(PricePoint::new(down_price, down_time));
        assert!(result1.is_none(), "Should not trigger stop at -10 bps");

        // Update 2: Price reverts up to +25 bps (profit target)
        let up_price = tracker.upper_barrier_cents;
        let up_time = entry_time_ns + 120_000_000_000; // 120s (2 minutes)
        let result2 = tracker.update(PricePoint::new(up_price, up_time));
        assert!(result2.is_some(), "Should hit profit target on reversion");

        let label = result2.unwrap();
        assert_eq!(label.label_value, 1, "Should be profitable exit");
        assert!(
            label.return_bps >= 20,
            "Should capture at least 20 bps (got {})",
            label.return_bps
        );
    }
}
