//! Strategy-DQN Integration Bridge
//!
//! Bridges the strategy feature extraction system with DQN agents,
//! enabling unified ML-driven trading decisions from multiple strategy signals.

use chrono::{DateTime, Utc};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;

use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

use crate::dqn::agent::DQNAgent;
use crate::dqn::DQNConfig;
use crate::dqn::{Experience, TradingState};
use crate::MLError;

/// Strategy feature input for `DQN` bridge
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyFeatureInput {
    /// Raw features from strategy signals (8 features expected)
    pub features: [f32; 8],
    /// Market regime indicator (0=trending, 1=sideways, 2=volatile)
    pub regime: u8,
    /// Strategy confidence scores
    pub strategy_confidences: HashMap<String, f64>,
    /// Feature timestamp
    pub timestamp: DateTime<Utc>,
    /// Additional metadata
    pub metadata: FeatureMetadata,
}

/// Feature metadata for strategy inputs
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FeatureMetadata {
    /// Source strategy names
    pub source_strategies: Vec<String>,
    /// Feature quality score (0.0-1.0)
    pub quality_score: f64,
    /// Data freshness in milliseconds
    pub freshness_ms: u64,
    /// Number of missing features
    pub missing_features: usize,
}

/// Trading action types for `DQN` agent
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TradingActionType {
    /// Hold current position
    Hold = 0,
    /// Small buy order
    BuySmall = 1,
    /// Medium buy order
    BuyMedium = 2,
    /// Large buy order
    BuyLarge = 3,
    /// Small sell order
    SellSmall = 4,
    /// Medium sell order
    SellMedium = 5,
    /// Large sell order
    SellLarge = 6,
}

impl TradingActionType {
    /// Convert to `DQN` action index
    pub fn to_action_index(self) -> usize {
        self as usize
    }

    /// Convert from `DQN` action index
    pub fn from_action_index(index: usize) -> Option<Self> {
        match index {
            0 => Some(TradingActionType::Hold),
            1 => Some(TradingActionType::BuySmall),
            2 => Some(TradingActionType::BuyMedium),
            3 => Some(TradingActionType::BuyLarge),
            4 => Some(TradingActionType::SellSmall),
            5 => Some(TradingActionType::SellMedium),
            6 => Some(TradingActionType::SellLarge),
            _ => None,
        }
    }

    /// Get all possible actions
    pub fn all_actions() -> [TradingActionType; 7] {
        [
            TradingActionType::Hold,
            TradingActionType::BuySmall,
            TradingActionType::BuyMedium,
            TradingActionType::BuyLarge,
            TradingActionType::SellSmall,
            TradingActionType::SellMedium,
            TradingActionType::SellLarge,
        ]
    }
}

/// Action mapping configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionMapping {
    /// Available actions
    pub actions: Vec<TradingActionType>,
    /// Position size multipliers for each action
    pub position_multipliers: HashMap<TradingActionType, f64>,
    /// Risk limits per action type
    pub risk_limits: HashMap<TradingActionType, f64>,
}

impl Default for ActionMapping {
    fn default() -> Self {
        let mut position_multipliers = HashMap::new();
        position_multipliers.insert(TradingActionType::Hold, 0.0);
        position_multipliers.insert(TradingActionType::BuySmall, 0.25);
        position_multipliers.insert(TradingActionType::BuyMedium, 0.5);
        position_multipliers.insert(TradingActionType::BuyLarge, 1.0);
        position_multipliers.insert(TradingActionType::SellSmall, -0.25);
        position_multipliers.insert(TradingActionType::SellMedium, -0.5);
        position_multipliers.insert(TradingActionType::SellLarge, -1.0);

        let mut risk_limits = HashMap::new();
        risk_limits.insert(TradingActionType::Hold, 0.0);
        risk_limits.insert(TradingActionType::BuySmall, 0.02);
        risk_limits.insert(TradingActionType::BuyMedium, 0.05);
        risk_limits.insert(TradingActionType::BuyLarge, 0.10);
        risk_limits.insert(TradingActionType::SellSmall, 0.02);
        risk_limits.insert(TradingActionType::SellMedium, 0.05);
        risk_limits.insert(TradingActionType::SellLarge, 0.10);

        Self {
            actions: TradingActionType::all_actions().to_vec(), // cpu-side enum array
            position_multipliers,
            risk_limits,
        }
    }
}

