use crate::regime::cusum::{CUSUMDetector, StructuralBreak};
use std::collections::VecDeque;

/// Extracts CUSUM-based regime detection features for ML models.
///
/// This feature extractor maintains a sliding window of structural breaks
/// detected by the CUSUM algorithm and computes 10 statistical features
/// that capture regime change characteristics.
///
/// # Feature Indices (201-210)
/// - 201: S+ Normalized - Positive CUSUM sum normalized by threshold, clamped [0.0, 1.5]
/// - 202: S- Normalized - Negative CUSUM sum normalized by threshold, clamped [0.0, 1.5]
/// - 203: Break Indicator - 1.0 if break in last update, else 0.0
/// - 204: Direction - 1.0 if positive break, -1.0 if negative, 0.0 if no break
/// - 205: Time Since Break - bars elapsed since last break (capped at 100)
/// - 206: Frequency - (breaks_window.len() / window_size) * 100.0
/// - 207: Positive Break Count - count PositiveMeanShift in window
/// - 208: Negative Break Count - count NegativeMeanShift in window
/// - 209: Intensity - abs(S+ - S-) / threshold
/// - 210: Drift Ratio - drift_allowance / threshold
#[derive(Debug)]
pub struct RegimeCUSUMFeatures {
    detector: CUSUMDetector,
    breaks_window: VecDeque<StructuralBreak>,
    window_size: usize,
    bar_count: usize,
    last_break_bar: Option<usize>,
    last_break_result: Option<StructuralBreak>,
}

impl RegimeCUSUMFeatures {
    /// Creates a new RegimeCUSUMFeatures extractor.
    ///
    /// # Arguments
    /// * `target_mean` - Expected mean of the process under H0 (no change)
    /// * `target_std` - Expected standard deviation under H0
    /// * `drift_allowance` - Minimum drift to trigger detection (in std units)
    /// * `threshold` - CUSUM threshold for break detection (typically 3-5)
    ///
    /// # Example
    /// ```rust,ignore
    /// let features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);
    /// ```
    pub fn new(target_mean: f64, target_std: f64, drift_allowance: f64, threshold: f64) -> Self {
        Self {
            detector: CUSUMDetector::new(target_mean, target_std, drift_allowance, threshold),
            breaks_window: VecDeque::with_capacity(100),
            window_size: 100,
            bar_count: 0,
            last_break_bar: None,
            last_break_result: None,
        }
    }

