#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::indexing_slicing,
    clippy::needless_borrow,
    clippy::out_of_bounds_indexing
)]
//! TDD Tests for 225-Feature Extraction Integration in ML Training Service Orchestrator
//!
//! These tests verify that the orchestrator uses the FeatureExtractor from the ml crate
//! to produce all 225 features (Wave C 201 + Wave D 24) instead of the legacy ~11-feature loader.
//!
//! **TDD Approach**: These tests are written FIRST and should initially FAIL.
//! After implementing `load_training_data_with_225_features()`, they should PASS.

use anyhow::Result;
use chrono::Utc;

// Import from ml crate - the CORRECT 225-feature extractor
use ml::features::extraction::{FeatureExtractor, OHLCVBar};

#[test]
fn test_feature_extractor_produces_225_features() -> Result<()> {
    // Arrange: Create synthetic OHLCV bars (need 100+ for warmup + validation)
    let bars: Vec<OHLCVBar> = (0..100)
        .map(|i| OHLCVBar {
            timestamp: Utc::now() + chrono::Duration::hours(i),
            open: 4500.0 + i as f64 * 0.5,      // ES.FUT typical price range
            high: 4502.0 + i as f64 * 0.5,
            low: 4498.0 + i as f64 * 0.5,
            close: 4500.5 + i as f64 * 0.5,
            volume: 10000.0 + i as f64 * 100.0,
        })
        .collect();

    // Act: Extract features using FeatureExtractor
    let mut extractor = FeatureExtractor::new();
    let mut feature_vectors = Vec::new();

    for (i, bar) in bars.iter().enumerate() {
        extractor.update(bar)?;

        // Start extracting after 50-bar warmup period
        if i >= 50 {
            let features = extractor.extract_current_features()?;
            feature_vectors.push(features);
        }
    }

    // Assert: Verify 225 features per vector
    assert_eq!(
        feature_vectors.len(),
        50,
        "Should have 50 feature vectors after warmup (100 bars - 50 warmup)"
    );

    assert_eq!(
        feature_vectors[0].len(),
        225,
        "Each feature vector must contain exactly 225 features"
    );

    // Verify all features are finite (no NaN, no Inf)
    for (idx, features) in feature_vectors.iter().enumerate() {
        for (feat_idx, &val) in features.iter().enumerate() {
            assert!(
                val.is_finite(),
                "Feature {} in vector {} must be finite, got: {}",
                feat_idx,
                idx,
                val
            );
        }
    }

    // Verify key feature ranges (smoke test)
    // Feature 0-4: Static metadata (should be constant across bars)
    // Feature 5-14: Known future (calendar features)
    // Feature 15-200: Wave C (OHLCV, indicators, microstructure)
    // Feature 201-224: Wave D (regime detection)

    // All features should have been populated (not all zeros)
    let all_zeros = feature_vectors[0].iter().all(|&x| x == 0.0);
    assert!(
        !all_zeros,
        "Feature vector should not be all zeros - features must be populated"
    );

    Ok(())
}

#[test]
#[ignore = "Ignore until implementation is complete"]
fn test_orchestrator_uses_225_features() -> Result<()> {
    // This test will verify the orchestrator integration once implemented
    // For now, it's marked as ignored because the implementation doesn't exist yet

    // TODO: After implementing load_training_data_with_225_features(), this test should:
    // 1. Call orchestrator's load_training_data() with a test DBN file
    // 2. Verify training data contains 225-feature vectors
    // 3. Verify validation data contains 225-feature vectors
    // 4. Verify train/val split is correct (80/20)

    Ok(())
}

#[test]
fn test_feature_extractor_warmup_period() -> Result<()> {
    // Verify that feature extraction requires a warmup period

    // Create exactly 50 bars (minimum warmup)
    let bars: Vec<OHLCVBar> = (0..50)
        .map(|i| OHLCVBar {
            timestamp: Utc::now() + chrono::Duration::hours(i),
            open: 4500.0 + i as f64,
            high: 4502.0 + i as f64,
            low: 4498.0 + i as f64,
            close: 4500.5 + i as f64,
            volume: 10000.0 + i as f64 * 100.0,
        })
        .collect();

    let mut extractor = FeatureExtractor::new();

    // Feed all 50 bars
    for bar in &bars {
        extractor.update(bar)?;
    }

    // Should be able to extract features after 50 bars
    let features = extractor.extract_current_features()?;
    assert_eq!(features.len(), 225);

    Ok(())
}

