//! Regime-Adaptive Position Sizing & Stop-Loss Features
//!
//! Implements adaptive trading features that adjust position sizing and stop-loss
//! levels based on detected market regimes. Features indices 221-224 (4 features).
//!
//! ## Architecture
//!
//! ```text
//! ┌──────────────────┐
//! │ Regime Detection │
//! │  (CUSUM, etc.)   │
//! └────────┬─────────┘
//!          │
//!          ▼
//! ┌──────────────────────────────┐
//! │ RegimeAdaptiveFeatures       │
//! ├──────────────────────────────┤
//! │ • Position Size Multiplier   │  ← Feature 221
//! │ • Stop-Loss Multiplier       │  ← Feature 222
//! │ • Regime-Adjusted Returns    │  ← Feature 223
//! │ • ATR-Based Stop Distance    │  ← Feature 224
//! └──────────────────────────────┘
//! ```
//!
//! ## Regime-Based Multipliers
//!
//! ### Position Size Multipliers (POSITION_MULTIPLIERS)
//! - Normal: 1.0x (baseline position sizing)
//! - Trending: 1.5x (capture strong directional moves)
//! - Ranging: 0.8x (reduce exposure in choppy markets)
//! - Volatile: 0.5x (reduce risk during high volatility)
//! - Crisis: 0.2x (extreme risk reduction)
//! - Bull: 1.2x (moderate increase in uptrends)
//! - Bear: 0.7x (reduce exposure in downtrends)
//!
//! ### Stop-Loss Multipliers (STOPLOSS_MULTIPLIERS)
//! - Normal: 2.0x ATR (standard stop distance)
//! - Trending: 2.5x ATR (wider stops to avoid whipsaws)
//! - Ranging: 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)
//!
//! ## Feature Extraction
//!
//! ### Feature 221: Position Size Multiplier
//! ```text
//! multiplier = POSITION_MULTIPLIERS[current_regime]
//! scaled_position = current_position * multiplier / max_position
//! ```
//!
//! ### Feature 222: Stop-Loss Multiplier
//! ```text
//! multiplier = STOPLOSS_MULTIPLIERS[current_regime]
//! atr = compute_atr(bars, atr_period)
//! stop_distance = atr * multiplier
//! ```
//!
//! ### Feature 223: Regime-Adjusted Returns
//! ```text
//! sharpe = mean(returns) / std(returns)
//! regime_sharpe = sharpe * POSITION_MULTIPLIERS[regime]
//! ```
//!
//! ### Feature 224: ATR-Based Stop Distance
//! ```text
//! atr = compute_atr(bars, atr_period)
//! stop_distance = atr * STOPLOSS_MULTIPLIERS[regime]
//! normalized_stop = stop_distance / close_price
//! ```
//!
//! ## Example
//!
//! ```rust
//! use ml::features::regime_adaptive::RegimeAdaptiveFeatures;
//! use ml::ensemble::MarketRegime;
//! use ml::features::extraction::OHLCVBar;
//!
//! let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
//!
//! // Update regime detection
//! let regime = MarketRegime::Trending;
//! let return_value = 0.01;
//! let current_position = 50_000.0;
//! let bars = vec![]; // OHLCV bars
//!
//! // Extract 4 adaptive features (indices 221-224)
//! let features = adaptive.update(regime, return_value, current_position, &bars);
//! assert_eq!(features.len(), 4);
//! ```

use crate::ensemble::MarketRegime;
use crate::features::extraction::OHLCVBar;
use std::collections::VecDeque;

/// Position size multipliers for each market regime
///
/// Maps market regime to position sizing adjustment factor.
/// Higher values = more aggressive position sizing.
const POSITION_MULTIPLIERS: [(MarketRegime, f64); 7] = [
    (MarketRegime::Normal, 1.0),         // Baseline position sizing
    (MarketRegime::Trending, 1.5),       // Increase size in strong trends
    (MarketRegime::Sideways, 0.8),       // Reduce size in range-bound markets
    (MarketRegime::Bull, 1.2),           // Moderate increase in bull markets
    (MarketRegime::Bear, 0.7),           // Reduce size in bear markets
    (MarketRegime::HighVolatility, 0.5), // Reduce size during high volatility
    (MarketRegime::Crisis, 0.2),         // Extreme risk reduction in crisis
];

