//! Regime Detection Database Query Layer
//!
//! Provides database access to regime state information for regime-adaptive
//! position sizing and dynamic stop-loss calculations.
//!
//! ## Integration
//!
//! This module queries the `regime_states` table (migration 045) to retrieve
//! the latest market regime for each symbol. The regime information is then
//! used by the allocation and order generation modules to apply regime-specific
//! multipliers.
//!
//! ## Example
//!
//! ```rust
//! use trading_agent_service::regime::{get_regime_for_symbol, RegimeState};
//! use sqlx::PgPool;
//!
//! # async fn example(pool: PgPool) -> anyhow::Result<()> {
//! let regime = get_regime_for_symbol(&pool, "ES.FUT").await?;
//! println!("ES.FUT regime: {} (confidence: {:.2})", regime.regime, regime.confidence);
//! # Ok(())
//! # }
//! ```

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use sqlx::PgPool;

/// Market regime state for a symbol
///
/// Represents the current market regime as detected by the regime detection
/// modules (Wave D Phase 1) and stored in the `regime_states` table.
#[derive(Debug, Clone)]
pub struct RegimeState {
    /// Symbol identifier (e.g., "ES.FUT")
    pub symbol: String,
    
    /// Current market regime
    ///
    /// Valid values: Normal, Trending, Ranging, Volatile, Crisis, Illiquid, Momentum
    /// Maps to `ml::ensemble::MarketRegime` variants
    pub regime: String,
    
    /// Confidence level (0.0-1.0)
    ///
    /// Higher values indicate more confidence in the regime classification.
    /// Typical threshold: 0.7 for production use.
    pub confidence: f64,
    
    /// Timestamp of regime detection event
    pub timestamp: DateTime<Utc>,
    
    /// Optional ADX value (0.0-100.0)
    ///
    /// Average Directional Index - measures trend strength.
    /// - ADX < 20: Weak/no trend (ranging)
    /// - ADX 20-40: Moderate trend
    /// - ADX > 40: Strong trend
    pub adx: Option<f64>,
    
    /// Optional +DI value (0.0-100.0)
    ///
    /// Plus Directional Indicator - measures upward movement.
    pub plus_di: Option<f64>,
    
    /// Optional -DI value (0.0-100.0)
    ///
    /// Minus Directional Indicator - measures downward movement.
    pub minus_di: Option<f64>,
}

/// Get latest regime state for a symbol
///
/// Queries the `regime_states` table to retrieve the most recent regime
/// classification for the specified symbol.
///
/// # Arguments
///
/// * `pool` - Database connection pool
/// * `symbol` - Symbol identifier (e.g., "ES.FUT")
///
/// # Returns
///
/// Latest `RegimeState` for the symbol, or an error if:
/// - Symbol not found in database
/// - Database query fails
/// - No regime data available
///
/// # Example
///
/// ```rust
/// use trading_agent_service::regime::get_regime_for_symbol;
/// use sqlx::PgPool;
///
/// # async fn example(pool: PgPool) -> anyhow::Result<()> {
/// let regime = get_regime_for_symbol(&pool, "ES.FUT").await?;
/// 
/// match regime.regime.as_str() {
///     "Trending" => println!("Apply 1.5x position multiplier"),
///     "Volatile" => println!("Apply 0.5x position multiplier"),
///     "Crisis" => println!("Apply 0.2x position multiplier"),
///     _ => println!("Apply 1.0x position multiplier"),
/// }
/// # Ok(())
/// # }
/// ```
pub async fn get_regime_for_symbol(pool: &PgPool, symbol: &str) -> Result<RegimeState> {
    let row = sqlx::query!(
        r#"
        SELECT 
            symbol,
            regime,
            confidence,
            event_timestamp,
            adx,
            plus_di,
            minus_di
        FROM regime_states
        WHERE symbol = $1
        ORDER BY event_timestamp DESC
        LIMIT 1
        "#,
        symbol
    )
    .fetch_optional(pool)
    .await
    .context("Failed to query regime_states table")?
    .with_context(|| format!("No regime data found for symbol: {}", symbol))?;

    Ok(RegimeState {
        symbol: row.symbol,
        regime: row.regime,
        confidence: row.confidence,
        timestamp: row.event_timestamp,
        adx: row.adx,
        plus_di: row.plus_di,
        minus_di: row.minus_di,
    })
}

