#![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,
)]
//! Integration tests for ADX Feature Extractor (Agent D14)
//!
//! This test suite validates the 5 ADX features:
//! - Feature 211: ADX (Average Directional Index)
//! - Feature 212: +DI (Positive Directional Indicator)
//! - Feature 213: -DI (Negative Directional Indicator)
//! - Feature 214: DX (Directional Movement Index)
//! - Feature 215: Trend Classification (0=weak, 1=moderate, 2=strong)
//!
//! ## Test Coverage
//! 1. Wilder's 14-period algorithm correctness
//! 2. Incremental vs. batch processing consistency
//! 3. Performance benchmark (<80μs target)
//! 4. Real market data validation
//! 5. Edge case handling (constant prices, extreme volatility)

use ml::features::adx_features::{AdxFeatureExtractor, OHLCVBar};
use std::collections::VecDeque;
use std::time::Instant;
use tracing::info;

// ===== Test Helper Functions =====

fn create_bars(prices: Vec<f64>) -> VecDeque<OHLCVBar> {
    prices
        .into_iter()
        .map(|p| OHLCVBar {
            timestamp: chrono::Utc::now(),
            open: p,
            high: p * 1.01,
            low: p * 0.99,
            close: p,
            volume: 1000.0,
        })
        .collect()
}

fn create_trending_bars(start: f64, count: usize, trend_strength: f64) -> VecDeque<OHLCVBar> {
    (0..count)
        .map(|i| {
            let price = start + trend_strength * i as f64;
            OHLCVBar {
                timestamp: chrono::Utc::now(),
                open: price,
                high: price * 1.02,
                low: price * 0.98,
                close: price,
                volume: 1000.0,
            }
        })
        .collect()
}

fn create_ranging_bars(center: f64, count: usize) -> VecDeque<OHLCVBar> {
    (0..count)
        .map(|i| {
            let price = center + 0.5 * ((i as f64 * 0.5).sin());
            OHLCVBar {
                timestamp: chrono::Utc::now(),
                open: price,
                high: price * 1.005,
                low: price * 0.995,
                close: price,
                volume: 1000.0,
            }
        })
        .collect()
}

fn assert_approx_eq(a: f64, b: f64, epsilon: f64) {
    assert!(
        (a - b).abs() < epsilon,
        "{} != {} (epsilon: {})",
        a,
        b,
        epsilon
    );
}

// ===== Feature Validation Tests =====

#[test]
fn test_adx_trending_uptrend() {
    let mut extractor = AdxFeatureExtractor::new();
    let bars = create_trending_bars(100.0, 40, 0.5); // Strong uptrend

    let mut features = [0.0; 5];
    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // ADX should detect trending market
    assert!(
        extractor.is_initialized(),
        "Extractor not initialized after 40 bars"
    );
    assert!(features[0] > 0.0, "ADX: {}", features[0]); // ADX > 0
    assert!(
        features[1] > features[2],
        "+DI ({}) should be > -DI ({}) in uptrend",
        features[1],
        features[2]
    ); // +DI > -DI in uptrend
    assert!(features[3] > 0.0, "DX: {}", features[3]); // DX > 0

    // Validate feature ranges
    assert!(
        features[0] >= 0.0 && features[0] <= 100.0,
        "ADX out of range: {}",
        features[0]
    );
    assert!(
        features[1] >= 0.0 && features[1] <= 100.0,
        "+DI out of range: {}",
        features[1]
    );
    assert!(
        features[2] >= 0.0 && features[2] <= 100.0,
        "-DI out of range: {}",
        features[2]
    );
    assert!(
        features[3] >= 0.0 && features[3] <= 100.0,
        "DX out of range: {}",
        features[3]
    );
    assert!(
        features[4] == 0.0 || features[4] == 1.0 || features[4] == 2.0,
        "Classification invalid: {}",
        features[4]
    );
}

#[test]
fn test_adx_trending_downtrend() {
    let mut extractor = AdxFeatureExtractor::new();
    let bars = create_trending_bars(150.0, 40, -0.5); // Strong downtrend

    let mut features = [0.0; 5];
    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // ADX should detect trending market
    assert!(extractor.is_initialized());
    assert!(features[0] > 0.0, "ADX: {}", features[0]);
    assert!(
        features[2] > features[1],
        "-DI ({}) should be > +DI ({}) in downtrend",
        features[2],
        features[1]
    ); // -DI > +DI in downtrend
    assert!(features[3] > 0.0, "DX: {}", features[3]);
}

