//! Regime State Persistence Helper
//!
//! High-level utilities for persisting regime detection states to the database.
//! This module bridges the gap between feature extraction (indices 201-224) and
//! database persistence (regime_states, regime_transitions, adaptive_strategy_metrics).
//!
//! ## Usage in ML Training
//! ```rust,no_run
//! use common::regime_persistence::RegimePersistenceManager;
//! use common::database::DatabasePool;
//!
//! let db_pool = DatabasePool::new("postgresql://...").await?;
//! let mut manager = RegimePersistenceManager::new(db_pool);
//!
//! // After extracting 225 features
//! manager.process_regime_features(
//!     "ES.FUT",
//!     &features[201..225],  // 24 regime features
//!     timestamp
//! ).await?;
//! ```
//!
//! ## Feature Mapping
//! - **Features 201-210**: CUSUM Statistics (structural breaks)
//! - **Features 211-215**: ADX & Directional (trend strength)
//! - **Features 216-220**: Transition Probabilities
//! - **Features 221-224**: Adaptive Metrics (position/risk multipliers)

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use tracing::debug;

use crate::database::{DatabaseError, DatabasePool};

/// Regime classification based on CUSUM and ADX features
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegimeType {
    /// High volatility market
    Volatile,
    /// Strong directional movement
    Trending,
    /// Low volatility, mean-reverting
    Ranging,
    /// Neutral/undefined state
    Normal,
}

impl RegimeType {
    /// Convert to database string representation
    pub fn as_str(&self) -> &'static str {
        match self {
            RegimeType::Volatile => "Volatile",
            RegimeType::Trending => "Trending",
            RegimeType::Ranging => "Ranging",
            RegimeType::Normal => "Normal",
        }
    }

    /// Classify regime from CUSUM and ADX features
    pub fn from_features(cusum_mean: f64, cusum_std: f64, adx: f64) -> Self {
        // Classification thresholds (from Wave D design)
        const VOLATILE_STD_THRESHOLD: f64 = 2.0;
        const TRENDING_CUSUM_THRESHOLD: f64 = 1.5;
        const TRENDING_ADX_THRESHOLD: f64 = 25.0;

        if cusum_std > VOLATILE_STD_THRESHOLD {
            RegimeType::Volatile
        } else if cusum_mean.abs() > TRENDING_CUSUM_THRESHOLD && adx > TRENDING_ADX_THRESHOLD {
            RegimeType::Trending
        } else if adx < 20.0 && cusum_std < 1.0 {
            RegimeType::Ranging
        } else {
            RegimeType::Normal
        }
    }
}

/// Regime persistence manager for database operations
#[derive(Debug)]
pub struct RegimePersistenceManager {
    /// Database connection pool
    db_pool: DatabasePool,
    /// Previous regime cache (symbol -> regime)
    prev_regime_cache: HashMap<String, String>,
    /// Regime start timestamp cache (symbol -> timestamp)
    regime_start_cache: HashMap<String, DateTime<Utc>>,
    /// Bar counter for duration tracking (symbol -> bar_count)
    bar_counter: HashMap<String, i32>,
}

impl RegimePersistenceManager {
    /// Create new regime persistence manager
    pub fn new(db_pool: DatabasePool) -> Self {
        Self {
            db_pool,
            prev_regime_cache: HashMap::new(),
            regime_start_cache: HashMap::new(),
            bar_counter: HashMap::new(),
        }
    }

