#![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 Edge Case Tests for Wave D Feature Extractors
//!
//! This test suite validates the robustness of all Wave D feature extractors against:
//! - Missing data (gaps, zero volume, insufficient bars)
//! - Invalid inputs (NaN, Inf, division by zero)
//! - Extreme values (circuit breakers, volume spikes, zero volatility)
//! - Initialization edge cases (cold start, insufficient data)
//! - Graceful degradation (error recovery, defensive programming)
//!
//! ## Test Coverage (Wave D Feature Extractors)
//!
//! 1. **CUSUM Features (Indices 201-210)** - RegimeCUSUMFeatures
//!    - Missing data: NaN inputs, Inf returns
//!    - Division by zero: Zero threshold, zero std
//!    - Cold start: <2 bars
//!    - Extreme values: 100x jumps
//!
//! 2. **ADX Features (Indices 211-215)** - AdxFeatureExtractor
//!    - Missing data: Gap in bars, zero volume
//!    - Invalid OHLC: H<L, C outside range
//!    - Cold start: <28 bars (ADX initialization)
//!    - Zero volatility: 100+ bars flat prices
//!
//! 3. **Transition Features (Indices 216-220)** - RegimeTransitionFeatures
//!    - Rapid regime cycling (10 changes in 10 bars)
//!    - Single regime persistence (100 bars)
//!    - Invalid regime transitions
//!
//! 4. **Adaptive Features (Indices 221-224)** - RegimeAdaptiveFeatures
//!    - Zero position size, max position size
//!    - Zero max position (division by zero)
//!    - Zero ATR (flat prices)
//!    - Extreme returns (±100%)
//!
//! ## Success Criteria
//!
//! - ✅ All edge cases handled gracefully (no panics)
//! - ✅ NaN/Inf inputs produce valid outputs (0.0 or clamped values)
//! - ✅ Comprehensive error logging
//! - ✅ 100% test coverage for error paths
//!
//! ## Test Organization
//!
//! Tests are grouped by feature extractor and edge case category:
//! - `test_cusum_*`: CUSUM edge cases
//! - `test_adx_*`: ADX edge cases
//! - `test_transition_*`: Transition matrix edge cases
//! - `test_adaptive_*`: Adaptive strategy edge cases
//! - `test_integration_*`: Cross-module edge cases

use chrono::Utc;
use ml::ensemble::MarketRegime;
use ml::features::adx_features::AdxFeatureExtractor;
use ml::features::adx_features::OHLCVBar as AdxOHLCVBar;
use ml::features::extraction::OHLCVBar;
use ml::features::regime_adaptive::RegimeAdaptiveFeatures;
use ml::features::regime_cusum::RegimeCUSUMFeatures;
use ml::features::regime_transition::RegimeTransitionFeatures;

// ============================================================================
// Test Helpers
// ============================================================================

/// Create OHLCV bar with specified parameters (for extraction/adaptive features)
fn create_bar(price: f64, high_mult: f64, low_mult: f64, volume: f64) -> OHLCVBar {
    OHLCVBar {
        timestamp: Utc::now(),
        open: price,
        high: price * high_mult,
        low: price * low_mult,
        close: price,
        volume,
    }
}

/// Create ADX OHLCV bar (for ADX-specific tests)
fn create_adx_bar(price: f64, high_mult: f64, low_mult: f64, volume: f64) -> AdxOHLCVBar {
    AdxOHLCVBar {
        timestamp: Utc::now(),
        open: price,
        high: price * high_mult,
        low: price * low_mult,
        close: price,
        volume,
    }
}

/// Create bars with a gap (missing bar represented by NaN) - ADX version
fn create_adx_bars_with_gap(prices: Vec<Option<f64>>) -> Vec<AdxOHLCVBar> {
    prices
        .into_iter()
        .filter_map(|p| p.map(|price| create_adx_bar(price, 1.01, 0.99, 1000.0)))
        .collect()
}

/// Create invalid OHLC bar (high < low) - ADX version
fn create_invalid_adx_bar(price: f64) -> AdxOHLCVBar {
    AdxOHLCVBar {
        timestamp: Utc::now(),
        open: price,
        high: price * 0.99, // Invalid: high < low
        low: price * 1.01,
        close: price,
        volume: 1000.0,
    }
}

/// Create bar with close outside OHLC range - ADX version
fn create_adx_bar_with_invalid_close(price: f64) -> AdxOHLCVBar {
    AdxOHLCVBar {
        timestamp: Utc::now(),
        open: price,
        high: price * 1.02,
        low: price * 0.98,
        close: price * 1.05, // Invalid: close > high
        volume: 1000.0,
    }
}