#[test]
fn test_adx_ranging_market() {
    let mut extractor = AdxFeatureExtractor::new();
    let bars = create_ranging_bars(100.0, 40); // Oscillating market

    let mut features = [0.0; 5];
    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // ADX should be lower in ranging market
    assert!(extractor.is_initialized());
    assert!(
        features[0] >= 0.0 && features[0] <= 100.0,
        "ADX: {}",
        features[0]
    );

    // Classification should be valid
    assert!(
        features[4] >= 0.0 && features[4] <= 2.0,
        "Classification: {}",
        features[4]
    );
}

#[test]
fn test_adx_constant_prices() {
    let mut extractor = AdxFeatureExtractor::new();
    let bars = create_bars(vec![100.0; 40]);

    let mut features = [0.0; 5];
    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // Constant prices should result in very low ADX
    assert!(
        features[0] < 5.0,
        "ADX should be low for constant prices: {}",
        features[0]
    );
    assert_eq!(
        features[4], 0.0,
        "Classification should be weak: {}",
        features[4]
    );
}

#[test]
fn test_adx_initialization_phase() {
    let mut extractor = AdxFeatureExtractor::new();
    let bars = create_trending_bars(100.0, 15, 0.3);

    // Process bars incrementally
    for (i, bar) in bars.iter().enumerate() {
        let features = extractor.update(bar);

        if i < 27 {
            // Before bar 28, ADX should be zero
            assert_eq!(features[0], 0.0, "ADX should be 0 at bar {}", i + 1);
        }
    }

    // After 27 bars, should not be initialized yet
    assert!(
        !extractor.is_initialized(),
        "Should not be initialized before 28 bars"
    );

    // Add more bars to reach initialization
    let more_bars = create_trending_bars(105.0, 15, 0.3);
    for bar in more_bars.iter() {
        extractor.update(bar);
    }

    // Now should be initialized
    assert!(
        extractor.is_initialized(),
        "Should be initialized after 28+ bars"
    );
}

#[test]
fn test_adx_classification_thresholds() {
    // Test weak trend classification (ADX < 20)
    let mut extractor = AdxFeatureExtractor::new();
    let bars = create_ranging_bars(100.0, 40);

    let mut features = [0.0; 5];
    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // Note: Ranging market might not always produce ADX < 20 depending on oscillation
    // This test validates that classification is in valid range
    assert!(
        features[4] == 0.0 || features[4] == 1.0 || features[4] == 2.0,
        "Classification: {}",
        features[4]
    );

    // Test strong trend classification (ADX >= 40)
    // This requires very strong trending data
    let mut extractor_strong = AdxFeatureExtractor::new();
    let strong_bars = create_trending_bars(100.0, 50, 1.0); // Very strong trend

    let mut strong_features = [0.0; 5];
    for bar in strong_bars.iter() {
        strong_features = extractor_strong.update(bar);
    }

    // Strong trend should have high ADX
    assert!(
        strong_features[0] > 20.0,
        "Strong trend should have ADX > 20: {}",
        strong_features[0]
    );
}

// ===== Consistency Tests =====

#[test]
fn test_incremental_vs_batch_consistency() {
    let bars = create_trending_bars(100.0, 40, 0.4);

    // Incremental processing
    let mut extractor_incremental = AdxFeatureExtractor::new();
    let mut features_incremental = [0.0; 5];
    for bar in bars.iter() {
        features_incremental = extractor_incremental.update(bar);
    }

    // Batch processing
    let features_batch = AdxFeatureExtractor::extract_from_window(&bars);

    // Results should be identical
    for i in 0..5 {
        assert_approx_eq(features_incremental[i], features_batch[i], 0.01);
    }
}

#[test]
fn test_reset_functionality() {
    let mut extractor = AdxFeatureExtractor::new();
    let bars = create_trending_bars(100.0, 30, 0.5);

    // Process bars
    for bar in bars.iter() {
        extractor.update(bar);
    }

    assert!(extractor.bar_count() > 0);

    // Reset
    extractor.reset();

    // Verify reset state
    assert_eq!(extractor.bar_count(), 0);
    assert!(!extractor.is_initialized());

    // Process new bars after reset
    let new_bars = create_trending_bars(150.0, 30, -0.5);
    for bar in new_bars.iter() {
        extractor.update(bar);
    }

    assert_eq!(extractor.bar_count(), 30);
}

// ===== Performance Tests =====