    /// Process regime features and persist to database
    ///
    /// # Arguments
    /// * `symbol` - Trading symbol (e.g., "ES.FUT")
    /// * `regime_features` - Array of 24 regime features (indices 201-224)
    /// * `timestamp` - Feature extraction timestamp
    ///
    /// # Feature Layout
    /// ```text
    /// [0-9]:   CUSUM Statistics (features 201-210)
    /// [10-14]: ADX & Directional (features 211-215)
    /// [15-19]: Transition Probabilities (features 216-220)
    /// [20-23]: Adaptive Metrics (features 221-224)
    /// ```
    pub async fn process_regime_features(
        &mut self,
        symbol: &str,
        regime_features: &[f64],
        timestamp: DateTime<Utc>,
    ) -> Result<()> {
        if regime_features.len() != 24 {
            return Err(anyhow::anyhow!(
                "Expected 24 regime features, got {}",
                regime_features.len()
            ));
        }

        // Extract CUSUM statistics (features 201-210)
        let cusum_mean = regime_features.first().copied().unwrap_or(0.0);
        let cusum_std = regime_features.get(1).copied().unwrap_or(1.0);
        let cusum_s_plus = regime_features.get(2).copied();
        let cusum_s_minus = regime_features.get(3).copied();

        // Extract ADX and directional indicators (features 211-215)
        let adx = regime_features.get(10).copied().unwrap_or(25.0);
        let adx_opt = Some(adx);

        // Classify regime
        let regime = RegimeType::from_features(cusum_mean, cusum_std, adx);
        let regime_str = regime.as_str();

        // Calculate confidence from ADX (0.0-1.0)
        let confidence = (adx / 50.0).clamp(0.0, 1.0);

        // Calculate stability from CUSUM std (inverse relationship)
        let stability = Some(1.0 / (1.0 + cusum_std));

        // Persist regime state
        self.db_pool
            .insert_regime_state(
                symbol,
                regime_str,
                confidence,
                timestamp,
                cusum_s_plus,
                cusum_s_minus,
                adx_opt,
                stability,
            )
            .await
            .context("Failed to insert regime state")?;

        debug!(
            "Persisted regime state: {} {} (confidence: {:.3})",
            symbol, regime_str, confidence
        );

        // Track regime transition
        let prev_regime_opt = self.prev_regime_cache.get(symbol).cloned();
        if let Some(prev_regime) = prev_regime_opt {
            if prev_regime.as_str() != regime_str {
                self.track_regime_transition(symbol, &prev_regime, regime_str, timestamp, adx, None)
                    .await?;
            } else {
                // Increment bar counter for current regime
                *self.bar_counter.entry(symbol.to_string()).or_insert(0) += 1;
            }
        } else {
            // First observation for this symbol
            self.regime_start_cache.insert(symbol.to_string(), timestamp);
            self.bar_counter.insert(symbol.to_string(), 1);
        }

        // Update cache
        self.prev_regime_cache
            .insert(symbol.to_string(), regime_str.to_string());

        // Update adaptive strategy metrics
        self.update_adaptive_metrics(symbol, regime_str, regime_features, timestamp)
            .await?;

        Ok(())
    }

    /// Track regime transition
    ///
    /// # Arguments
    /// * `features` - Optional full 225-element feature vector. When provided,
    ///   extracts transition probability from Wave D regime features (indices 216-220)
    ///   and CUSUM structural break alert from feature index 210.
    async fn track_regime_transition(
        &mut self,
        symbol: &str,
        from_regime: &str,
        to_regime: &str,
        timestamp: DateTime<Utc>,
        adx: f64,
        features: Option<&[f64]>,
    ) -> Result<()> {
        // Calculate duration in bars
        let duration_bars = self.bar_counter.get(symbol).copied();

        // Extract transition probability from features 216-220 (Wave D regime features).
        // Mapping: bull->216, bear->217, sideways->218, volatile->219, crisis->220.
        // We select the probability corresponding to the target regime.
        let transition_probability = features.and_then(|f| {
            let idx = match to_regime {
                "Trending" => 216,  // bull/trending
                "Ranging" => 218,   // sideways/ranging
                "Volatile" => 219,  // volatile
                "Normal" => 217,    // bear/normal (conservative default)
                _ => return None,
            };
            f.get(idx).copied()
        });

        // Check if this was a CUSUM structural break alert.
        // Feature 210 is the CUSUM statistic; alert triggers when |cusum| > 3.0
        let cusum_alert_triggered = features
            .and_then(|f| f.get(210))
            .map(|cusum_stat| cusum_stat.abs() > 3.0)
            .unwrap_or(false);

        self.db_pool
            .insert_regime_transition(
                symbol,
                from_regime,
                to_regime,
                timestamp,
                duration_bars,
                transition_probability,
                Some(adx),
                cusum_alert_triggered,
            )
            .await
            .context("Failed to insert regime transition")?;

        debug!(
            "Tracked regime transition: {} {} -> {} (duration: {} bars)",
            symbol,
            from_regime,
            to_regime,
            duration_bars.unwrap_or(0)
        );

        // Reset counters for new regime
        self.regime_start_cache.insert(symbol.to_string(), timestamp);
        self.bar_counter.insert(symbol.to_string(), 1);

        Ok(())
    }