/// Create bars with zero volatility - ADX version
fn create_flat_adx_bars(count: usize, price: f64) -> Vec<AdxOHLCVBar> {
    (0..count)
        .map(|_| AdxOHLCVBar {
            timestamp: Utc::now(),
            open: price,
            high: price,
            low: price,
            close: price,
            volume: 1000.0,
        })
        .collect()
}

/// Create bars with zero volatility - extraction version
fn create_flat_bars(count: usize, price: f64) -> Vec<OHLCVBar> {
    (0..count)
        .map(|_| OHLCVBar {
            timestamp: Utc::now(),
            open: price,
            high: price,
            low: price,
            close: price,
            volume: 1000.0,
        })
        .collect()
}

// ============================================================================
// 1. CUSUM Features Edge Cases (Indices 201-210)
// ============================================================================

#[test]
fn test_cusum_nan_input() {
    let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

    // Feed NaN input
    let result = features.update(f64::NAN);

    // Verify: All features should be valid (no NaN propagation)
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with NaN input, got {}",
            201 + i,
            feature
        );
    }

    // Verify: Break indicator should be 0.0 (no false detection)
    assert_eq!(
        result[2], 0.0,
        "NaN input should not trigger break detection"
    );
}

#[test]
fn test_cusum_inf_input() {
    let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

    // Feed infinity input
    let result = features.update(f64::INFINITY);

    // Verify: All features should be finite
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with Inf input, got {}",
            201 + i,
            feature
        );
    }
}

#[test]
fn test_cusum_negative_inf_input() {
    let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

    // Feed negative infinity input
    let result = features.update(f64::NEG_INFINITY);

    // Verify: All features should be finite
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with -Inf input, got {}",
            201 + i,
            feature
        );
    }
}

#[test]
fn test_cusum_zero_threshold() {
    // Edge case: Zero threshold (division by zero)
    let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 0.0);

    let result = features.update(1.0);

    // Verify: Features should handle zero threshold gracefully
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with zero threshold, got {}",
            201 + i,
            feature
        );
    }
}

#[test]
fn test_cusum_zero_std() {
    // Edge case: Zero standard deviation
    let mut features = RegimeCUSUMFeatures::new(0.0, 0.0, 0.5, 4.0);

    let result = features.update(1.0);

    // Verify: Features should handle zero std gracefully
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with zero std, got {}",
            201 + i,
            feature
        );
    }
}

#[test]
fn test_cusum_extreme_positive_value() {
    let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

    // Feed extreme value (100x jump)
    let result = features.update(100.0);

    // Verify: Features should be clamped to valid ranges
    // Feature 201: S+ Normalized should be clamped to [0.0, 1.5]
    assert!(
        result[0] >= 0.0 && result[0] <= 1.5,
        "S+ normalized should be in [0, 1.5], got {}",
        result[0]
    );

    // Feature 202: S- Normalized should be clamped to [0.0, 1.5]
    assert!(
        result[1] >= 0.0 && result[1] <= 1.5,
        "S- normalized should be in [0, 1.5], got {}",
        result[1]
    );

    // All features should be finite
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with extreme value, got {}",
            201 + i,
            feature
        );
    }
}

#[test]
fn test_cusum_extreme_negative_value() {
    let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

    // Feed extreme negative value
    let result = features.update(-100.0);

    // Verify: All features are finite and in valid ranges
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with extreme negative value, got {}",
            201 + i,
            feature
        );
    }
}

#[test]
fn test_cusum_rapid_oscillation() {
    let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 3.0);

    // Rapid oscillation: +10, -10, +10, -10
    for i in 0..100 {
        let value = if i % 2 == 0 { 10.0 } else { -10.0 };
        let result = features.update(value);

        // Verify: All features remain finite despite rapid oscillation
        for (j, &feature) in result.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "Feature {} should be finite during oscillation, got {}",
                201 + j,
                feature
            );
        }
    }
}

#[test]
fn test_cusum_cold_start_insufficient_data() {
    let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

    // Feed only 1 observation
    let result = features.update(0.5);

    // Verify: All features should be valid (no panic on cold start)
    assert_eq!(result.len(), 10);
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite on cold start, got {}",
            201 + i,
            feature
        );
    }
}

// ============================================================================
// 2. ADX Features Edge Cases (Indices 211-215)
// ============================================================================