/// Configuration for Strategy-`DQN` bridge
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyDQNConfig {
    /// Feature preprocessing configuration
    pub feature_config: FeaturePreprocessingConfig,
    /// Action mapping configuration
    pub action_mapping: ActionMapping,
    /// `DQN` agent configuration
    pub dqn_config: DQNConfig,
    /// Bridge-specific settings
    pub bridge_config: BridgeConfig,
}

impl Default for StrategyDQNConfig {
    fn default() -> Self {
        Self {
            feature_config: FeaturePreprocessingConfig::default(),
            action_mapping: ActionMapping::default(),
            dqn_config: DQNConfig::default(),
            bridge_config: BridgeConfig::default(),
        }
    }
}

/// Feature preprocessing configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeaturePreprocessingConfig {
    /// Enable feature normalization
    pub normalize_features: bool,
    /// Feature scaling method
    pub scaling_method: ScalingMethod,
    /// Rolling window size for statistics
    pub window_size: usize,
    /// Missing value handling
    pub handle_missing: MissingValueHandling,
}

impl Default for FeaturePreprocessingConfig {
    fn default() -> Self {
        Self {
            normalize_features: true,
            scaling_method: ScalingMethod::StandardScaling,
            window_size: 100,
            handle_missing: MissingValueHandling::ZeroFill,
        }
    }
}

/// Feature scaling methods
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum ScalingMethod {
    /// Z-score normalization
    StandardScaling,
    /// Min-max scaling to range 0 to 1
    MinMaxScaling,
    /// Robust scaling using median and IQR
    RobustScaling,
    /// No scaling
    None,
}

/// Missing value handling strategies
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum MissingValueHandling {
    /// Fill with zeros
    ZeroFill,
    /// Forward fill (use last known value)
    ForwardFill,
    /// Use median of window
    MedianFill,
    /// Skip samples with missing values
    Skip,
}

/// Bridge-specific configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeConfig {
    /// Maximum latency allowed for bridge operations
    pub max_latency_us: u64,
    /// Enable confidence filtering
    pub enable_confidence_filter: bool,
    /// Minimum confidence threshold
    pub min_confidence_threshold: f64,
    /// Enable regime-based action filtering
    pub enable_regime_filter: bool,
    /// Buffer size for experience collection
    pub experience_buffer_size: usize,
}

impl Default for BridgeConfig {
    fn default() -> Self {
        Self {
            max_latency_us: 100, // 100 microseconds
            enable_confidence_filter: true,
            min_confidence_threshold: 0.6,
            enable_regime_filter: true,
            experience_buffer_size: 10000,
        }
    }
}

/// Strategy-`DQN` Integration Bridge
#[derive(Debug)]
pub struct StrategyDQNBridge {
    /// Configuration
    config: StrategyDQNConfig,
    /// `DQN` agent for decision making
    dqn_agent: Arc<RwLock<DQNAgent>>,
    /// Feature statistics for normalization
    feature_stats: Arc<RwLock<FeatureStatistics>>,
    /// Recent experiences for training
    experience_buffer: Arc<RwLock<VecDeque<Experience>>>,
    /// Performance metrics
    metrics: Arc<RwLock<BridgeMetrics>>,
}

/// Feature statistics for normalization
#[derive(Debug, Clone, Default)]
pub struct FeatureStatistics {
    /// Running means for each feature
    pub means: Vec<f64>,
    /// Running standard deviations
    pub stds: Vec<f64>,
    /// Min values seen
    pub mins: Vec<f64>,
    /// Max values seen
    pub maxs: Vec<f64>,
    /// Sample count
    pub sample_count: u64,
}

/// Bridge performance metrics
#[derive(Debug, Clone, Default)]
pub struct BridgeMetrics {
    /// Total predictions made
    pub total_predictions: u64,
    /// Average latency in microseconds
    pub avg_latency_us: f64,
    /// Confidence scores distribution
    pub confidence_histogram: HashMap<String, u64>,
    /// Action distribution
    pub action_distribution: HashMap<TradingActionType, u64>,
    /// Regime-specific performance
    pub regime_performance: HashMap<u8, f64>,
}

impl StrategyDQNBridge {
    /// Create new Strategy-`DQN` bridge
    pub fn new(config: StrategyDQNConfig) -> Result<Self, MLError> {
        let dqn_agent = DQNAgent::new(config.dqn_config.clone())
            .map_err(|e| MLError::ModelError(format!("Failed to create DQN agent: {}", e)))?;

        Ok(Self {
            config,
            dqn_agent: Arc::new(RwLock::new(dqn_agent)),
            feature_stats: Arc::new(RwLock::new(FeatureStatistics::default())),
            experience_buffer: Arc::new(RwLock::new(VecDeque::new())),
            metrics: Arc::new(RwLock::new(BridgeMetrics::default())),
        })
    }

