//! Unified Feature Extraction for Training/Inference Consistency
//!
//! This module provides a unified interface for feature extraction that ensures
//! consistency between training and serving. It bridges the gap between:
//! - `extract_ml_features()`: 256-dimension feature vectors from OHLCV bars
//! - `UnifiedFinancialFeatures`: Comprehensive feature structure for ML models
//!
//! ## Architecture
//! ```rust
//! use ml::features::unified::{UnifiedFeatureExtractor, UnifiedFinancialFeatures};
//! use ml::safety::MLSafetyManager;
//! use std::sync::Arc;
//!
//! let config = FeatureExtractionConfig::default();
//! let safety_manager = Arc::new(MLSafetyManager::new(Default::default()));
//! let extractor = UnifiedFeatureExtractor::new(config, safety_manager);
//!
//! // Extract features from market data
//! let features = extractor.extract_features(
//!     symbol,
//!     &market_data,
//!     &trades,
//!     order_book
//! ).await?;
//! ```

use std::collections::HashMap;
use std::sync::Arc;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tracing::{debug, warn};

use crate::features::extraction::OHLCVBar;
use crate::safety::{MLSafetyError, MLSafetyManager, SafetyResult};
use crate::{MarketDataSnapshot, Trade};
use common::types::{Price, Quantity, Symbol};

/// Feature extraction configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureExtractionConfig {
    /// Time windows for various calculations
    pub short_window: usize,
    pub medium_window: usize,
    pub long_window: usize,

    /// Minimum data requirements
    pub min_data_points: usize,
    pub max_missing_ratio: f64,

    /// Normalization parameters
    pub enable_normalization: bool,
    pub normalization_method: String,
    pub outlier_threshold: f64,

    /// Feature selection
    pub enable_feature_selection: bool,
    pub max_features: Option<usize>,
    pub correlation_threshold: f64,

    /// Safety parameters
    pub max_computation_time_ms: u64,
    pub enable_validation: bool,
    pub validation_strict: bool,
}

impl Default for FeatureExtractionConfig {
    fn default() -> Self {
        Self {
            short_window: 20,
            medium_window: 50,
            long_window: 200,
            min_data_points: 10,
            max_missing_ratio: 0.1,
            enable_normalization: true,
            normalization_method: "z-score".to_owned(),
            outlier_threshold: 3.0,
            enable_feature_selection: true,
            max_features: Some(100),
            correlation_threshold: 0.95,
            max_computation_time_ms: 1000,
            enable_validation: true,
            validation_strict: true,
        }
    }
}

/// Order book level representing a price-quantity pair
pub type OrderBookLevel = (Price, Quantity);

/// Unified financial features structure (42-dimension wrapper)
///
/// This structure wraps the 42-dimension feature vector extracted by
/// `extract_ml_features()` and provides metadata for training/inference.
#[derive(Debug, Clone)]
pub struct UnifiedFinancialFeatures {
    /// Symbol identifier
    pub symbol: Symbol,
    /// Feature timestamp
    pub timestamp: DateTime<Utc>,
    /// 42-dimension feature vector (40 base market + 2 regime: ADX, CUSUM)
    pub features: [f64; 42],
    /// Feature quality metrics
    pub quality_metrics: FeatureQualityMetrics,
}

/// Feature quality metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureQualityMetrics {
    /// Data completeness (0.0 to 1.0)
    pub completeness_ratio: f64,
    /// Data freshness (seconds since last update)
    pub data_age_seconds: i64,
    /// Feature stability score
    pub stability_score: f64,
    /// Outlier detection flags
    pub outlier_flags: HashMap<String, bool>,
    /// Missing data indicators
    pub missing_data_features: Vec<String>,
}

// Custom serialization for large arrays (serde doesn't support arrays > 32 by default)
impl Serialize for UnifiedFinancialFeatures {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeStruct;
        let mut state = serializer.serialize_struct("UnifiedFinancialFeatures", 4)?;
        state.serialize_field("symbol", &self.symbol)?;
        state.serialize_field("timestamp", &self.timestamp)?;
        state.serialize_field("features", &self.features.to_vec())?; // cpu-side array→vec for serde
        state.serialize_field("quality_metrics", &self.quality_metrics)?;
        state.end()
    }
}