/// Get regime states for multiple symbols
///
/// Batch query to retrieve latest regime states for multiple symbols.
/// More efficient than calling `get_regime_for_symbol` multiple times.
///
/// # Arguments
///
/// * `pool` - Database connection pool
/// * `symbols` - Slice of symbol identifiers
///
/// # Returns
///
/// Vector of `RegimeState` for symbols with available data.
/// Symbols without regime data are omitted from the result.
///
/// # Example
///
/// ```rust
/// use trading_agent_service::regime::get_regimes_for_symbols;
/// use sqlx::PgPool;
///
/// # async fn example(pool: PgPool) -> anyhow::Result<()> {
/// let symbols = vec!["ES.FUT", "NQ.FUT", "ZN.FUT"];
/// let regimes = get_regimes_for_symbols(&pool, &symbols).await?;
/// 
/// for regime in regimes {
///     println!("{}: {} (conf: {:.2})", regime.symbol, regime.regime, regime.confidence);
/// }
/// # Ok(())
/// # }
/// ```
pub async fn get_regimes_for_symbols(pool: &PgPool, symbols: &[&str]) -> Result<Vec<RegimeState>> {
    if symbols.is_empty() {
        return Ok(vec![]);
    }

    // Build IN clause for symbols
    let symbol_list: Vec<String> = symbols.iter().map(|s| s.to_string()).collect();

    let rows = sqlx::query!(
        r#"
        SELECT DISTINCT ON (symbol)
            symbol,
            regime,
            confidence,
            event_timestamp,
            adx,
            plus_di,
            minus_di
        FROM regime_states
        WHERE symbol = ANY($1)
        ORDER BY symbol, event_timestamp DESC
        "#,
        &symbol_list
    )
    .fetch_all(pool)
    .await
    .context("Failed to query regime_states table for multiple symbols")?;

    Ok(rows
        .into_iter()
        .map(|row| RegimeState {
            symbol: row.symbol,
            regime: row.regime,
            confidence: row.confidence,
            timestamp: row.event_timestamp,
            adx: row.adx,
            plus_di: row.plus_di,
            minus_di: row.minus_di,
        })
        .collect())
}

/// Map regime string to position size multiplier
///
/// Applies the position sizing strategy defined in Wave D Phase 2.
/// See `ml::features::regime_adaptive::POSITION_MULTIPLIERS` for reference.
///
/// # Arguments
///
/// * `regime` - Regime name (e.g., "Trending", "Volatile", "Crisis")
///
/// # Returns
///
/// Position size multiplier in range [0.2, 1.5]:
/// - Normal: 1.0x (baseline)
/// - Trending: 1.5x (increase size in trends)
/// - Ranging/Sideways: 0.8x (reduce size in choppy markets)
/// - Volatile: 0.5x (reduce risk during volatility)
/// - Crisis: 0.2x (extreme risk reduction)
/// - Bull: 1.2x (moderate increase in bull markets)
/// - Bear: 0.7x (reduce exposure in bear markets)
///
/// # Example
///
/// ```rust
/// use trading_agent_service::regime::regime_to_position_multiplier;
///
/// assert_eq!(regime_to_position_multiplier("Trending"), 1.5);
/// assert_eq!(regime_to_position_multiplier("Crisis"), 0.2);
/// assert_eq!(regime_to_position_multiplier("Unknown"), 1.0);
/// ```
pub fn regime_to_position_multiplier(regime: &str) -> f64 {
    match regime {
        "Normal" => 1.0,
        "Trending" => 1.5,
        "Ranging" | "Sideways" => 0.8,
        "Volatile" => 0.5,
        "Crisis" => 0.2,
        "Bull" => 1.2,
        "Bear" => 0.7,
        "Momentum" => 1.3, // Similar to Trending but slightly lower
        "Illiquid" => 0.6, // Reduce size in illiquid markets
        _ => 1.0,          // Default to Normal regime
    }
}