#[test]
fn test_adx_nan_in_close_price() {
    let mut extractor = AdxFeatureExtractor::new();

    // Feed bar with NaN close price
    let bar = AdxOHLCVBar {
        timestamp: Utc::now(),
        open: 100.0,
        high: 102.0,
        low: 98.0,
        close: f64::NAN,
        volume: 1000.0,
    };

    let features = extractor.update(&bar);

    // Verify: All features should be finite (NaN handled gracefully)
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with NaN close, got {}",
            211 + i,
            feature
        );
    }
}

#[test]
fn test_adx_inf_in_volume() {
    let mut extractor = AdxFeatureExtractor::new();

    // Feed bar with infinite volume
    let bar = AdxOHLCVBar {
        timestamp: Utc::now(),
        open: 100.0,
        high: 102.0,
        low: 98.0,
        close: 101.0,
        volume: f64::INFINITY,
    };

    let features = extractor.update(&bar);

    // Verify: All features should be finite
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with Inf volume, got {}",
            211 + i,
            feature
        );
    }
}

#[test]
fn test_adx_zero_volume_bar() {
    let mut extractor = AdxFeatureExtractor::new();

    // Initialize with 30 normal bars
    for i in 0..30 {
        let bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0);
        extractor.update(&bar);
    }

    // Feed bar with zero volume
    let bar = create_adx_bar(130.0, 1.02, 0.98, 0.0);
    let features = extractor.update(&bar);

    // Verify: Zero volume should not crash, features should be finite
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with zero volume, got {}",
            211 + i,
            feature
        );
    }
}

#[test]
fn test_adx_invalid_ohlc_high_less_than_low() {
    let mut extractor = AdxFeatureExtractor::new();

    // Feed invalid bar (high < low)
    for _ in 0..30 {
        let bar = create_invalid_adx_bar(100.0);
        let features = extractor.update(&bar);

        // Verify: All features should be finite despite invalid OHLC
        for (i, &feature) in features.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "Feature {} should be finite with invalid OHLC, got {}",
                211 + i,
                feature
            );
        }
    }
}

#[test]
fn test_adx_close_outside_ohlc_range() {
    let mut extractor = AdxFeatureExtractor::new();

    // Feed bars with close outside [low, high]
    for _ in 0..30 {
        let bar = create_adx_bar_with_invalid_close(100.0);
        let features = extractor.update(&bar);

        // Verify: Features should be finite
        for (i, &feature) in features.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "Feature {} should be finite with close outside range, got {}",
                211 + i,
                feature
            );
        }
    }
}

#[test]
fn test_adx_cold_start_less_than_14_bars() {
    let mut extractor = AdxFeatureExtractor::new();

    // Feed only 5 bars (insufficient for ADX initialization)
    for i in 0..5 {
        let bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0);
        let features = extractor.update(&bar);

        // Verify: Features should be zeros until initialization
        assert_eq!(
            features, [0.0; 5],
            "ADX should return zeros before initialization"
        );
    }
}

#[test]
fn test_adx_zero_volatility_100_bars() {
    let mut extractor = AdxFeatureExtractor::new();

    // Feed 100 bars with zero volatility (flat prices)
    let bars = create_flat_adx_bars(100, 100.0);
    let mut features = [0.0; 5];

    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // Verify: ADX should be very low (<5) with zero volatility
    assert!(
        extractor.is_initialized(),
        "ADX should be initialized after 100 bars"
    );
    assert!(
        features[0] < 5.0,
        "ADX should be <5 with zero volatility, got {}",
        features[0]
    );

    // Verify: All features are finite
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with zero volatility, got {}",
            211 + i,
            feature
        );
    }
}

#[test]
fn test_adx_price_jump_50_percent() {
    let mut extractor = AdxFeatureExtractor::new();

    // Initialize with normal bars
    for i in 0..20 {
        let bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0);
        extractor.update(&bar);
    }

    // Circuit breaker event: 50% price jump
    let bar = create_adx_bar(180.0, 1.02, 0.98, 5000.0);
    let features = extractor.update(&bar);

    // Verify: Features should remain finite (extreme values handled)
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite after 50% jump, got {}",
            211 + i,
            feature
        );
    }

    // Verify: ADX should remain in [0, 100] range
    assert!(
        features[0] >= 0.0 && features[0] <= 100.0,
        "ADX should be in [0, 100], got {}",
        features[0]
    );
}