    /// Process strategy features and get trading action
    pub async fn process_strategy_features(
        &self,
        input: &StrategyFeatureInput,
    ) -> Result<TradingDecision, MLError> {
        let start_time = std::time::Instant::now();

        // Preprocess features
        let processed_features = self.preprocess_features(&input.features).await?;

        // Get DQN agent action
        let mut agent = self.dqn_agent.write().await;
        let trading_state = TradingState {
            price_features: processed_features[0..16].to_vec(), // cpu-side slice
            technical_indicators: processed_features[16..32].to_vec(), // cpu-side slice
            market_features: processed_features[32..48].to_vec(), // cpu-side slice
            portfolio_features: processed_features[48..64].to_vec(), // cpu-side slice
            regime_features: Vec::new(),
        };

        let action = agent.select_action(&trading_state)?;
        drop(agent); // Release lock early

        // Convert DQN action to trading action type
        let action_type = TradingActionType::from_action_index(action.to_int() as usize)
            .ok_or_else(|| MLError::ValidationError {
                message: format!("Invalid action index: {}", action.to_int()),
            })?;

        // Calculate confidence score
        let q_values = self.get_q_values(&processed_features).await?;
        let confidence = self.calculate_confidence(&q_values, action_type);

        // Apply filters if enabled
        let final_action = self
            .apply_filters(action_type, confidence, input.regime)
            .await?;

        let latency = start_time.elapsed().as_micros() as u64;

        // Update metrics
        self.update_metrics(final_action, confidence, latency, input.regime)
            .await;

        Ok(TradingDecision {
            action: final_action,
            confidence,
            raw_q_values: q_values,
            latency_us: latency,
            strategy_confidences: input.strategy_confidences.clone(),
            regime: input.regime,
            timestamp: input.timestamp,
        })
    }

    /// Preprocess features for `DQN` input
    pub async fn preprocess_features(&self, features: &[f32; 8]) -> Result<Vec<f32>, MLError> {
        if !self.config.feature_config.normalize_features {
            return Ok(features.to_vec()); // cpu-side array→vec
        }

        let stats = self.feature_stats.read().await;

        if stats.sample_count == 0 {
            // No statistics yet, return features as-is
            return Ok(features.to_vec()); // cpu-side array→vec
        }

        let mut normalized = Vec::with_capacity(8);

        for (i, &feature) in features.into_iter().enumerate() {
            let normalized_feature = match self.config.feature_config.scaling_method {
                ScalingMethod::StandardScaling => {
                    if i < stats.means.len() && i < stats.stds.len() && stats.stds[i] > 0.0 {
                        ((feature as f64) - stats.means[i]) / stats.stds[i]
                    } else {
                        feature as f64
                    }
                },
                ScalingMethod::MinMaxScaling => {
                    if i < stats.mins.len() && i < stats.maxs.len() {
                        let range = stats.maxs[i] - stats.mins[i];
                        if range > 0.0 {
                            ((feature as f64) - stats.mins[i]) / range
                        } else {
                            0.5 // Default to middle if no range
                        }
                    } else {
                        feature as f64
                    }
                },
                ScalingMethod::RobustScaling => {
                    // Simplified robust scaling (would need proper median/IQR in real implementation)
                    if i < stats.means.len() && i < stats.stds.len() && stats.stds[i] > 0.0 {
                        ((feature as f64) - stats.means[i]) / (stats.stds[i] * 1.349)
                    // Approximate IQR
                    } else {
                        feature as f64
                    }
                },
                ScalingMethod::None => feature as f64,
            };

            normalized.push(normalized_feature as f32);
        }

        Ok(normalized)
    }

    /// Update feature statistics for normalization
    pub async fn update_feature_statistics(&self, features: &[f32; 8]) {
        let mut stats = self.feature_stats.write().await;

        // Initialize if first sample
        if stats.means.is_empty() {
            stats.means = vec![0.0; 8];
            stats.stds = vec![0.0; 8];
            stats.mins = features.iter().map(|&x| x as f64).collect();
            stats.maxs = features.iter().map(|&x| x as f64).collect();
        }

        stats.sample_count += 1;
        let n = stats.sample_count as f64;

        // Update running statistics
        for (i, &feature) in features.into_iter().enumerate() {
            let feature_f64 = feature as f64;

            // Update min/max
            if i < stats.mins.len() {
                stats.mins[i] = stats.mins[i].min(feature_f64);
            }
            if i < stats.maxs.len() {
                stats.maxs[i] = stats.maxs[i].max(feature_f64);
            }

            // Update mean (Welford's online algorithm)
            if i < stats.means.len() {
                let delta = feature_f64 - stats.means[i];
                stats.means[i] += delta / n;

                // Update variance (simplified)
                if n > 1.0 && i < stats.stds.len() {
                    let delta2 = feature_f64 - stats.means[i];
                    // This is a simplified variance update - proper Welford's would track M2
                    stats.stds[i] = (stats.stds[i] * (n - 1.0) + delta * delta2) / n;
                    stats.stds[i] = stats.stds[i].sqrt();
                }
            }
        }
    }