    /// Update adaptive strategy metrics
    async fn update_adaptive_metrics(
        &mut self,
        symbol: &str,
        regime: &str,
        regime_features: &[f64],
        timestamp: DateTime<Utc>,
    ) -> Result<()> {
        // Extract adaptive metrics (features 221-224)
        let position_multiplier = regime_features.get(20).copied().unwrap_or(1.0); // Feature 221
        let stop_loss_multiplier = regime_features.get(21).copied().unwrap_or(2.0); // Feature 222

        // Regime Sharpe and risk utilization need to be calculated from backtest results
        // For now, set to None and update during backtesting
        let regime_sharpe = None;
        let risk_budget_utilization = None;

        self.db_pool
            .upsert_adaptive_strategy_metrics(
                symbol,
                regime,
                timestamp,
                position_multiplier,
                stop_loss_multiplier,
                regime_sharpe,
                risk_budget_utilization,
                0, // total_trades (updated during backtesting)
                0, // winning_trades (updated during backtesting)
                0, // total_pnl (updated during backtesting)
            )
            .await
            .context("Failed to upsert adaptive strategy metrics")?;

        debug!(
            "Updated adaptive metrics: {} {} (pos_mult: {:.2}, stop_mult: {:.2})",
            symbol, regime, position_multiplier, stop_loss_multiplier
        );

        Ok(())
    }

    /// Update trading performance metrics (called after trade execution)
    pub async fn update_trade_metrics(
        &mut self,
        symbol: &str,
        regime: &str,
        timestamp: DateTime<Utc>,
        pnl: i64,
        is_winner: bool,
    ) -> Result<()> {
        // Get existing metrics or create new ones
        let position_multiplier = 1.0; // Default, should be fetched from cache
        let stop_loss_multiplier = 2.0; // Default, should be fetched from cache

        self.db_pool
            .upsert_adaptive_strategy_metrics(
                symbol,
                regime,
                timestamp,
                position_multiplier,
                stop_loss_multiplier,
                None, // regime_sharpe (calculated separately)
                None, // risk_budget_utilization (calculated separately)
                1,    // total_trades increment
                if is_winner { 1 } else { 0 }, // winning_trades increment
                pnl,  // total_pnl increment
            )
            .await
            .context("Failed to update trade metrics")?;

        Ok(())
    }

    /// Get latest regime for a symbol
    pub async fn get_latest_regime(&self, symbol: &str) -> Result<String, DatabaseError> {
        let regime_state = self.db_pool.get_latest_regime(symbol).await?;
        Ok(regime_state.regime)
    }

    /// Get regime transition history
    pub async fn get_regime_history(
        &self,
        symbol: &str,
        limit: i32,
    ) -> Result<Vec<crate::database::RegimeTransition>, DatabaseError> {
        self.db_pool.get_regime_transitions(symbol, limit).await
    }

    /// Clear caches (useful for testing)
    pub fn clear_caches(&mut self) {
        self.prev_regime_cache.clear();
        self.regime_start_cache.clear();
        self.bar_counter.clear();
    }
}

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

    #[test]
    fn test_regime_classification() {
        // Test volatile regime
        let regime = RegimeType::from_features(0.5, 3.0, 30.0);
        assert_eq!(regime, RegimeType::Volatile);

        // Test trending regime
        let regime = RegimeType::from_features(2.0, 1.0, 35.0);
        assert_eq!(regime, RegimeType::Trending);

        // Test ranging regime
        let regime = RegimeType::from_features(0.2, 0.5, 15.0);
        assert_eq!(regime, RegimeType::Ranging);

        // Test normal regime
        let regime = RegimeType::from_features(0.5, 1.0, 22.0);
        assert_eq!(regime, RegimeType::Normal);
    }

    #[test]
    fn test_regime_str_conversion() {
        assert_eq!(RegimeType::Volatile.as_str(), "Volatile");
        assert_eq!(RegimeType::Trending.as_str(), "Trending");
        assert_eq!(RegimeType::Ranging.as_str(), "Ranging");
        assert_eq!(RegimeType::Normal.as_str(), "Normal");
    }
}