#[test]
fn test_adx_volume_spike_1000x() {
    let mut extractor = AdxFeatureExtractor::new();

    // Initialize with normal bars
    for i in 0..20 {
        let bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0);
        extractor.update(&bar);
    }

    // Volume spike: 1000x normal
    let bar = create_adx_bar(120.0, 1.02, 0.98, 1_000_000.0);
    let features = extractor.update(&bar);

    // Verify: Volume spike should not crash, features should be finite
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite after volume spike, got {}",
            211 + i,
            feature
        );
    }
}

#[test]
fn test_adx_gaps_in_data() {
    let mut extractor = AdxFeatureExtractor::new();

    // Create bars with gaps (None = missing bar)
    let prices = vec![
        Some(100.0),
        Some(101.0),
        None, // Gap
        Some(103.0),
        None, // Gap
        Some(105.0),
    ];

    let bars = create_adx_bars_with_gap(prices);

    // Feed bars with gaps
    for bar in bars.iter() {
        let features = extractor.update(bar);

        // Verify: Gaps should not cause issues
        for (i, &feature) in features.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "Feature {} should be finite with data gaps, got {}",
                211 + i,
                feature
            );
        }
    }
}

// ============================================================================
// 3. Transition Features Edge Cases (Indices 216-220)
// ============================================================================

#[test]
fn test_transition_rapid_regime_cycling() {
    let mut features = RegimeTransitionFeatures::new(4, 0.1);

    // Rapid cycling: 10 regime changes in 10 bars
    let regimes = vec![
        MarketRegime::Bull,
        MarketRegime::Bear,
        MarketRegime::Sideways,
        MarketRegime::HighVolatility,
        MarketRegime::Bull,
        MarketRegime::Bear,
        MarketRegime::Sideways,
        MarketRegime::HighVolatility,
        MarketRegime::Bull,
        MarketRegime::Bear,
    ];

    for regime in regimes {
        let result = features.update(regime);

        // Verify: Rapid cycling should not crash
        assert_eq!(result.len(), 5);
        for (i, &feature) in result.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "Feature {} should be finite during rapid cycling, got {}",
                216 + i,
                feature
            );
        }
    }
}

#[test]
fn test_transition_single_regime_persistence() {
    let mut features = RegimeTransitionFeatures::new(4, 0.1);

    // Single regime for 100 bars (no transitions)
    for _ in 0..100 {
        let result = features.update(MarketRegime::Bull);

        // Verify: Single regime persistence should be handled
        assert_eq!(result.len(), 5);
        for (i, &feature) in result.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "Feature {} should be finite with single regime, got {}",
                216 + i,
                feature
            );
        }
    }
}

#[test]
fn test_transition_cold_start() {
    let mut features = RegimeTransitionFeatures::new(4, 0.1);

    // First update (cold start)
    let result = features.update(MarketRegime::Sideways);

    // Verify: Cold start should not crash
    assert_eq!(result.len(), 5);
    for (i, &feature) in result.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite on cold start, got {}",
            216 + i,
            feature
        );
    }
}

// ============================================================================
// 4. Adaptive Features Edge Cases (Indices 221-224)
// ============================================================================

#[test]
fn test_adaptive_zero_position_size() {
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
    let bars: Vec<OHLCVBar> = (0..20)
        .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0))
        .collect();

    // Zero position size
    let features = adaptive.update(MarketRegime::Normal, 0.01, 0.0, &bars);

    // Verify: Zero position should be handled gracefully
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with zero position, got {}",
            221 + i,
            feature
        );
    }

    // Feature 224 (risk budget) should be 0.0
    assert_eq!(
        features[3], 0.0,
        "Risk budget should be 0.0 with zero position"
    );
}

#[test]
fn test_adaptive_zero_max_position() {
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 0.0, 14);
    let bars: Vec<OHLCVBar> = (0..20)
        .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0))
        .collect();

    // Zero max position (division by zero)
    let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);

    // Verify: Zero max position should be handled (no division by zero)
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with zero max position, got {}",
            221 + i,
            feature
        );
    }
}

#[test]
fn test_adaptive_zero_atr_flat_prices() {
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

    // Flat prices (zero ATR)
    let bars = create_flat_bars(20, 100.0);

    let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);

    // Verify: Zero ATR should be handled gracefully
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with zero ATR, got {}",
            221 + i,
            feature
        );
    }

    // Feature 222 (stop multiplier) should be close to zero (0 * multiplier)
    assert!(
        features[1].abs() < 0.1,
        "Stop multiplier should be near zero with flat prices, got {}",
        features[1]
    );
}

