#![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,
)]
//! DQN Kelly Criterion and Regime Detection Integration Tests
//!
//! Comprehensive integration tests for Kelly criterion position sizing
//! and regime detection features integration with DQN training.
//!
//! Test Coverage:
//! 1. Kelly fraction varies with performance (60% win → Kelly ~0.8, 40% → ~0.2)
//! 2. Regime features change over time (trending vs ranging different vectors)
//! 3. Kelly scales position size (Kelly=0.5 → half position vs Kelly=1.0)
//! 4. Regime detection populates all 5 features [type, confidence, cusum+, cusum-, adx]
//! 5. Kelly applied in backtest (EvaluationEngine PnL reflects Kelly scaling)
//! 6. Regime features in state tensor (state shape [batch, 133] = 128 + 5)

use ml::dqn::action_space::{ExposureLevel, FactoredAction, OrderType, Urgency};
use ml::dqn::agent::TradingState;
use ml::dqn::portfolio_tracker::PortfolioTracker;
use ml::risk::kelly_optimizer::KellyCriterionOptimizer;
use ml::risk::kelly_optimizer::KellyOptimizerConfig;

/// Helper: Create test trading state
fn create_test_state(portfolio_value: f32, position_size: f32) -> TradingState {
    TradingState {
        price_features: vec![0.0; 4],
        technical_indicators: vec![0.5; 121],
        market_features: vec![],
        portfolio_features: vec![portfolio_value, position_size, 0.001],
        regime_features: vec![],
    }
}

/// Helper: Create test state with regime features
fn create_test_state_with_regime(
    portfolio_value: f32,
    position_size: f32,
    regime_features: Vec<f32>,
) -> TradingState {
    TradingState {
        price_features: vec![0.0; 4],
        technical_indicators: vec![0.5; 121],
        market_features: vec![],
        portfolio_features: vec![portfolio_value, position_size, 0.001],
        regime_features,
    }
}

/// Test 1: Kelly fraction varies with performance metrics
///
/// **EXPECTED**:
/// - High win rate (60%) → Higher Kelly fraction (~0.7-0.9)
/// - Low win rate (40%) → Lower Kelly fraction (~0.1-0.3)
/// - Kelly fraction clamped to [0.01, 0.25] (safety bounds)
#[test]
fn test_kelly_fraction_varies_with_performance() {
    let config = KellyOptimizerConfig {
        max_fraction: 0.25,
        min_fraction: 0.01,
        lookback_period: 100,
        confidence_threshold: 0.6,
        volatility_adjustment: true,
        drawdown_protection: true,
    };

    let kelly_optimizer = KellyCriterionOptimizer::new(config).unwrap();

    // Scenario 1: High win rate (60%), favorable odds
    let kelly_high = kelly_optimizer
        .calculate_basic_kelly(
            0.60,  // 60% win rate
            100.0, // Avg win: $100
            50.0,  // Avg loss: $50 (2:1 win/loss ratio)
        )
        .unwrap();

    // Scenario 2: Low win rate (40%), unfavorable odds
    let kelly_low = kelly_optimizer
        .calculate_basic_kelly(
            0.40,  // 40% win rate
            100.0, // Avg win: $100
            50.0,  // Avg loss: $50 (2:1 win/loss ratio, but low win rate)
        )
        .unwrap();

    // Kelly formula: f = (bp - q) / b, where b = odds, p = win_prob, q = 1 - p
    // High: (2 × 0.6 - 0.4) / 2 = (1.2 - 0.4) / 2 = 0.4 → clamped to 0.25 (max)
    // Low:  (2 × 0.4 - 0.6) / 2 = (0.8 - 0.6) / 2 = 0.1

    assert!(
        kelly_high >= 0.20,
        "High win rate (60%) should produce Kelly fraction ≥ 0.20, got {}",
        kelly_high
    );

    assert!(
        kelly_low <= 0.15,
        "Low win rate (40%) should produce Kelly fraction ≤ 0.15, got {}",
        kelly_low
    );

    assert!(
        kelly_high > kelly_low * 1.5,
        "High performance Kelly ({}) should be significantly larger than low performance ({})",
        kelly_high,
        kelly_low
    );
}

