//! Feature Extraction for DQN Training
//!
//! Methods for extracting 140-dimensional features (125 market + 3 portfolio
//! + 12 microstructure), calculating feature statistics, normalizing datasets,
//! and creating synthetic features for testing.

use anyhow::Result;


use crate::features::extraction::OHLCVBar;
use crate::training_pipeline::FinancialFeatures;

use super::statistics::FeatureStatistics;
use crate::features::extraction::FeatureVector;
use super::trainer::DQNTrainer;

impl DQNTrainer {
    /// Create synthetic features (placeholder for testing)
    pub(crate) fn create_synthetic_features(&self, price: f64) -> Result<FinancialFeatures> {
        use std::collections::HashMap;

        let price_obj =
            common::Price::from_f64(price).or_else(|_| common::Price::new(price))?;

        let mut indicators = HashMap::new();
        indicators.insert("rsi_14".to_owned(), 50.0);
        indicators.insert("sma_20".to_owned(), price);
        indicators.insert("ema_12".to_owned(), price);

        Ok(FinancialFeatures {
            prices: vec![price_obj; 4],
            volumes: vec![1000],
            technical_indicators: indicators,
            microstructure: crate::training_pipeline::MicrostructureFeatures {
                spread_bps: 10,
                imbalance: 0.0,
                trade_intensity: 100.0,
                vwap: price_obj,
            },
            risk_metrics: crate::training_pipeline::RiskFeatures {
                var_5pct: -0.02,
                expected_shortfall: -0.03,
                max_drawdown: -0.05,
                sharpe_ratio: 1.0,
            },
            timestamp: chrono::Utc::now(),
        })
    }

    /// WAVE 3.10: Extract 140 features (125 market + 3 portfolio + 12 microstructure)
    ///
    /// This method extracts 140 features for DQN state representation:
    /// - 125 market features (price, technical indicators, volatility, etc.)
    /// - 3 portfolio features (populated later via PortfolioTracker)
    /// - 12 microstructure features (spread estimators, liquidity, order flow, market impact)
    ///
    /// The microstructure features are calculated on-the-fly from OHLCV data using
    /// the calculators initialized in DQNTrainer::new().
    ///
    /// # Arguments
    ///
    /// * `bars` - OHLCV bars for feature extraction
    /// * `mbp10_snapshots` - Optional MBP-10 order book snapshots for OFI calculation
    pub(crate) fn extract_full_features(
        &mut self,
        bars: &[OHLCVBar],
        _mbp10_snapshots: Option<&[data::providers::databento::mbp10::Mbp10Snapshot]>,
    ) -> Result<Vec<FeatureVector>> {
        use crate::features::extraction::FeatureExtractor;

        if bars.is_empty() {
            anyhow::bail!("Cannot extract features from empty bar sequence");
        }

        const WARMUP_PERIOD: usize = 50;
        if bars.len() < WARMUP_PERIOD {
            anyhow::bail!(
                "Insufficient data: {} bars provided, {} required for warmup",
                bars.len(),
                WARMUP_PERIOD
            );
        }

        let mut extractor = FeatureExtractor::new();
        let mut feature_vectors = Vec::with_capacity(bars.len() - WARMUP_PERIOD);

        // Feed bars sequentially to build rolling windows
        for (i, bar) in bars.iter().enumerate() {
            extractor.update(bar)?;

            // Update microstructure calculators
            let timestamp_ns = bar.timestamp.timestamp_nanos_opt().unwrap_or(0) as u64;

            let hl_spread = self.micro_high_low_spread.update(bar.high, bar.low);
            let _vw_spread = self.micro_vw_spread.update(hl_spread, bar.volume);
            let _tick_count = self.micro_tick_count.update(bar.close);
            let _inter_arrival = self.micro_inter_arrival.update(timestamp_ns);
            let _buy_sell_imb = self.micro_buy_sell_imbalance.update(bar.close, bar.volume);

            let return_pct = if self.last_close > 0.0 {
                (bar.close - self.last_close) / self.last_close
            } else {
                0.0
            };
            let signed_volume = (bar.close - bar.open).signum() * (bar.close * bar.volume).sqrt();
            let _kyle_lambda = self.micro_kyle_lambda.maybe_update(timestamp_ns, return_pct, signed_volume);

            let _price_impact = self.micro_price_impact.update(bar.high, bar.low, bar.close);
            let _variance_ratio = self.micro_variance_ratio.update(return_pct);

            self.last_close = bar.close;

            // Start extracting features after warmup
            if i >= WARMUP_PERIOD {
                // Extract 42 market features (40 base + 2 regime: ADX, CUSUM)
                let features_40 = extractor.extract_current_features()?;
                feature_vectors.push(features_40);
            }
        }

        Ok(feature_vectors)
    }

    /// Calculate feature statistics from training samples using Welford's algorithm
    pub(crate) fn calculate_feature_statistics(
        &self,
        samples: &[(FeatureVector, Vec<f64>)],
    ) -> Result<FeatureStatistics> {
        let mut stats = FeatureStatistics::new(43);

        for (feature_vec, _) in samples {
            let features: Vec<f32> = feature_vec.iter().map(|&v| v as f32).collect();
            stats.update(&features);
        }

        Ok(stats)
    }

    /// Normalize all samples in a dataset using z-score normalization
    pub(crate) fn normalize_dataset(
        &mut self,
        samples: &mut [(FeatureVector, Vec<f64>)],
    ) -> Result<()> {
        if let Some(ref stats) = self.feature_stats {
            for (feature_vec, _) in samples.iter_mut() {
                // Convert to f32 for normalization
                let features_f32: Vec<f32> = feature_vec.iter().map(|&v| v as f32).collect();

                // Normalize all features (no skip indices)
                let normalized = stats.normalize_with_skip(&features_f32, &[]);

                // Convert back to f64 and update
                for (i, &val) in normalized.iter().enumerate() {
                    feature_vec[i] = val as f64;
                }
            }
        } else {
            return Err(anyhow::anyhow!("Feature statistics not initialized"));
        }

        Ok(())
    }
}
