//! MBP-10 to TLOB Feature Extraction
//!
//! Maps Market By Price (10 levels) order book snapshots to 51 TLOB features
//! for transformer-based limit order book prediction.

use crate::tlob::features::{FeatureVector, TLOBFeatureExtractor, TLOBFeatures};
use ml_core::MLError;
use anyhow::Result;
use data::providers::databento::mbp10::Mbp10Snapshot;
use tracing::{debug, instrument};

/// Extract TLOB features from MBP-10 snapshot
///
/// Maps 10-level order book data to 51 features:
/// - Price levels (10 features)
/// - Volume levels (10 features)
/// - Order counts (10 features)
/// - Microstructure features (11 features)
/// - Technical indicators (10 features)
///
/// # Arguments
/// * `snapshot` - MBP-10 order book snapshot
///
/// # Returns
/// `TLOBFeatures` structure ready for extraction
#[instrument(skip(snapshot))]
pub fn extract_features_from_mbp10(snapshot: &Mbp10Snapshot) -> Result<TLOBFeatures, MLError> {
    // Extract price levels (bid/ask prices for 5 levels)
    let mut bid_levels = Vec::with_capacity(5);
    let mut ask_levels = Vec::with_capacity(5);

    for i in 0..5.min(snapshot.levels.len()) {
        bid_levels.push(snapshot.levels[i].bid_px);
        ask_levels.push(snapshot.levels[i].ask_px);
    }

    // Pad with zeros if we have fewer than 5 levels
    while bid_levels.len() < 5 {
        bid_levels.push(0);
        ask_levels.push(0);
    }

    // Extract volume levels
    let mut bid_volumes = Vec::with_capacity(5);
    let mut ask_volumes = Vec::with_capacity(5);

    for i in 0..5.min(snapshot.levels.len()) {
        bid_volumes.push(snapshot.levels[i].bid_sz as i64);
        ask_volumes.push(snapshot.levels[i].ask_sz as i64);
    }

    while bid_volumes.len() < 5 {
        bid_volumes.push(0);
        ask_volumes.push(0);
    }

    // Calculate microstructure features
    let spread = snapshot.spread();
    let mid_price = snapshot.mid_price();
    let volume_imbalance = snapshot.volume_imbalance();
    let vwap = snapshot.calculate_vwap();
    let weighted_mid = snapshot.weighted_mid_price();

    // Calculate book pressure (volume at each level)
    let total_bid_vol = snapshot.total_bid_volume() as f64;
    let total_ask_vol = snapshot.total_ask_volume() as f64;
    let book_pressure = (total_bid_vol - total_ask_vol) / (total_bid_vol + total_ask_vol + 1e-8);

    // Calculate order imbalance
    let bid_order_count: u32 = snapshot.levels.iter().map(|l| l.bid_ct).sum();
    let ask_order_count: u32 = snapshot.levels.iter().map(|l| l.ask_ct).sum();
    let order_imbalance = (bid_order_count as f64 - ask_order_count as f64)
        / (bid_order_count as f64 + ask_order_count as f64 + 1e-8);

    // Calculate depth features
    let depth = snapshot.depth() as f64;
    let spread_bps = (spread / mid_price * 10000.0).max(0.0).min(1000.0); // Basis points

    // Calculate price impact (simplified)
    let price_impact = if total_bid_vol + total_ask_vol > 0.0 {
        (spread * (total_bid_vol + total_ask_vol)) / 1000.0
    } else {
        0.0
    };

    // Build microstructure features vector
    let microstructure_features = vec![
        spread,
        volume_imbalance,
        book_pressure,
        order_imbalance,
        vwap - mid_price,         // VWAP deviation
        weighted_mid - mid_price, // Weighted mid deviation
        depth,
        spread_bps,
        price_impact,
        (bid_order_count as f64).ln(), // Log order counts
        (ask_order_count as f64).ln(),
    ];

    // Create TLOBFeatures structure
    TLOBFeatures::new(
        snapshot.timestamp,
        snapshot.symbol.clone(),
        bid_levels,
        ask_levels,
        bid_volumes,
        ask_volumes,
        (mid_price * 1e9) as i64, // Convert back to fixed-point
        total_bid_vol as i64 + total_ask_vol as i64,
        spread / mid_price, // Relative volatility estimate
        volume_imbalance,   // Momentum proxy
        microstructure_features,
    )
}