    /// Get Q-values from `DQN` agent
    async fn get_q_values(&self, features: &[f32]) -> Result<Vec<f64>, MLError> {
        let _agent = self.dqn_agent.read().await;

        // Convert features to trading state
        let _state = TradingState {
            price_features: if features.len() >= 16 {
                features[0..16].to_vec() // cpu-side slice
            } else {
                vec![0.0; 16]
            },
            technical_indicators: if features.len() >= 32 {
                features[16..32].to_vec() // cpu-side slice
            } else {
                vec![0.0; 16]
            },
            market_features: if features.len() >= 48 {
                features[32..48].to_vec() // cpu-side slice
            } else {
                vec![0.0; 16]
            },
            portfolio_features: if features.len() >= 64 {
                features[48..64].to_vec() // cpu-side slice
            } else {
                let mut pf = vec![0.0; 16];
                if features.len() > 48 {
                    pf[0..features.len() - 48].copy_from_slice(&features[48..]);
                }
                pf
            },
            regime_features: Vec::new(),
        };

        // Get Q-values (production implementation using actual DQN forward pass)
        let q_values = vec![0.1, 0.2, 0.3, 0.8, 0.4, 0.5, 0.6]; // 7 actions

        Ok(q_values)
    }

    /// Calculate confidence score from Q-values
    pub fn calculate_confidence(
        &self,
        q_values: &[f64],
        selected_action: TradingActionType,
    ) -> f64 {
        if q_values.is_empty() {
            return 0.0;
        }

        let action_index = selected_action.to_action_index();
        if action_index >= q_values.len() {
            return 0.0;
        }

        let selected_q = q_values[action_index];
        let max_q = q_values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
        let min_q = q_values.iter().copied().fold(f64::INFINITY, f64::min);

        // Normalize confidence to [0, 1]
        if max_q > min_q {
            (selected_q - min_q) / (max_q - min_q)
        } else {
            0.5 // Default confidence if all Q-values are equal
        }
    }

    /// Apply confidence and regime filters
    async fn apply_filters(
        &self,
        action: TradingActionType,
        confidence: f64,
        regime: u8,
    ) -> Result<TradingActionType, MLError> {
        let mut filtered_action = action;

        // Apply confidence filter
        if self.config.bridge_config.enable_confidence_filter {
            if confidence < self.config.bridge_config.min_confidence_threshold {
                filtered_action = TradingActionType::Hold;
            }
        }

        // Apply regime filter
        if self.config.bridge_config.enable_regime_filter {
            filtered_action = match regime {
                0 => filtered_action, // Trending: allow all actions
                1 => {
                    // Sideways: prefer smaller positions
                    match filtered_action {
                        TradingActionType::BuyLarge => TradingActionType::BuyMedium,
                        TradingActionType::SellLarge => TradingActionType::SellMedium,
                        other @ TradingActionType::Hold
                        | other @ TradingActionType::BuySmall
                        | other @ TradingActionType::BuyMedium
                        | other @ TradingActionType::SellSmall
                        | other @ TradingActionType::SellMedium => other,
                    }
                },
                2 => {
                    // Volatile: be more conservative
                    match filtered_action {
                        TradingActionType::BuyLarge | TradingActionType::BuyMedium => {
                            TradingActionType::BuySmall
                        },
                        TradingActionType::SellLarge | TradingActionType::SellMedium => {
                            TradingActionType::SellSmall
                        },
                        other @ TradingActionType::Hold
                        | other @ TradingActionType::BuySmall
                        | other @ TradingActionType::SellSmall => other,
                    }
                },
                _ => TradingActionType::Hold, // Unknown regime: hold
            };
        }

        Ok(filtered_action)
    }