// Custom deserialization for large arrays
impl<'de> Deserialize<'de> for UnifiedFinancialFeatures {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper {
            symbol: Symbol,
            timestamp: DateTime<Utc>,
            features: Vec<f64>,
            quality_metrics: FeatureQualityMetrics,
        }
        let helper = Helper::deserialize(deserializer)?;
        let features: [f64; 42] = helper.features.try_into().map_err(|v: Vec<f64>| {
            serde::de::Error::custom(format!(
                "features array must have exactly 42 elements, got {}",
                v.len()
            ))
        })?;
        Ok(UnifiedFinancialFeatures {
            symbol: helper.symbol,
            timestamp: helper.timestamp,
            features,
            quality_metrics: helper.quality_metrics,
        })
    }
}

impl Default for FeatureQualityMetrics {
    fn default() -> Self {
        Self {
            completeness_ratio: 1.0,
            data_age_seconds: 0,
            stability_score: 1.0,
            outlier_flags: HashMap::new(),
            missing_data_features: Vec::new(),
        }
    }
}

/// Unified feature extractor
///
/// Provides a consistent interface for feature extraction across training and serving.
/// Uses `extract_ml_features()` internally for 256-dimension feature vectors.
#[derive(Debug)]
pub struct UnifiedFeatureExtractor {
    config: FeatureExtractionConfig,
    safety_manager: Arc<MLSafetyManager>,
}

impl UnifiedFeatureExtractor {
    /// Create new unified feature extractor
    pub fn new(config: FeatureExtractionConfig, safety_manager: Arc<MLSafetyManager>) -> Self {
        Self {
            config,
            safety_manager,
        }
    }

    /// Extract comprehensive features from market data
    ///
    /// This method converts market data snapshots to OHLCV bars and extracts
    /// 256-dimension feature vectors using `extract_ml_features()`.
    ///
    /// ## Arguments
    /// - `symbol`: Symbol identifier
    /// - `market_data`: Market data snapshots (price, volume, timestamp)
    /// - `trades`: Trade history (currently unused, reserved for future microstructure features)
    /// - `order_book`: Order book levels (currently unused, reserved for future features)
    ///
    /// ## Returns
    /// - `UnifiedFinancialFeatures`: 256-dimension feature vector with metadata
    pub async fn extract_features(
        &self,
        symbol: Symbol,
        market_data: &[MarketDataSnapshot],
        _trades: &[Trade],
        _order_book: Option<&[OrderBookLevel]>,
    ) -> SafetyResult<UnifiedFinancialFeatures> {
        let extraction_start = std::time::Instant::now();

        // Validate input data
        self.validate_input_data(market_data)?;

        // Convert market data snapshots to OHLCV bars
        let bars = self.convert_to_ohlcv_bars(market_data)?;

        // Extract 256-dimension features using the production feature extraction function
        let feature_vectors =
            crate::features::extraction::extract_ml_features(&bars).map_err(|e| {
                MLSafetyError::ValidationError {
                    message: format!("Feature extraction failed: {}", e),
                }
            })?;

        // Take the most recent feature vector (last bar after warmup)
        let features =
            feature_vectors
                .last()
                .copied()
                .ok_or_else(|| MLSafetyError::ValidationError {
                    message: "No features extracted (insufficient data after warmup)".to_owned(),
                })?;

        // Calculate quality metrics
        let quality_metrics = self.calculate_quality_metrics(market_data);

        // Validate extracted features
        let unified_features = UnifiedFinancialFeatures {
            symbol: symbol.clone(),
            timestamp: Utc::now(),
            features,
            quality_metrics,
        };

        if self.config.enable_validation {
            self.validate_features(&unified_features)?;
        }

        // Safety check: Ensure extraction time is within bounds
        let elapsed_ms = extraction_start.elapsed().as_millis() as u64;
        if elapsed_ms > self.config.max_computation_time_ms {
            warn!(
                "Feature extraction took {}ms (limit: {}ms)",
                elapsed_ms, self.config.max_computation_time_ms
            );
        }

        debug!("Extracted 256 features for {} in {}ms", symbol, elapsed_ms);

        Ok(unified_features)
    }