/// Test 2: Regime features change over time (trending vs ranging)
///
/// **EXPECTED**: Different market regimes produce different feature vectors
/// - Trending regime: High ADX (>25), directional CUSUM
/// - Ranging regime: Low ADX (<20), balanced CUSUM
#[test]
fn test_regime_features_change_over_time() {
    // Simulate trending regime features
    let trending_regime = vec![
        1.0, // Regime type: 1 = Trending
        0.85, // Confidence: 85%
        5.0, // CUSUM positive: uptrend
        0.0, // CUSUM negative: no downtrend
        35.0, // ADX: strong trend
    ];

    // Simulate ranging regime features
    let ranging_regime = vec![
        2.0, // Regime type: 2 = Ranging
        0.75, // Confidence: 75%
        2.0, // CUSUM positive: weak uptrend
        2.0, // CUSUM negative: weak downtrend (balanced)
        15.0, // ADX: weak trend
    ];

    let state_trending = create_test_state_with_regime(100_000.0, 0.0, trending_regime.clone());
    let state_ranging = create_test_state_with_regime(100_000.0, 0.0, ranging_regime.clone());

    // Verify regime features are populated
    assert_eq!(
        state_trending.regime_features.len(),
        5,
        "Trending regime should have 5 features"
    );
    assert_eq!(
        state_ranging.regime_features.len(),
        5,
        "Ranging regime should have 5 features"
    );

    // Verify regime type differs
    assert_ne!(
        state_trending.regime_features[0], state_ranging.regime_features[0],
        "Regime types should differ (trending vs ranging)"
    );

    // Verify ADX differs significantly (trending > ranging)
    assert!(
        state_trending.regime_features[4] > state_ranging.regime_features[4] * 1.5,
        "Trending ADX ({}) should be significantly higher than ranging ADX ({})",
        state_trending.regime_features[4],
        state_ranging.regime_features[4]
    );

    // Verify CUSUM balance differs (trending directional, ranging balanced)
    let trending_cusum_imbalance =
        (state_trending.regime_features[2] - state_trending.regime_features[3]).abs();
    let ranging_cusum_imbalance =
        (state_ranging.regime_features[2] - state_ranging.regime_features[3]).abs();

    assert!(
        trending_cusum_imbalance > ranging_cusum_imbalance,
        "Trending CUSUM should be more imbalanced ({}) than ranging ({})",
        trending_cusum_imbalance,
        ranging_cusum_imbalance
    );
}

/// Test 3: Kelly scales position size correctly
///
/// **EXPECTED**: Kelly fraction directly scales position sizing
/// - Kelly=0.5 → Target position = 50% of max_position
/// - Kelly=1.0 → Target position = 100% of max_position
#[test]
fn test_kelly_scales_position_size() {
    let initial_capital = 10_000.0;
    let price = 100.0;
    let max_position = 100.0; // 100 contracts

    // Test 1: Kelly = 0.5 (half Kelly)
    let mut tracker_half = PortfolioTracker::new(initial_capital, 0.0001, 0.0);
    let long_action = FactoredAction::new(
        ExposureLevel::Long100,
        OrderType::Market,
        Urgency::Normal,
    );

    tracker_half.execute_action_with_kelly(long_action, price, max_position, 0.5);
    let position_half = tracker_half.current_position();

    // Expected: 0.5 × 100 = 50 contracts
    assert!(
        (position_half - 50.0).abs() < 1.0,
        "Kelly=0.5 should result in ~50 contracts, got {}",
        position_half
    );

    // Test 2: Kelly = 1.0 (full Kelly)
    let mut tracker_full = PortfolioTracker::new(initial_capital, 0.0001, 0.0);
    tracker_full.execute_action_with_kelly(long_action, price, max_position, 1.0);
    let position_full = tracker_full.current_position();

    // Expected: 1.0 × 100 = 100 contracts
    assert!(
        (position_full - 100.0).abs() < 1.0,
        "Kelly=1.0 should result in ~100 contracts, got {}",
        position_full
    );

    // Verify 2x scaling
    assert!(
        (position_full / position_half - 2.0).abs() < 0.1,
        "Full Kelly ({}) should be 2x half Kelly ({})",
        position_full,
        position_half
    );
}

/// Test 4: Regime detection populates all 5 features correctly
///
/// **EXPECTED**: Regime features = [regime_type, confidence, cusum_plus, cusum_minus, adx]
/// - regime_type: 1 = Trending, 2 = Ranging, 3 = Volatile, 4 = Transition
/// - confidence: 0.0-1.0 (ADX-based confidence score)
/// - cusum_plus: Positive cumulative sum (uptrend strength)
/// - cusum_minus: Negative cumulative sum (downtrend strength)
/// - adx: Average Directional Index (trend strength indicator)
#[test]
fn test_regime_detection_populates_all_5_features() {
    // Simulate realistic regime detection output
    let regime_features = vec![
        1.0, // Regime type: Trending (1)
        0.82, // Confidence: 82% (ADX-derived)
        4.5, // CUSUM+: Strong uptrend signal
        0.3, // CUSUM-: Weak downtrend signal
        30.0, // ADX: Strong trend (>25)
    ];

    let state = create_test_state_with_regime(100_000.0, 0.0, regime_features.clone());

    // Verify all 5 features present
    assert_eq!(
        state.regime_features.len(),
        5,
        "Regime features must have exactly 5 elements"
    );

    // Verify regime type is valid (1-4)
    assert!(
        state.regime_features[0] >= 1.0 && state.regime_features[0] <= 4.0,
        "Regime type must be 1-4, got {}",
        state.regime_features[0]
    );

    // Verify confidence is valid (0-1)
    assert!(
        state.regime_features[1] >= 0.0 && state.regime_features[1] <= 1.0,
        "Confidence must be 0-1, got {}",
        state.regime_features[1]
    );

    // Verify CUSUM values are non-negative
    assert!(
        state.regime_features[2] >= 0.0,
        "CUSUM+ must be non-negative, got {}",
        state.regime_features[2]
    );
    assert!(
        state.regime_features[3] >= 0.0,
        "CUSUM- must be non-negative, got {}",
        state.regime_features[3]
    );

    // Verify ADX is reasonable (0-100, typically 0-60)
    assert!(
        state.regime_features[4] >= 0.0 && state.regime_features[4] <= 100.0,
        "ADX must be 0-100, got {}",
        state.regime_features[4]
    );
}