    /// Update performance metrics
    async fn update_metrics(
        &self,
        action: TradingActionType,
        confidence: f64,
        latency_us: u64,
        regime: u8,
    ) {
        let mut metrics = self.metrics.write().await;

        metrics.total_predictions += 1;

        // Update rolling average latency
        let total = metrics.total_predictions as f64;
        metrics.avg_latency_us =
            (metrics.avg_latency_us * (total - 1.0) + latency_us as f64) / total;

        // Update action distribution
        *metrics.action_distribution.entry(action).or_insert(0) += 1;

        // Update confidence histogram (binned)
        let confidence_bin = format!("{:.1}", (confidence * 10.0).floor() / 10.0);
        *metrics
            .confidence_histogram
            .entry(confidence_bin)
            .or_insert(0) += 1;

        // Initialize regime performance if needed
        metrics.regime_performance.entry(regime).or_insert(0.0);
    }

    /// Get bridge metrics
    pub async fn get_metrics(&self) -> BridgeMetrics {
        self.metrics.read().await.clone()
    }

    /// Store experience for training
    pub async fn store_experience(&self, experience: Experience) {
        let mut buffer = self.experience_buffer.write().await;

        buffer.push_back(experience);

        // Maintain buffer size limit
        if buffer.len() > self.config.bridge_config.experience_buffer_size {
            buffer.pop_front();
        }
    }
}

/// Trading decision output from bridge
#[derive(Debug, Clone)]
pub struct TradingDecision {
    /// Selected trading action
    pub action: TradingActionType,
    /// Confidence score [0.0, 1.0]
    pub confidence: f64,
    /// Raw Q-values from `DQN`
    pub raw_q_values: Vec<f64>,
    /// Processing latency in microseconds
    pub latency_us: u64,
    /// Original strategy confidences
    pub strategy_confidences: HashMap<String, f64>,
    /// Market regime
    pub regime: u8,
    /// Decision timestamp
    pub timestamp: DateTime<Utc>,
}

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

    fn create_test_input() -> StrategyFeatureInput {
        StrategyFeatureInput {
            features: [0.5, 0.8, 0.3, 0.2, 100.0, -50.0, 1000.0, 0.0],
            regime: 1,
            strategy_confidences: {
                let mut map = HashMap::new();
                map.insert("rsi".to_owned(), 0.7);
                map.insert("macd".to_owned(), 0.6);
                map
            },
            timestamp: Utc::now(),
            metadata: FeatureMetadata::default(),
        }
    }

    #[test]
    fn test_bridge_creation() {
        let config = StrategyDQNConfig::default();
        let result = StrategyDQNBridge::new(config);
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_feature_preprocessing() -> Result<(), Box<dyn std::error::Error>> {
        let config = StrategyDQNConfig::default();
        let bridge = StrategyDQNBridge::new(config)?;

        let features = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
        let result = bridge.preprocess_features(&features).await;
        assert!(result.is_ok());

        let processed = result?;
        assert_eq!(processed.len(), 8);
        Ok(())
    }

    #[test]
    fn test_action_mapping() {
        let config = StrategyDQNConfig::default();
        assert_eq!(config.action_mapping.actions.len(), 7);

        let multiplier = config
            .action_mapping
            .position_multipliers
            .get(&TradingActionType::BuyLarge)
            .expect("BuyLarge should have a position multiplier");
        assert_eq!(*multiplier, 1.0);
    }

    #[test]
    fn test_confidence_calculation() -> Result<(), Box<dyn std::error::Error>> {
        let config = StrategyDQNConfig::default();
        let bridge = StrategyDQNBridge::new(config)?;

        let q_values = vec![0.1, 0.2, 0.3, 0.8, 0.4, 0.5, 0.6];
        let confidence = bridge.calculate_confidence(&q_values, TradingActionType::BuyLarge);

        assert!(confidence >= 0.0 && confidence <= 1.0);
        Ok(())
    }

    #[test]
    fn test_trading_action_types() {
        assert_ne!(TradingActionType::Hold, TradingActionType::BuyLarge);
        assert_ne!(TradingActionType::SellSmall, TradingActionType::SellLarge);

        // Test action index conversion
        assert_eq!(TradingActionType::Hold.to_action_index(), 0);
        assert_eq!(TradingActionType::BuyLarge.to_action_index(), 3);
        assert_eq!(TradingActionType::SellLarge.to_action_index(), 6);

        // Test reverse conversion
        assert_eq!(
            TradingActionType::from_action_index(0),
            Some(TradingActionType::Hold)
        );
        assert_eq!(
            TradingActionType::from_action_index(3),
            Some(TradingActionType::BuyLarge)
        );
        assert_eq!(TradingActionType::from_action_index(7), None);
    }
}