/// Extract TLOB feature vector from MBP-10 snapshot
///
/// Convenience function that combines extraction and feature vector generation
///
/// # Arguments
/// * `snapshot` - MBP-10 order book snapshot
/// * `extractor` - TLOB feature extractor
///
/// # Returns
/// 51-dimensional feature vector ready for model input
pub fn extract_feature_vector_from_mbp10(
    snapshot: &Mbp10Snapshot,
    extractor: &TLOBFeatureExtractor,
) -> Result<FeatureVector, MLError> {
    let features = extract_features_from_mbp10(snapshot)?;
    extractor.extract(&features)
}

/// Batch extract features from multiple snapshots
///
/// Optimized for processing large sequences of order book snapshots
///
/// # Arguments
/// * `snapshots` - Vector of MBP-10 snapshots
/// * `extractor` - TLOB feature extractor
///
/// # Returns
/// Vector of 51-dimensional feature vectors
pub fn batch_extract_features(
    snapshots: &[Mbp10Snapshot],
    extractor: &TLOBFeatureExtractor,
) -> Result<Vec<FeatureVector>, MLError> {
    let mut feature_vectors = Vec::with_capacity(snapshots.len());

    for (idx, snapshot) in snapshots.iter().enumerate() {
        let features = extract_features_from_mbp10(snapshot)?;
        let feature_vector = extractor.extract(&features)?;
        feature_vectors.push(feature_vector);

        // Log progress every 1000 snapshots
        if (idx + 1) % 1000 == 0 {
            debug!(
                "Extracted {} / {} feature vectors",
                idx + 1,
                snapshots.len()
            );
        }
    }

    Ok(feature_vectors)
}

#[cfg(test)]
#[allow(clippy::manual_range_contains)]
mod tests {
    use super::*;
    use data::providers::databento::mbp10::BidAskPair;

    fn create_test_snapshot() -> Mbp10Snapshot {
        let levels = vec![
            BidAskPair {
                bid_px: 150000000000000, // 150.0
                bid_sz: 100,
                bid_ct: 5,
                ask_px: 150010000000000, // 150.01
                ask_sz: 120,
                ask_ct: 6,
            },
            BidAskPair {
                bid_px: 149990000000000, // 149.99
                bid_sz: 200,
                bid_ct: 8,
                ask_px: 150020000000000, // 150.02
                ask_sz: 180,
                ask_ct: 7,
            },
        ];

        Mbp10Snapshot::new("ES.FUT".to_owned(), 1640995200000000000, levels, 0, 100)
    }

    #[test]
    fn test_extract_features_from_mbp10() {
        let snapshot = create_test_snapshot();
        let result = extract_features_from_mbp10(&snapshot);

        assert!(result.is_ok());

        let features = result.unwrap();
        assert_eq!(features.symbol, "ES.FUT");
        assert_eq!(features.bid_levels.len(), 5); // Padded to 5
        assert_eq!(features.ask_levels.len(), 5);
        assert_eq!(features.bid_volumes.len(), 5);
        assert_eq!(features.ask_volumes.len(), 5);

        // Verify non-zero first levels
        assert!(features.bid_levels[0] > 0);
        assert!(features.ask_levels[0] > features.bid_levels[0]);
    }

    #[test]
    fn test_microstructure_features() {
        let snapshot = create_test_snapshot();
        let features = extract_features_from_mbp10(&snapshot).unwrap();

        // Should have 11 microstructure features
        assert!(features.microstructure_features.len() >= 11);

        // Verify features are reasonable
        for &feature in &features.microstructure_features {
            assert!(feature.is_finite(), "Feature should be finite");
        }
    }

    #[test]
    fn test_extract_feature_vector() {
        let snapshot = create_test_snapshot();
        let extractor = TLOBFeatureExtractor::new().unwrap();

        let result = extract_feature_vector_from_mbp10(&snapshot, &extractor);
        assert!(result.is_ok());

        let feature_vector = result.unwrap();
        assert_eq!(feature_vector.values.len(), 51); // 51 TLOB features
        assert_eq!(feature_vector.feature_names.len(), 51);

        // Verify all features are normalized to [-1, 1]
        for &value in &feature_vector.values {
            assert!(
                value >= -1.0 && value <= 1.0,
                "Feature value {} out of range [-1, 1]",
                value
            );
        }
    }

    #[test]
    fn test_batch_extract_features() {
        let snapshot1 = create_test_snapshot();
        let snapshot2 = create_test_snapshot();
        let snapshots = vec![snapshot1, snapshot2];

        let extractor = TLOBFeatureExtractor::new().unwrap();
        let result = batch_extract_features(&snapshots, &extractor);

        assert!(result.is_ok());

        let feature_vectors = result.unwrap();
        assert_eq!(feature_vectors.len(), 2);

        for fv in feature_vectors {
            assert_eq!(fv.values.len(), 51);
        }
    }
}