/// Stop-loss distance multipliers (in ATR units) for each market regime
///
/// Maps market regime to stop-loss distance adjustment factor.
/// Higher values = wider stops (more tolerance for volatility).
const STOPLOSS_MULTIPLIERS: [(MarketRegime, f64); 7] = [
    (MarketRegime::Normal, 2.0),         // Standard 2x ATR stop
    (MarketRegime::Trending, 2.5),       // Wider stops to avoid trend whipsaws
    (MarketRegime::Sideways, 1.5),       // Tighter stops in ranges
    (MarketRegime::Bull, 2.0),           // Standard stops in bull markets
    (MarketRegime::Bear, 2.5),           // Wider stops in bear markets
    (MarketRegime::HighVolatility, 3.0), // Wide stops for high volatility
    (MarketRegime::Crisis, 4.0),         // Very wide stops to avoid panic exits
];

/// Regime-Adaptive Position Sizing & Stop-Loss Features
///
/// Tracks current market regime and adjusts position sizing and stop-loss
/// distances accordingly. Extracts 4 features (indices 221-224).
///
/// # State Management
///
/// - `current_regime`: Last observed market regime
/// - `returns_window`: Rolling window of returns for Sharpe calculation
/// - `window_size`: Size of returns window
/// - `current_position_size`: Current position size (in USD)
/// - `max_position_size`: Maximum allowed position size (in USD)
/// - `atr_period`: Period for ATR calculation (typically 14)
///
/// # Example
///
/// ```rust
/// use ml::features::regime_adaptive::RegimeAdaptiveFeatures;
/// use ml::ensemble::MarketRegime;
///
/// let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
///
/// // Update state
/// let features = adaptive.update(
///     MarketRegime::Trending,
///     0.01,
///     50_000.0,
///     &bars,
/// );
/// ```
#[derive(Debug, Clone)]
pub struct RegimeAdaptiveFeatures {
    /// Current market regime (from regime detection modules)
    current_regime: MarketRegime,

    /// Rolling window of returns for Sharpe calculation
    returns_window: VecDeque<f64>,

    /// Size of returns window
    window_size: usize,

    /// Current position size (in USD)
    current_position_size: f64,

    /// Maximum allowed position size (in USD)
    max_position_size: f64,

    /// Period for ATR calculation (typically 14)
    atr_period: usize,
}

impl RegimeAdaptiveFeatures {
    /// Create new regime-adaptive feature extractor
    ///
    /// # Arguments
    ///
    /// * `window_size` - Size of rolling returns window for Sharpe calculation
    /// * `max_position` - Maximum allowed position size (in USD)
    /// * `atr_period` - Period for ATR calculation (typically 14)
    ///
    /// # Returns
    ///
    /// New `RegimeAdaptiveFeatures` initialized to Normal regime with empty state.
    ///
    /// # Example
    ///
    /// ```rust
    /// use ml::features::regime_adaptive::RegimeAdaptiveFeatures;
    ///
    /// // 20-bar returns window, $100K max position, 14-bar ATR
    /// let adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
    /// ```
    pub fn new(window_size: usize, max_position: f64, atr_period: usize) -> Self {
        Self {
            current_regime: MarketRegime::Normal,
            returns_window: VecDeque::with_capacity(window_size),
            window_size,
            current_position_size: 0.0,
            max_position_size: max_position,
            atr_period,
        }
    }