/// Map regime string to stop-loss ATR multiplier
///
/// Applies the dynamic stop-loss strategy defined in Wave D Phase 2.
/// See `ml::features::regime_adaptive::STOPLOSS_MULTIPLIERS` for reference.
///
/// # Arguments
///
/// * `regime` - Regime name (e.g., "Trending", "Volatile", "Crisis")
///
/// # Returns
///
/// Stop-loss multiplier in range [1.5, 4.0] (in ATR units):
/// - Normal: 2.0x ATR (standard stop distance)
/// - Trending: 2.5x ATR (wider stops to avoid whipsaws)
/// - Ranging/Sideways: 1.5x ATR (tighter stops in ranges)
/// - Volatile: 3.0x ATR (wider stops for volatility)
/// - Crisis: 4.0x ATR (very wide stops to avoid panic exits)
/// - Bull: 2.0x ATR (standard stops in bull markets)
/// - Bear: 2.5x ATR (wider stops in bear markets)
///
/// # Example
///
/// ```rust
/// use trading_agent_service::regime::regime_to_stoploss_multiplier;
///
/// assert_eq!(regime_to_stoploss_multiplier("Normal"), 2.0);
/// assert_eq!(regime_to_stoploss_multiplier("Crisis"), 4.0);
/// assert_eq!(regime_to_stoploss_multiplier("Ranging"), 1.5);
/// ```
pub fn regime_to_stoploss_multiplier(regime: &str) -> f64 {
    match regime {
        "Normal" => 2.0,
        "Trending" => 2.5,
        "Ranging" | "Sideways" => 1.5,
        "Volatile" => 3.0,
        "Crisis" => 4.0,
        "Bull" => 2.0,
        "Bear" => 2.5,
        "Momentum" => 2.5, // Similar to Trending
        "Illiquid" => 3.5, // Wider stops in illiquid markets
        _ => 2.0,          // Default to Normal regime
    }
}

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

    #[test]
    fn test_position_multiplier_mapping() {
        // Test all expected regimes
        assert_eq!(regime_to_position_multiplier("Normal"), 1.0);
        assert_eq!(regime_to_position_multiplier("Trending"), 1.5);
        assert_eq!(regime_to_position_multiplier("Ranging"), 0.8);
        assert_eq!(regime_to_position_multiplier("Sideways"), 0.8);
        assert_eq!(regime_to_position_multiplier("Volatile"), 0.5);
        assert_eq!(regime_to_position_multiplier("Crisis"), 0.2);
        assert_eq!(regime_to_position_multiplier("Bull"), 1.2);
        assert_eq!(regime_to_position_multiplier("Bear"), 0.7);
        assert_eq!(regime_to_position_multiplier("Momentum"), 1.3);
        assert_eq!(regime_to_position_multiplier("Illiquid"), 0.6);
        
        // Test unknown regime defaults to Normal
        assert_eq!(regime_to_position_multiplier("Unknown"), 1.0);
        assert_eq!(regime_to_position_multiplier(""), 1.0);
    }

    #[test]
    fn test_stoploss_multiplier_mapping() {
        // Test all expected regimes
        assert_eq!(regime_to_stoploss_multiplier("Normal"), 2.0);
        assert_eq!(regime_to_stoploss_multiplier("Trending"), 2.5);
        assert_eq!(regime_to_stoploss_multiplier("Ranging"), 1.5);
        assert_eq!(regime_to_stoploss_multiplier("Sideways"), 1.5);
        assert_eq!(regime_to_stoploss_multiplier("Volatile"), 3.0);
        assert_eq!(regime_to_stoploss_multiplier("Crisis"), 4.0);
        assert_eq!(regime_to_stoploss_multiplier("Bull"), 2.0);
        assert_eq!(regime_to_stoploss_multiplier("Bear"), 2.5);
        assert_eq!(regime_to_stoploss_multiplier("Momentum"), 2.5);
        assert_eq!(regime_to_stoploss_multiplier("Illiquid"), 3.5);
        
        // Test unknown regime defaults to Normal
        assert_eq!(regime_to_stoploss_multiplier("Unknown"), 2.0);
        assert_eq!(regime_to_stoploss_multiplier(""), 2.0);
    }

    #[test]
    fn test_position_multiplier_ranges() {
        let regimes = vec![
            "Normal", "Trending", "Ranging", "Sideways", "Volatile",
            "Crisis", "Bull", "Bear", "Momentum", "Illiquid",
        ];

        for regime in regimes {
            let mult = regime_to_position_multiplier(regime);
            assert!(
                (0.2..=1.5).contains(&mult),
                "Position multiplier for {} ({}) out of range [0.2, 1.5]",
                regime,
                mult
            );
        }
    }

    #[test]
    fn test_stoploss_multiplier_ranges() {
        let regimes = vec![
            "Normal", "Trending", "Ranging", "Sideways", "Volatile",
            "Crisis", "Bull", "Bear", "Momentum", "Illiquid",
        ];

        for regime in regimes {
            let mult = regime_to_stoploss_multiplier(regime);
            assert!(
                (1.5..=4.0).contains(&mult),
                "Stop-loss multiplier for {} ({}) out of range [1.5, 4.0]",
                regime,
                mult
            );
        }
    }

    #[test]
    fn test_crisis_regime_multipliers() {
        // Crisis should have lowest position size and highest stop-loss
        let pos_mult = regime_to_position_multiplier("Crisis");
        let stop_mult = regime_to_stoploss_multiplier("Crisis");

        assert_eq!(pos_mult, 0.2, "Crisis should have minimum position size");
        assert_eq!(stop_mult, 4.0, "Crisis should have maximum stop-loss width");
    }

    #[test]
    fn test_trending_regime_multipliers() {
        // Trending should have highest position size and wide stops
        let pos_mult = regime_to_position_multiplier("Trending");
        let stop_mult = regime_to_stoploss_multiplier("Trending");

        assert_eq!(
            pos_mult, 1.5,
            "Trending should have maximum position size"
        );
        assert_eq!(stop_mult, 2.5, "Trending should have wide stops");
    }

    #[test]
    fn test_ranging_regime_multipliers() {
        // Ranging should have reduced position size and tight stops
        let pos_mult = regime_to_position_multiplier("Ranging");
        let stop_mult = regime_to_stoploss_multiplier("Ranging");

        assert_eq!(
            pos_mult, 0.8,
            "Ranging should have reduced position size"
        );
        assert_eq!(stop_mult, 1.5, "Ranging should have tight stops");
    }
}