/// Test 5: Kelly applied in backtest (simulated portfolio PnL)
///
/// **EXPECTED**: Kelly sizing reduces drawdowns while maintaining returns
/// - Half Kelly (0.5): Lower volatility, smaller positions
/// - Full Kelly (1.0): Higher volatility, larger positions
#[test]
fn test_kelly_applied_in_backtest() {
    let initial_capital = 10_000.0;
    let price_sequence = vec![100.0, 105.0, 103.0, 108.0, 112.0]; // +12% total move

    // Scenario 1: Half Kelly (conservative)
    let mut tracker_half = PortfolioTracker::new(initial_capital, 0.0001, 0.0);
    let max_position = 50.0; // Max 50 contracts

    for &price in &price_sequence {
        let long_action = FactoredAction::new(
            ExposureLevel::Long100,
            OrderType::Market,
            Urgency::Normal,
        );
        tracker_half.execute_action_with_kelly(long_action, price, max_position, 0.5);
    }

    let final_value_half = tracker_half.total_value(*price_sequence.last().unwrap());
    let pnl_half = final_value_half - initial_capital;

    // Scenario 2: Full Kelly (aggressive)
    let mut tracker_full = PortfolioTracker::new(initial_capital, 0.0001, 0.0);

    for &price in &price_sequence {
        let long_action = FactoredAction::new(
            ExposureLevel::Long100,
            OrderType::Market,
            Urgency::Normal,
        );
        tracker_full.execute_action_with_kelly(long_action, price, max_position, 1.0);
    }

    let final_value_full = tracker_full.total_value(*price_sequence.last().unwrap());
    let pnl_full = final_value_full - initial_capital;

    // Verify both are profitable (upward price movement)
    assert!(
        pnl_half > 0.0,
        "Half Kelly should be profitable, got PnL: {}",
        pnl_half
    );
    assert!(
        pnl_full > 0.0,
        "Full Kelly should be profitable, got PnL: {}",
        pnl_full
    );

    // Verify Full Kelly has larger PnL (higher leverage)
    assert!(
        pnl_full > pnl_half * 1.3,
        "Full Kelly PnL ({}) should be significantly larger than Half Kelly ({})",
        pnl_full,
        pnl_half
    );
}

/// Test 6: Regime features in state tensor (state shape validation)
///
/// **EXPECTED**: State tensor shape = [batch, 128 + regime_dim]
/// - Without regime: [batch, 128] = 4 price + 121 technical + 3 portfolio
/// - With regime: [batch, 133] = 128 + 5 regime features
#[test]
fn test_regime_features_in_state_tensor() {
    // State without regime features
    let state_no_regime = create_test_state(100_000.0, 0.0);

    // Calculate total feature count (price + technical + portfolio)
    let features_no_regime = state_no_regime.price_features.len()
        + state_no_regime.technical_indicators.len()
        + state_no_regime.portfolio_features.len();

    assert_eq!(
        features_no_regime, 128,
        "Base state should have 128 features (4 + 121 + 3), got {}",
        features_no_regime
    );

    // State with regime features
    let regime_features = vec![1.0, 0.8, 3.5, 0.5, 28.0]; // 5 regime features
    let state_with_regime = create_test_state_with_regime(100_000.0, 0.0, regime_features);

    // Calculate total feature count including regime
    let features_with_regime = state_with_regime.price_features.len()
        + state_with_regime.technical_indicators.len()
        + state_with_regime.portfolio_features.len()
        + state_with_regime.regime_features.len();

    assert_eq!(
        features_with_regime, 133,
        "State with regime should have 133 features (128 + 5), got {}",
        features_with_regime
    );

    // Verify regime features are correctly appended
    assert_eq!(
        state_with_regime.regime_features.len(),
        5,
        "Regime features should have 5 elements"
    );
}