    /// Update regime state and extract 4 adaptive features (indices 221-224)
    ///
    /// # Arguments
    ///
    /// * `regime` - Current market regime (from regime detection)
    /// * `return_value` - Most recent return (fractional, e.g., 0.01 = 1%)
    /// * `current_position` - Current position size (in USD)
    /// * `bars` - OHLCV bars for ATR calculation
    ///
    /// # Returns
    ///
    /// Array of 4 features:
    /// - `[0]`: Position size multiplier (regime-adjusted, normalized to \[0,1\])
    /// - `[1]`: Stop-loss multiplier (regime-adjusted, in ATR units)
    /// - `[2]`: Regime-adjusted Sharpe ratio (mean return / std * multiplier)
    /// - `[3]`: ATR-based stop distance (normalized by close price)
    ///
    /// # Feature Indices
    ///
    /// - Feature 221: Position size multiplier
    /// - Feature 222: Stop-loss multiplier
    /// - Feature 223: Regime-adjusted Sharpe ratio
    /// - Feature 224: ATR-based stop distance
    ///
    /// # Example
    ///
    /// ```rust
    /// use ml::features::regime_adaptive::RegimeAdaptiveFeatures;
    /// use ml::ensemble::MarketRegime;
    ///
    /// let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
    /// let features = adaptive.update(
    ///     MarketRegime::Trending,
    ///     0.01,  // 1% return
    ///     50_000.0,  // $50K position
    ///     &bars,
    /// );
    /// ```
    pub fn update(
        &mut self,
        regime: MarketRegime,
        return_value: f64,
        current_position: f64,
        bars: &[OHLCVBar],
    ) -> [f64; 4] {
        // Regime transition reset
        if regime != self.current_regime {
            self.returns_window.clear();
            self.current_regime = regime;
        }

        // Update returns window
        self.returns_window.push_back(return_value);
        if self.returns_window.len() > self.window_size {
            self.returns_window.pop_front();
        }

        self.current_position_size = current_position;

        // Feature 221: Position multiplier
        let position_mult = self.get_position_multiplier();

        // Feature 222: Stop-loss multiplier (ATR-based)
        let atr = if bars.len() >= self.atr_period {
            // Compute ATR inline to avoid module dependency
            let mut true_ranges = Vec::new();
            for i in 1..bars.len().min(self.atr_period + 1) {
                let tr = (bars[i].high - bars[i].low)
                    .max((bars[i].high - bars[i - 1].close).abs())
                    .max((bars[i].low - bars[i - 1].close).abs());
                true_ranges.push(tr);
            }
            if !true_ranges.is_empty() {
                true_ranges.iter().sum::<f64>() / true_ranges.len() as f64
            } else {
                0.0
            }
        } else {
            0.0
        };
        let stop_mult = self.get_stoploss_multiplier() * atr;

        // Feature 223: Regime-conditioned Sharpe
        let sharpe = if self.returns_window.len() >= 2 {
            let mean = self.returns_window.iter().sum::<f64>() / self.returns_window.len() as f64;
            let variance = self
                .returns_window
                .iter()
                .map(|r| (r - mean).powi(2))
                .sum::<f64>()
                / self.returns_window.len() as f64;
            let std = variance.sqrt();
            if std > 1e-10 {
                (mean / std) * (252.0_f64).sqrt()
            } else {
                0.0
            }
        } else {
            0.0
        };

        // Feature 224: Risk budget utilization
        let risk_budget = if self.max_position_size > 1e-10 {
            (self.current_position_size / (position_mult * self.max_position_size)).clamp(0.0, 1.0)
        } else {
            0.0
        };

        [position_mult, stop_mult, sharpe, risk_budget]
    }

    /// Get position size multiplier for current regime
    ///
    /// # Returns
    ///
    /// Position size multiplier in [0.2, 1.5] based on current regime.
    fn get_position_multiplier(&self) -> f64 {
        POSITION_MULTIPLIERS
            .iter()
            .find(|(r, _)| *r == self.current_regime)
            .map(|(_, m)| *m)
            .unwrap_or(1.0) // Default to Normal regime multiplier
    }