    /// Updates the feature extractor with a new observation and returns 10 features.
    ///
    /// # Arguments
    /// * `value` - The new observation (e.g., log return, price change)
    ///
    /// # Returns
    /// Array of 10 features capturing CUSUM regime characteristics:
    /// - `[0]`: S+ Normalized (0.0 to 1.5)
    /// - `[1]`: S- Normalized (0.0 to 1.5)
    /// - `[2]`: Break Indicator (0.0 or 1.0)
    /// - `[3]`: Direction (-1.0, 0.0, or 1.0)
    /// - `[4]`: Time Since Break (0 to 100)
    /// - `[5]`: Frequency (0.0 to 100.0)
    /// - `[6]`: Positive Break Count
    /// - `[7]`: Negative Break Count
    /// - `[8]`: Intensity (0.0 to ~2.0)
    /// - `[9]`: Drift Ratio
    ///
    /// # Performance
    /// Target: <50μs per bar
    pub fn update(&mut self, value: f64) -> [f64; 10] {
        // Update CUSUM detector and check for breaks
        let break_result = self.detector.update(value);
        self.bar_count += 1;

        // Track break in window and update last break info
        if let Some(ref structural_break) = break_result {
            self.breaks_window.push_back(structural_break.clone());
            if self.breaks_window.len() > self.window_size {
                self.breaks_window.pop_front();
            }
            self.last_break_bar = Some(self.bar_count);
            self.last_break_result = Some(structural_break.clone());

            // Reset CUSUM detector after break detection (standard practice)
            self.detector.reset();
        }

        // Get detector parameters
        let threshold = self.detector.detection_threshold();
        let drift_allowance = self.detector.drift_allowance();

        // Feature 201: S+ Normalized (clamped to [0.0, 1.5], safe division)
        let s_plus_normalized = if threshold > 1e-10 {
            (self.detector.positive_sum() / threshold).clamp(0.0, 1.5)
        } else {
            0.0 // Zero threshold disables detection, return neutral value
        };

        // Feature 202: S- Normalized (clamped to [0.0, 1.5], safe division)
        let s_minus_normalized = if threshold > 1e-10 {
            (self.detector.negative_sum() / threshold).clamp(0.0, 1.5)
        } else {
            0.0 // Zero threshold disables detection, return neutral value
        };

        // Feature 203: Break Indicator (1.0 if break just occurred, else 0.0)
        let break_indicator = if break_result.is_some() { 1.0 } else { 0.0 };

        // Feature 204: Direction (1.0 positive, -1.0 negative, 0.0 no break)
        let direction = match &break_result {
            Some(sb) if sb.direction == "positive" => 1.0,
            Some(sb) if sb.direction == "negative" => -1.0,
            _ => 0.0,
        };

        // Feature 205: Time Since Break (bars elapsed, capped at 100)
        let time_since_break = match self.last_break_bar {
            Some(last_bar) => ((self.bar_count - last_bar) as f64).min(100.0),
            None => 100.0, // No break yet, maximum value
        };

        // Feature 206: Frequency (breaks per 100 bars)
        let frequency = (self.breaks_window.len() as f64 / self.window_size as f64) * 100.0;

        // Feature 207: Positive Break Count
        let positive_break_count = self
            .breaks_window
            .iter()
            .filter(|sb| sb.direction == "positive")
            .count() as f64;

        // Feature 208: Negative Break Count
        let negative_break_count = self
            .breaks_window
            .iter()
            .filter(|sb| sb.direction == "negative")
            .count() as f64;

        // Feature 209: Intensity (abs difference of CUSUM sums normalized by threshold, safe division)
        let intensity = if threshold > 1e-10 {
            (self.detector.positive_sum() - self.detector.negative_sum()).abs() / threshold
        } else {
            0.0 // Zero threshold disables detection
        };

        // Feature 210: Drift Ratio (safe division)
        let drift_ratio = if threshold > 1e-10 {
            drift_allowance / threshold
        } else {
            0.0 // Zero threshold disables detection
        };

        [
            s_plus_normalized,    // 201
            s_minus_normalized,   // 202
            break_indicator,      // 203
            direction,            // 204
            time_since_break,     // 205
            frequency,            // 206
            positive_break_count, // 207
            negative_break_count, // 208
            intensity,            // 209
            drift_ratio,          // 210
        ]
    }
}

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

    #[test]
    fn test_regime_cusum_features_new() {
        let features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);
        assert_eq!(features.bar_count, 0);
        assert_eq!(features.window_size, 100);
        assert_eq!(features.breaks_window.len(), 0);
        assert!(features.last_break_bar.is_none());
        assert!(features.last_break_result.is_none());
    }

    #[test]
    fn test_regime_cusum_features_no_break() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);
        let result = features.update(0.1); // Small value, no break

        // Verify return type is correct
        assert_eq!(result.len(), 10);

        // Verify bar counter increments
        assert_eq!(features.bar_count, 1);

        // Feature 203: Break Indicator should be 0.0 (no break)
        assert_relative_eq!(result[2], 0.0);

        // Feature 204: Direction should be 0.0 (no break)
        assert_relative_eq!(result[3], 0.0);

        // Feature 205: Time Since Break should be 100.0 (no break yet)
        assert_relative_eq!(result[4], 100.0);

        // Feature 206: Frequency should be 0.0 (no breaks in window)
        assert_relative_eq!(result[5], 0.0);

        // Feature 209: Drift Ratio (0.5 / 4.0 = 0.125)
        assert_relative_eq!(result[9], 0.125);
    }

    #[test]
    fn test_regime_cusum_features_positive_break() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 3.0);

        // Feed large positive values to trigger a positive break
        for _ in 0..10 {
            let result = features.update(2.0);

            // Check if break was detected
            if result[2] == 1.0 {
                // Feature 203: Break Indicator should be 1.0
                assert_relative_eq!(result[2], 1.0);

                // Feature 204: Direction should be 1.0 (positive)
                assert_relative_eq!(result[3], 1.0);

                // Feature 205: Time Since Break should be 0.0 (just happened)
                assert_relative_eq!(result[4], 0.0);

                // Feature 207: Positive Break Count should be 1.0
                assert_relative_eq!(result[6], 1.0);

                // Feature 208: Negative Break Count should be 0.0
                assert_relative_eq!(result[7], 0.0);

                break;
            }
        }

        assert!(features.last_break_bar.is_some());
        assert!(features.last_break_result.is_some());
    }

    #[test]
    fn test_regime_cusum_features_negative_break() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 3.0);

        // Feed large negative values to trigger a negative break
        for _ in 0..10 {
            let result = features.update(-2.0);

            // Check if break was detected
            if result[2] == 1.0 {
                // Feature 203: Break Indicator should be 1.0
                assert_relative_eq!(result[2], 1.0);

                // Feature 204: Direction should be -1.0 (negative)
                assert_relative_eq!(result[3], -1.0);

                // Feature 205: Time Since Break should be 0.0 (just happened)
                assert_relative_eq!(result[4], 0.0);

                // Feature 207: Positive Break Count should be 0.0
                assert_relative_eq!(result[6], 0.0);

                // Feature 208: Negative Break Count should be 1.0
                assert_relative_eq!(result[7], 1.0);

                break;
            }
        }
    }

    #[test]
    fn test_regime_cusum_features_time_since_break() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 3.0);

        // Trigger a break
        for _ in 0..10 {
            let result = features.update(2.0);
            if result[2] == 1.0 {
                break;
            }
        }

        // Now feed normal values and check time since break increases
        for i in 1..=5 {
            let result = features.update(0.1);

            // Feature 205: Time Since Break should increment
            assert_relative_eq!(result[4], i as f64);
        }
    }

    #[test]
    fn test_regime_cusum_features_frequency() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 3.0);
        let mut break_count = 0;

        // Trigger multiple breaks by alternating large positive and negative values
        for i in 0..50 {
            let value = if i % 20 < 10 { 2.0 } else { -2.0 };
            let result = features.update(value);

            if result[2] == 1.0 {
                break_count += 1;
            }
        }

        // Feed one more value to check frequency
        let result = features.update(0.1);

        // Feature 206: Frequency should reflect breaks in window
        let expected_frequency =
            (features.breaks_window.len() as f64 / features.window_size as f64) * 100.0;
        assert_relative_eq!(result[5], expected_frequency);
        assert!(break_count > 0, "Expected at least one break");
    }

    #[test]
    fn test_regime_cusum_features_normalized_sums() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

        // Feed positive values
        let result = features.update(1.0);

        // Feature 201: S+ Normalized should be > 0 and <= 1.5
        assert!(result[0] >= 0.0 && result[0] <= 1.5);

        // Feature 202: S- Normalized should be 0.0 (no negative accumulation)
        assert_relative_eq!(result[1], 0.0, epsilon = 1e-10);
    }

    #[test]
    fn test_regime_cusum_features_intensity() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

        // Feed positive values
        let result = features.update(1.5);

        // Feature 209: Intensity should be positive (S+ > S-)
        assert!(result[8] >= 0.0);
    }

    #[test]
    fn test_regime_cusum_features_drift_ratio() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 4.0);

        let result = features.update(0.5);

        // Feature 210: Drift Ratio (0.5 / 4.0 = 0.125)
        assert_relative_eq!(result[9], 0.125);
    }

    #[test]
    fn test_regime_cusum_features_window_overflow() {
        let mut features = RegimeCUSUMFeatures::new(0.0, 1.0, 0.5, 2.0);

        // Trigger many breaks to overflow the window (size=100)
        for i in 0..150 {
            let value = if i % 10 < 5 { 2.0 } else { -2.0 };
            features.update(value);
        }

        // Window should not exceed capacity
        assert!(features.breaks_window.len() <= features.window_size);
    }
}