#[test]
fn test_adaptive_extreme_positive_return() {
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
    let bars: Vec<OHLCVBar> = (0..20)
        .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0))
        .collect();

    // Extreme positive return (+100%)
    let features = adaptive.update(MarketRegime::Normal, 1.0, 50_000.0, &bars);

    // Verify: Extreme return should not crash
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with +100% return, got {}",
            221 + i,
            feature
        );
    }
}

#[test]
fn test_adaptive_extreme_negative_return() {
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
    let bars: Vec<OHLCVBar> = (0..20)
        .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0))
        .collect();

    // Extreme negative return (-100%)
    let features = adaptive.update(MarketRegime::Crisis, -1.0, 50_000.0, &bars);

    // Verify: Extreme negative return should be handled
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with -100% return, got {}",
            221 + i,
            feature
        );
    }
}

#[test]
fn test_adaptive_nan_return() {
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
    let bars: Vec<OHLCVBar> = (0..20)
        .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0))
        .collect();

    // NaN return
    let features = adaptive.update(MarketRegime::Normal, f64::NAN, 50_000.0, &bars);

    // Verify: NaN return should be handled gracefully
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with NaN return, got {}",
            221 + i,
            feature
        );
    }
}

#[test]
fn test_adaptive_insufficient_bars_for_atr() {
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

    // Only 5 bars (insufficient for 14-period ATR)
    let bars: Vec<OHLCVBar> = (0..5)
        .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0))
        .collect();

    let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);

    // Verify: Insufficient bars should be handled
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with insufficient bars, got {}",
            221 + i,
            feature
        );
    }

    // Feature 222 (stop multiplier) should be 0.0 (no ATR available)
    assert_eq!(
        features[1], 0.0,
        "Stop multiplier should be 0.0 with insufficient bars for ATR"
    );
}

#[test]
fn test_adaptive_max_position_size_exceeded() {
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
    let bars: Vec<OHLCVBar> = (0..20)
        .map(|i| create_bar(100.0 + i as f64, 1.02, 0.98, 1000.0))
        .collect();

    // Position size exceeds max (150% of max)
    let features = adaptive.update(MarketRegime::Normal, 0.01, 150_000.0, &bars);

    // Verify: Over-leveraged position should be handled
    for (i, &feature) in features.iter().enumerate() {
        assert!(
            feature.is_finite(),
            "Feature {} should be finite with over-leverage, got {}",
            221 + i,
            feature
        );
    }

    // Feature 224 (risk budget) should be clamped to 1.0
    assert!(
        features[3] <= 1.0,
        "Risk budget should be clamped to 1.0, got {}",
        features[3]
    );
}

// ============================================================================
// 5. Integration Edge Cases (Cross-Module)
// ============================================================================

#[test]
fn test_integration_all_extractors_with_nan_inputs() {
    // Initialize all extractors
    let mut cusum = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);
    let mut adx = AdxFeatureExtractor::new();
    let mut transition = RegimeTransitionFeatures::new(4, 0.1);
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

    // Create ADX bar with NaN values
    let adx_bar = AdxOHLCVBar {
        timestamp: Utc::now(),
        open: f64::NAN,
        high: f64::NAN,
        low: f64::NAN,
        close: f64::NAN,
        volume: 1000.0,
    };

    // Create extraction bar with NaN values
    let bar = OHLCVBar {
        timestamp: Utc::now(),
        open: f64::NAN,
        high: f64::NAN,
        low: f64::NAN,
        close: f64::NAN,
        volume: 1000.0,
    };

    let bars = vec![bar.clone()];

    // Update all extractors with NaN inputs
    let cusum_features = cusum.update(f64::NAN);
    let adx_features = adx.update(&adx_bar);
    let transition_features = transition.update(MarketRegime::Unknown);
    let adaptive_features = adaptive.update(MarketRegime::Unknown, f64::NAN, 50_000.0, &bars);

    // Verify: All extractors handle NaN gracefully
    let all_features = [
        ("CUSUM", cusum_features.as_slice()),
        ("ADX", adx_features.as_slice()),
        ("Transition", transition_features.as_slice()),
        ("Adaptive", adaptive_features.as_slice()),
    ];

    for (extractor, features) in all_features.iter() {
        for (i, &feature) in features.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "{} feature {} should be finite with NaN inputs, got {}",
                extractor,
                i,
                feature
            );
        }
    }
}