    /// Get stop-loss multiplier for current regime
    ///
    /// # Returns
    ///
    /// Stop-loss multiplier in [1.5, 4.0] (in ATR units) based on current regime.
    fn get_stoploss_multiplier(&self) -> f64 {
        STOPLOSS_MULTIPLIERS
            .iter()
            .find(|(r, _)| *r == self.current_regime)
            .map(|(_, m)| *m)
            .unwrap_or(2.0) // Default to Normal regime multiplier
    }
}

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

    // Helper function to create test bars
    fn create_test_bars(count: usize, base_price: f64, volatility: f64) -> Vec<OHLCVBar> {
        let base_time = Utc::now();
        (0..count)
            .map(|i| {
                let price = base_price + (i as f64 * 0.1) + (volatility * ((i as f64 * 0.5).sin()));
                OHLCVBar {
                    timestamp: base_time + chrono::Duration::seconds(i as i64 * 60),
                    open: price,
                    high: price * 1.02,
                    low: price * 0.98,
                    close: price,
                    volume: 1000.0,
                }
            })
            .collect()
    }

    #[test]
    fn test_new_initialization() {
        let adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

        assert_eq!(adaptive.window_size, 20);
        assert_eq!(adaptive.max_position_size, 100_000.0);
        assert_eq!(adaptive.atr_period, 14);
        assert_eq!(adaptive.current_position_size, 0.0);
        assert_eq!(adaptive.returns_window.len(), 0);
        assert_eq!(adaptive.current_regime, MarketRegime::Normal);
    }

    #[test]
    fn test_position_multipliers() {
        let _adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

        // Check all regime multipliers are defined
        for (regime, _) in POSITION_MULTIPLIERS.iter() {
            let multiplier = POSITION_MULTIPLIERS
                .iter()
                .find(|(r, _)| r == regime)
                .map(|(_, m)| *m);
            assert!(multiplier.is_some(), "Missing multiplier for {:?}", regime);
        }
    }

    #[test]
    fn test_stoploss_multipliers() {
        let _adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

        // Check all regime multipliers are defined
        for (regime, _) in STOPLOSS_MULTIPLIERS.iter() {
            let multiplier = STOPLOSS_MULTIPLIERS
                .iter()
                .find(|(r, _)| r == regime)
                .map(|(_, m)| *m);
            assert!(multiplier.is_some(), "Missing multiplier for {:?}", regime);
        }
    }

    #[test]
    fn test_feature_221_position_multiplier() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 0.5);

        // Test Normal regime (1.0x)
        let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);
        assert_eq!(
            features[0], 1.0,
            "Normal regime should have 1.0x multiplier"
        );

        // Test Trending regime (1.5x)
        let features = adaptive.update(MarketRegime::Trending, 0.01, 50_000.0, &bars);
        assert_eq!(
            features[0], 1.5,
            "Trending regime should have 1.5x multiplier"
        );

        // Test Crisis regime (0.2x)
        let features = adaptive.update(MarketRegime::Crisis, 0.01, 50_000.0, &bars);
        assert_eq!(
            features[0], 0.2,
            "Crisis regime should have 0.2x multiplier"
        );
    }

    #[test]
    fn test_feature_222_stoploss_multiplier_atr_based() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 1.0);

        // Helper function to compute ATR inline (matches implementation)
        let compute_atr = |bars: &[OHLCVBar], period: usize| -> f64 {
            if bars.len() < period {
                return 0.0;
            }
            let mut true_ranges = Vec::new();
            for i in 1..bars.len().min(period + 1) {
                let tr = (bars[i].high - bars[i].low)
                    .max((bars[i].high - bars[i - 1].close).abs())
                    .max((bars[i].low - bars[i - 1].close).abs());
                true_ranges.push(tr);
            }
            if !true_ranges.is_empty() {
                true_ranges.iter().sum::<f64>() / true_ranges.len() as f64
            } else {
                0.0
            }
        };

        // Test Normal regime (2.0x ATR)
        let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);
        let atr = compute_atr(&bars, 14);
        let expected_stop = 2.0 * atr;
        assert!(
            (features[1] - expected_stop).abs() < 0.01,
            "Normal regime stop-loss should be 2.0x ATR, got {} expected {}",
            features[1],
            expected_stop
        );

        // Test Volatile regime (3.0x ATR)
        let features = adaptive.update(MarketRegime::HighVolatility, 0.01, 50_000.0, &bars);
        let expected_stop = 3.0 * atr;
        assert!(
            (features[1] - expected_stop).abs() < 0.01,
            "Volatile regime stop-loss should be 3.0x ATR, got {} expected {}",
            features[1],
            expected_stop
        );
    }

    #[test]
    fn test_feature_223_regime_conditioned_sharpe() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 0.5);

        // Add varying positive returns (not all identical to ensure non-zero std)
        for i in 0..20 {
            let return_value = 0.01 + (i as f64 * 0.0001); // Vary from 0.01 to 0.0119
            adaptive.update(MarketRegime::Normal, return_value, 50_000.0, &bars);
        }

        let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);
        let sharpe = features[2];

        // With varying positive returns, Sharpe should be positive
        assert!(
            sharpe > 0.0,
            "Sharpe ratio should be positive with consistent gains, got {}",
            sharpe
        );

        // Sharpe calculation: (mean / std) * sqrt(252)
        // With varying returns around 0.01, mean ≈ 0.01, std > 0
        assert!(sharpe.is_finite(), "Sharpe ratio should be finite");
    }

    #[test]
    fn test_feature_224_risk_budget_utilization() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 0.5);

        // Test 50% position in Normal regime (1.0x multiplier)
        let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);
        assert!(
            (features[3] - 0.5).abs() < 0.01,
            "Risk budget should be 0.5 for 50% position, got {}",
            features[3]
        );

        // Test 75% position in Trending regime (1.5x multiplier)
        // Risk budget = 75K / (1.5 * 100K) = 0.5
        let features = adaptive.update(MarketRegime::Trending, 0.01, 75_000.0, &bars);
        assert!(
            (features[3] - 0.5).abs() < 0.01,
            "Risk budget should be 0.5 for 75% position in Trending, got {}",
            features[3]
        );

        // Test full position in Crisis regime (0.2x multiplier)
        // Risk budget = 100K / (0.2 * 100K) = 5.0, clamped to 1.0
        let features = adaptive.update(MarketRegime::Crisis, 0.01, 100_000.0, &bars);
        assert!(
            (features[3] - 1.0).abs() < 0.01,
            "Risk budget should be clamped to 1.0, got {}",
            features[3]
        );
    }

    #[test]
    fn test_regime_transition_resets_returns() {
        let mut adaptive = RegimeAdaptiveFeatures::new(5, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 0.5);

        // Add returns in Normal regime
        for i in 0..5 {
            adaptive.update(MarketRegime::Normal, i as f64 * 0.01, 50_000.0, &bars);
        }
        assert_eq!(adaptive.returns_window.len(), 5);

        // Transition to Trending regime should clear returns
        adaptive.update(MarketRegime::Trending, 0.01, 50_000.0, &bars);
        assert_eq!(
            adaptive.returns_window.len(),
            1,
            "Returns should reset on regime transition"
        );
    }

    #[test]
    fn test_returns_window_capacity() {
        let mut adaptive = RegimeAdaptiveFeatures::new(5, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 0.5);

        // Add 10 returns (should only keep last 5)
        for i in 0..10 {
            adaptive.update(MarketRegime::Normal, i as f64 * 0.01, 0.0, &bars);
        }

        assert_eq!(adaptive.returns_window.len(), 5);
        // Should contain returns 5, 6, 7, 8, 9
        assert_eq!(adaptive.returns_window[0], 0.05);
        assert_eq!(adaptive.returns_window[4], 0.09);
    }

    #[test]
    fn test_get_position_multiplier() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

        // Test different regimes
        adaptive.current_regime = MarketRegime::Normal;
        assert_eq!(adaptive.get_position_multiplier(), 1.0);

        adaptive.current_regime = MarketRegime::Trending;
        assert_eq!(adaptive.get_position_multiplier(), 1.5);

        adaptive.current_regime = MarketRegime::Crisis;
        assert_eq!(adaptive.get_position_multiplier(), 0.2);
    }

    #[test]
    fn test_get_stoploss_multiplier() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);

        // Test different regimes
        adaptive.current_regime = MarketRegime::Normal;
        assert_eq!(adaptive.get_stoploss_multiplier(), 2.0);

        adaptive.current_regime = MarketRegime::Sideways;
        assert_eq!(adaptive.get_stoploss_multiplier(), 1.5);

        adaptive.current_regime = MarketRegime::Crisis;
        assert_eq!(adaptive.get_stoploss_multiplier(), 4.0);
    }

    #[test]
    fn test_all_features_finite() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 0.5);

        // Test all regimes
        for (regime, _) in POSITION_MULTIPLIERS.iter() {
            let features = adaptive.update(*regime, 0.01, 50_000.0, &bars);

            for (i, &feature) in features.iter().enumerate() {
                assert!(
                    feature.is_finite(),
                    "Feature {} should be finite for regime {:?}, got {}",
                    i + 221,
                    regime,
                    feature
                );
            }
        }
    }

    #[test]
    fn test_insufficient_bars_for_atr() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
        let bars = create_test_bars(5, 100.0, 0.5); // Too few bars for ATR

        let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);

        // Feature 222 (stop-loss) should be 0.0 when insufficient bars
        assert_eq!(
            features[1], 0.0,
            "Stop-loss should be 0.0 with insufficient bars for ATR"
        );
    }

    #[test]
    fn test_zero_position_size() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 0.5);

        let features = adaptive.update(MarketRegime::Normal, 0.01, 0.0, &bars);

        // Feature 224 (risk budget) should be 0.0 with zero position
        assert_eq!(
            features[3], 0.0,
            "Risk budget should be 0.0 with zero position"
        );
    }

    #[test]
    fn test_zero_volatility_sharpe() {
        let mut adaptive = RegimeAdaptiveFeatures::new(20, 100_000.0, 14);
        let bars = create_test_bars(20, 100.0, 0.5);

        // Add identical returns (zero volatility)
        for _ in 0..20 {
            adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);
        }

        let features = adaptive.update(MarketRegime::Normal, 0.01, 50_000.0, &bars);

        // Feature 223 (Sharpe) should be 0.0 with zero std dev
        assert_eq!(
            features[2], 0.0,
            "Sharpe should be 0.0 with zero volatility"
        );
    }
}