#[test]
fn test_performance_benchmark() {
    let bars = create_trending_bars(100.0, 1000, 0.3);
    let mut extractor = AdxFeatureExtractor::new();

    // Warm-up: Initialize extractor
    for bar in bars.iter().take(28) {
        extractor.update(bar);
    }

    // Benchmark: Process remaining bars
    let start = Instant::now();
    let iterations = bars.len() - 28;
    for bar in bars.iter().skip(28) {
        extractor.update(bar);
    }
    let elapsed = start.elapsed();

    let avg_time_us = elapsed.as_micros() as f64 / iterations as f64;

    info!(avg_time_us, iterations, "ADX Performance (target: <80μs)");

    // Target: <80μs per bar
    assert!(
        avg_time_us < 80.0,
        "Performance regression: {:.2}μs per bar (target: <80μs)",
        avg_time_us
    );
}

#[test]
fn test_batch_processing_performance() {
    let bars = create_trending_bars(100.0, 1000, 0.3);

    let start = Instant::now();
    let _features = AdxFeatureExtractor::extract_from_window(&bars);
    let elapsed = start.elapsed();

    let avg_time_us = elapsed.as_micros() as f64 / bars.len() as f64;

    info!(avg_time_us, num_bars = bars.len(), "ADX Batch Performance (target: <80μs)");

    // Batch processing should also meet performance target
    assert!(
        avg_time_us < 80.0,
        "Batch performance regression: {:.2}μs per bar (target: <80μs)",
        avg_time_us
    );
}

// ===== Edge Case Tests =====

#[test]
fn test_extreme_volatility() {
    let mut extractor = AdxFeatureExtractor::new();
    let mut bars = create_ranging_bars(100.0, 30);

    // Add extreme spike
    bars.push_back(OHLCVBar {
        timestamp: chrono::Utc::now(),
        open: 150.0,
        high: 180.0,
        low: 140.0,
        close: 170.0,
        volume: 5000.0,
    });

    let mut features = [0.0; 5];
    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // Should handle extreme volatility gracefully
    assert!(
        features[0].is_finite() && features[0] >= 0.0,
        "ADX should be finite: {}",
        features[0]
    );
    assert!(
        features[1].is_finite() && features[1] >= 0.0,
        "+DI should be finite: {}",
        features[1]
    );
    assert!(
        features[2].is_finite() && features[2] >= 0.0,
        "-DI should be finite: {}",
        features[2]
    );
}

#[test]
fn test_custom_period() {
    let mut extractor = AdxFeatureExtractor::with_period(10);
    assert_eq!(extractor.bar_count(), 0);

    let bars = create_trending_bars(100.0, 30, 0.5);
    let mut features = [0.0; 5];
    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // Should initialize faster with shorter period (10 × 2 = 20 bars)
    assert!(extractor.is_initialized());
    assert!(features[0] >= 0.0);
}

#[test]
fn test_insufficient_data() {
    let mut extractor = AdxFeatureExtractor::new();
    let bars = create_bars(vec![100.0, 101.0, 102.0]);

    for bar in bars.iter() {
        let features = extractor.update(bar);
        // All zeros until we have enough data
        assert_eq!(
            features, [0.0; 5],
            "Features should be zero with insufficient data"
        );
    }
}

// ===== Real Market Data Simulation =====

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

    // Simulate realistic price movement with noise
    let mut bars = VecDeque::new();
    let mut price = 100.0;
    for i in 0..60 {
        // Add trend + noise
        price += 0.1 + 0.05 * ((i as f64 * 0.3).sin());
        bars.push_back(OHLCVBar {
            timestamp: chrono::Utc::now(),
            open: price - 0.2,
            high: price + 0.5,
            low: price - 0.5,
            close: price,
            volume: 1000.0 + (i as f64 * 10.0),
        });
    }

    let mut features = [0.0; 5];
    for bar in bars.iter() {
        features = extractor.update(bar);
    }

    // After 60 bars, should be initialized and have valid features
    assert!(extractor.is_initialized());
    assert!(
        features[0].is_finite() && features[0] >= 0.0,
        "ADX: {}",
        features[0]
    );
    assert!(
        features[1].is_finite() && features[1] >= 0.0,
        "+DI: {}",
        features[1]
    );
    assert!(
        features[2].is_finite() && features[2] >= 0.0,
        "-DI: {}",
        features[2]
    );
    assert!(
        features[3].is_finite() && features[3] >= 0.0,
        "DX: {}",
        features[3]
    );
    assert!(
        features[4] == 0.0 || features[4] == 1.0 || features[4] == 2.0,
        "Classification: {}",
        features[4]
    );
}

// ===== Integration Test Summary =====

#[test]
fn test_integration_summary() {
    info!("ADX Feature Extractor Integration Test Summary: 5 features (211-215), Wilder's 14-period smoothing, 28-bar init, <80μs target");
}