#[test]
fn test_integration_all_extractors_with_extreme_values() {
    // Initialize all extractors
    let mut cusum = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);
    let mut adx = AdxFeatureExtractor::new();
    let mut transition = RegimeTransitionFeatures::new(4, 0.1);
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

    // Feed 30 bars to initialize ADX
    for i in 0..30 {
        let adx_bar = create_adx_bar(100.0 + i as f64, 1.02, 0.98, 1000.0);
        adx.update(&adx_bar);
        cusum.update(i as f64 * 0.01);
        transition.update(MarketRegime::Normal);
    }

    // Extreme event: 100x price jump, 1000x volume spike
    let extreme_adx_bar = create_adx_bar(10000.0, 1.5, 0.5, 1_000_000.0);
    let extreme_bar = create_bar(10000.0, 1.5, 0.5, 1_000_000.0);
    let bars = vec![extreme_bar];

    let cusum_features = cusum.update(100.0);
    let adx_features = adx.update(&extreme_adx_bar);
    let transition_features = transition.update(MarketRegime::Crisis);
    let adaptive_features = adaptive.update(MarketRegime::Crisis, 1.0, 100_000.0, &bars);

    // Verify: All extractors handle extreme values gracefully
    let all_features = [
        ("CUSUM", cusum_features.as_slice()),
        ("ADX", adx_features.as_slice()),
        ("Transition", transition_features.as_slice()),
        ("Adaptive", adaptive_features.as_slice()),
    ];

    for (extractor, features) in all_features.iter() {
        for (i, &feature) in features.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "{} feature {} should be finite with extreme values, got {}",
                extractor,
                i,
                feature
            );
        }
    }
}

#[test]
fn test_integration_cold_start_all_extractors() {
    // Initialize all extractors
    let mut cusum = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);
    let mut adx = AdxFeatureExtractor::new();
    let mut transition = RegimeTransitionFeatures::new(4, 0.1);
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

    // Cold start: first bar
    let adx_bar = create_adx_bar(100.0, 1.02, 0.98, 1000.0);
    let bar = create_bar(100.0, 1.02, 0.98, 1000.0);
    let bars = vec![bar];

    let cusum_features = cusum.update(0.01);
    let adx_features = adx.update(&adx_bar);
    let transition_features = transition.update(MarketRegime::Normal);
    let adaptive_features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);

    // Verify: All extractors handle cold start gracefully (no panic)
    let all_features = [
        ("CUSUM", cusum_features.as_slice()),
        ("ADX", adx_features.as_slice()),
        ("Transition", transition_features.as_slice()),
        ("Adaptive", adaptive_features.as_slice()),
    ];

    for (extractor, features) in all_features.iter() {
        for (i, &feature) in features.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "{} feature {} should be finite on cold start, got {}",
                extractor,
                i,
                feature
            );
        }
    }
}

#[test]
fn test_integration_zero_volatility_all_extractors() {
    // Initialize all extractors
    let mut cusum = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);
    let mut adx = AdxFeatureExtractor::new();
    let mut transition = RegimeTransitionFeatures::new(4, 0.1);
    let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

    // Zero volatility: 50 flat bars
    let adx_bars = create_flat_adx_bars(50, 100.0);
    let bars = create_flat_bars(50, 100.0);

    let mut features_snapshot = None;

    for (i, adx_bar) in adx_bars.iter().enumerate() {
        let cusum_features = cusum.update(0.0);
        let adx_features = adx.update(adx_bar);
        let transition_features = transition.update(MarketRegime::Sideways);
        let adaptive_features = adaptive.update(
            MarketRegime::Sideways,
            0.0,
            50_000.0,
            &bars[..20.min(i + 1)],
        );

        features_snapshot = Some((
            cusum_features,
            adx_features,
            transition_features,
            adaptive_features,
        ));
    }

    let (cusum_features, adx_features, transition_features, adaptive_features) =
        features_snapshot.unwrap();

    // Verify: All extractors handle zero volatility gracefully
    let all_features = [
        ("CUSUM", cusum_features.as_slice()),
        ("ADX", adx_features.as_slice()),
        ("Transition", transition_features.as_slice()),
        ("Adaptive", adaptive_features.as_slice()),
    ];

    for (extractor, features) in all_features.iter() {
        for (i, &feature) in features.iter().enumerate() {
            assert!(
                feature.is_finite(),
                "{} feature {} should be finite with zero volatility, got {}",
                extractor,
                i,
                feature
            );
        }
    }

    // Verify: ADX should be very low with zero volatility
    assert!(
        adx_features[0] < 5.0,
        "ADX should be <5 with zero volatility, got {}",
        adx_features[0]
    );
}