#[test]
fn test_feature_extractor_insufficient_warmup() {
    // Verify that feature extraction fails with insufficient warmup

    // Create only 30 bars (less than 50 warmup requirement)
    let bars: Vec<OHLCVBar> = (0..30)
        .map(|i| OHLCVBar {
            timestamp: Utc::now() + chrono::Duration::hours(i),
            open: 4500.0 + i as f64,
            high: 4502.0 + i as f64,
            low: 4498.0 + i as f64,
            close: 4500.5 + i as f64,
            volume: 10000.0 + i as f64 * 100.0,
        })
        .collect();

    let mut extractor = FeatureExtractor::new();

    // Feed only 30 bars
    for bar in &bars {
        let _ = extractor.update(&bar);
    }

    // Attempting to extract features should either:
    // 1. Return an error (if FeatureExtractor enforces warmup)
    // 2. Return features with some zeroed/default values (if it doesn't enforce)
    //
    // We'll test for error case, but if implementation allows extraction,
    // we should at least verify the features are finite
    let result = extractor.extract_current_features();

    match result {
        Ok(features) => {
            // If extraction succeeds, verify features are at least valid
            assert_eq!(features.len(), 225);
            for &val in &features {
                assert!(val.is_finite(), "Features should be finite even without full warmup");
            }
        }
        Err(_) => {
            // Expected behavior: extraction should fail without sufficient warmup
            // This is the preferred behavior for production safety
        }
    }
}

#[test]
fn test_feature_vector_dimension_consistency() -> Result<()> {
    // Verify that all feature vectors have consistent dimensions across multiple bars

    let bars: Vec<OHLCVBar> = (0..200)
        .map(|i| OHLCVBar {
            timestamp: Utc::now() + chrono::Duration::hours(i),
            open: 4500.0 + (i as f64 * 0.1).sin() * 10.0,  // Add some variation
            high: 4502.0 + (i as f64 * 0.1).sin() * 10.0,
            low: 4498.0 + (i as f64 * 0.1).sin() * 10.0,
            close: 4500.5 + (i as f64 * 0.1).sin() * 10.0,
            volume: 10000.0 + (i as f64 * 0.1).cos() * 1000.0,
        })
        .collect();

    let mut extractor = FeatureExtractor::new();
    let mut feature_vectors = Vec::new();

    for (i, bar) in bars.iter().enumerate() {
        extractor.update(bar)?;
        if i >= 50 {
            let features = extractor.extract_current_features()?;
            feature_vectors.push(features);
        }
    }

    // Verify all vectors have exactly 225 dimensions
    for (idx, features) in feature_vectors.iter().enumerate() {
        assert_eq!(
            features.len(),
            225,
            "Feature vector {} should have exactly 225 dimensions",
            idx
        );
    }

    // Verify we got the expected number of vectors
    assert_eq!(
        feature_vectors.len(),
        150, // 200 bars - 50 warmup
        "Should extract 150 feature vectors from 200 bars after warmup"
    );

    Ok(())
}

#[test]
fn test_wave_c_features_present() -> Result<()> {
    // Verify Wave C features (indices 0-200) are populated

    let bars: Vec<OHLCVBar> = (0..100)
        .map(|i| OHLCVBar {
            timestamp: Utc::now() + chrono::Duration::hours(i),
            open: 4500.0 + i as f64 * 0.5,
            high: 4502.0 + i as f64 * 0.5,
            low: 4498.0 + i as f64 * 0.5,
            close: 4500.5 + i as f64 * 0.5,
            volume: 10000.0 + i as f64 * 100.0,
        })
        .collect();

    let mut extractor = FeatureExtractor::new();
    for bar in &bars {
        extractor.update(bar)?;
    }

    let features = extractor.extract_current_features()?;

    // Wave C features: indices 0-200 (201 total)
    let wave_c_features = &features[0..=200];

    // Check that Wave C features are not all zeros
    let all_zeros = wave_c_features.iter().all(|&x| x == 0.0);
    assert!(
        !all_zeros,
        "Wave C features (0-200) should not be all zeros"
    );

    // Check that all Wave C features are finite
    for (idx, &val) in wave_c_features.iter().enumerate() {
        assert!(
            val.is_finite(),
            "Wave C feature {} should be finite, got: {}",
            idx,
            val
        );
    }

    Ok(())
}

#[test]
fn test_wave_d_features_present() -> Result<()> {
    // Verify Wave D regime detection features (indices 201-224) are populated

    let bars: Vec<OHLCVBar> = (0..100)
        .map(|i| OHLCVBar {
            timestamp: Utc::now() + chrono::Duration::hours(i),
            open: 4500.0 + i as f64 * 0.5,
            high: 4502.0 + i as f64 * 0.5,
            low: 4498.0 + i as f64 * 0.5,
            close: 4500.5 + i as f64 * 0.5,
            volume: 10000.0 + i as f64 * 100.0,
        })
        .collect();

    let mut extractor = FeatureExtractor::new();
    for bar in &bars {
        extractor.update(bar)?;
    }

    let features = extractor.extract_current_features()?;

    // Wave D features: indices 201-224 (24 total)
    // 201-210: CUSUM regime detection (10 features)
    // 211-215: ADX directional indicators (5 features)
    // 216-220: Transition probabilities (5 features)
    // 221-224: Adaptive metrics (4 features)
    let wave_d_features = &features[201..=224];

    assert_eq!(
        wave_d_features.len(),
        24,
        "Wave D should have exactly 24 features"
    );

    // Check that all Wave D features are finite
    for (idx, &val) in wave_d_features.iter().enumerate() {
        assert!(
            val.is_finite(),
            "Wave D feature {} (global index {}) should be finite, got: {}",
            idx,
            201 + idx,
            val
        );
    }

    Ok(())
}