    /// Extract financial features (alias for extract_features)
    ///
    /// Provides a consistent interface for backward compatibility with existing code.
    pub async fn extract_financial_features(
        &self,
        symbol: Symbol,
        market_data: &[MarketDataSnapshot],
        trades: &[Trade],
        order_book: Option<&[OrderBookLevel]>,
    ) -> SafetyResult<UnifiedFinancialFeatures> {
        self.extract_features(symbol, market_data, trades, order_book)
            .await
    }

    /// Validate input data
    fn validate_input_data(&self, market_data: &[MarketDataSnapshot]) -> SafetyResult<()> {
        if market_data.is_empty() {
            return Err(MLSafetyError::ValidationError {
                message: "Cannot extract features from empty market data".to_owned(),
            });
        }

        if market_data.len() < self.config.min_data_points {
            return Err(MLSafetyError::ValidationError {
                message: format!(
                    "Insufficient data: {} points provided, {} required",
                    market_data.len(),
                    self.config.min_data_points
                ),
            });
        }

        Ok(())
    }

    /// Convert market data snapshots to OHLCV bars
    ///
    /// For real-time data, we aggregate snapshots into bars (e.g., 1-minute bars).
    /// For now, we treat each snapshot as a single bar with open=close=price.
    fn convert_to_ohlcv_bars(
        &self,
        market_data: &[MarketDataSnapshot],
    ) -> SafetyResult<Vec<OHLCVBar>> {
        use rust_decimal::prelude::ToPrimitive;

        let bars = market_data
            .iter()
            .map(|snapshot| {
                let price_f64 = snapshot.price.to_f64().unwrap_or(0.0);
                let volume_f64 = snapshot.volume.to_f64().unwrap_or(0.0);
                OHLCVBar {
                    timestamp: snapshot.timestamp,
                    open: price_f64,
                    high: price_f64,
                    low: price_f64,
                    close: price_f64,
                    volume: volume_f64,
                }
            })
            .collect();

        Ok(bars)
    }

    /// Calculate feature quality metrics
    fn calculate_quality_metrics(
        &self,
        market_data: &[MarketDataSnapshot],
    ) -> FeatureQualityMetrics {
        let data_age_seconds = if let Some(latest) = market_data.last() {
            (Utc::now() - latest.timestamp).num_seconds()
        } else {
            0
        };

        let completeness_ratio = 1.0; // All data points are complete
        let stability_score = 1.0; // Default to stable

        FeatureQualityMetrics {
            completeness_ratio,
            data_age_seconds,
            stability_score,
            outlier_flags: HashMap::new(),
            missing_data_features: Vec::new(),
        }
    }

    /// Validate extracted features
    fn validate_features(&self, features: &UnifiedFinancialFeatures) -> SafetyResult<()> {
        // Check for NaN/Inf values
        for (i, &val) in features.features.iter().enumerate() {
            if !val.is_finite() {
                return Err(MLSafetyError::ValidationError {
                    message: format!("Invalid feature at index {}: {}", i, val),
                });
            }
        }

        // Check quality metrics
        if features.quality_metrics.completeness_ratio < (1.0 - self.config.max_missing_ratio) {
            return Err(MLSafetyError::ValidationError {
                message: format!(
                    "Data completeness too low: {} (min: {})",
                    features.quality_metrics.completeness_ratio,
                    1.0 - self.config.max_missing_ratio
                ),
            });
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::safety::MLSafetyConfig;

    fn create_test_market_data(count: usize) -> Vec<MarketDataSnapshot> {
        use rust_decimal::Decimal;
        (0..count)
            .map(|i| MarketDataSnapshot {
                symbol: "TEST".to_owned(),
                price: Decimal::from_f64_retain(100.0 + i as f64).unwrap(),
                volume: Decimal::from_f64_retain(1000.0 + i as f64 * 10.0).unwrap(),
                timestamp: Utc::now() + chrono::Duration::hours(i as i64),
            })
            .collect()
    }

    #[tokio::test]
    async fn test_unified_feature_extractor_creation() {
        let config = FeatureExtractionConfig::default();
        let safety_manager = Arc::new(MLSafetyManager::new(MLSafetyConfig::default()));
        let extractor = UnifiedFeatureExtractor::new(config, safety_manager);

        assert_eq!(extractor.config.short_window, 20);
        assert_eq!(extractor.config.medium_window, 50);
    }

    #[tokio::test]
    async fn test_feature_extraction_success() {
        let config = FeatureExtractionConfig {
            min_data_points: 50,
            ..Default::default()
        };
        let safety_manager = Arc::new(MLSafetyManager::new(MLSafetyConfig::default()));
        let extractor = UnifiedFeatureExtractor::new(config, safety_manager);

        let market_data = create_test_market_data(100);
        let symbol = Symbol::from("TEST");
        let trades = vec![];
        let order_book = None;

        let features = extractor
            .extract_features(symbol.clone(), &market_data, &trades, order_book)
            .await
            .unwrap();

        assert_eq!(features.symbol, symbol);
        assert_eq!(features.features.len(), 42);

        // Validate all features are finite
        for &val in features.features.iter() {
            assert!(val.is_finite(), "Found non-finite value: {}", val);
        }
    }

    #[tokio::test]
    async fn test_feature_extraction_insufficient_data() {
        let config = FeatureExtractionConfig {
            min_data_points: 50,
            ..Default::default()
        };
        let safety_manager = Arc::new(MLSafetyManager::new(MLSafetyConfig::default()));
        let extractor = UnifiedFeatureExtractor::new(config, safety_manager);

        let market_data = create_test_market_data(10); // Too few
        let symbol = Symbol::from("TEST");
        let trades = vec![];
        let order_book = None;

        let result = extractor
            .extract_features(symbol, &market_data, &trades, order_book)
            .await;

        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("Insufficient data"));
    }

    #[tokio::test]
    async fn test_feature_extraction_empty_data() {
        let config = FeatureExtractionConfig::default();
        let safety_manager = Arc::new(MLSafetyManager::new(MLSafetyConfig::default()));
        let extractor = UnifiedFeatureExtractor::new(config, safety_manager);

        let market_data = vec![];
        let symbol = Symbol::from("TEST");
        let trades = vec![];
        let order_book = None;

        let result = extractor
            .extract_features(symbol, &market_data, &trades, order_book)
            .await;

        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("empty market data"));
    }

    #[tokio::test]
    async fn test_extract_financial_features_alias() {
        let config = FeatureExtractionConfig {
            min_data_points: 50,
            ..Default::default()
        };
        let safety_manager = Arc::new(MLSafetyManager::new(MLSafetyConfig::default()));
        let extractor = UnifiedFeatureExtractor::new(config, safety_manager);

        let market_data = create_test_market_data(100);
        let symbol = Symbol::from("TEST");
        let trades = vec![];
        let order_book = None;

        let features = extractor
            .extract_financial_features(symbol.clone(), &market_data, &trades, order_book)
            .await
            .unwrap();

        assert_eq!(features.symbol, symbol);
        assert_eq!(features.features.len(), 42);
    }

    #[test]
    fn test_feature_extraction_config_default() {
        let config = FeatureExtractionConfig::default();

        assert_eq!(config.short_window, 20);
        assert_eq!(config.medium_window, 50);
        assert_eq!(config.long_window, 200);
        assert_eq!(config.min_data_points, 10);
        assert!(config.enable_normalization);
        assert!(config.enable_validation);
    }

    #[test]
    fn test_feature_quality_metrics_default() {
        let metrics = FeatureQualityMetrics::default();

        assert_eq!(metrics.completeness_ratio, 1.0);
        assert_eq!(metrics.data_age_seconds, 0);
        assert_eq!(metrics.stability_score, 1.0);
        assert!(metrics.outlier_flags.is_empty());
        assert!(metrics.missing_data_features.is_empty());
    }
}
