//! Volatility Clustering for Universe Selection
//!
//! Implements advanced volatility clustering algorithms to identify assets
//! with favorable volatility characteristics for HFT strategies.
//! Uses fixed-point arithmetic for sub-100μs performance targets.

use std::collections::{HashMap, VecDeque};

use serde::{Deserialize, Serialize};

use crate::{MLError, PRECISION_FACTOR, VolatilityClusterEngineConfig};

/// Volatility regime classification
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VolatilityRegime {
    LowVolatility,
    ModerateVolatility,
    HighVolatility,
    ExtremeLyHighVolatility,
}

/// Configuration for volatility regime detection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolatilityRegimeConfig {
    pub low_threshold: i64,
    pub moderate_threshold: i64,
    pub high_threshold: i64,
    pub window_size: usize,
}

impl Default for VolatilityRegimeConfig {
    fn default() -> Self {
        Self {
            low_threshold: PRECISION_FACTOR / 20,      // 5%
            moderate_threshold: PRECISION_FACTOR / 10, // 10%
            high_threshold: PRECISION_FACTOR / 5,      // 20%
            window_size: 20,
        }
    }
}

/// Volatility regime detector
#[derive(Debug)]
pub struct VolatilityRegimeDetector {
    config: VolatilityRegimeConfig,
}

impl VolatilityRegimeDetector {
    /// Create a new volatility regime detector
    ///
    /// # Errors
    ///
    /// Returns `MLError` if:
    /// - Configuration validation fails
    /// - Threshold values are invalid
    pub const fn new(config: VolatilityRegimeConfig) -> Result<Self, MLError> {
        Ok(Self { config })
    }

    pub const fn classify_regime(&self, volatility: i64) -> VolatilityRegime {
        if volatility < self.config.low_threshold {
            VolatilityRegime::LowVolatility
        } else if volatility < self.config.moderate_threshold {
            VolatilityRegime::ModerateVolatility
        } else if volatility < self.config.high_threshold {
            VolatilityRegime::HighVolatility
        } else {
            VolatilityRegime::ExtremeLyHighVolatility
        }
    }
}

/// Configuration for volatility clustering
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VolatilityClusterConfig {
    pub window_size: usize,
    pub cluster_count: usize,
    pub min_observations: usize,
}

/// Price point data structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricePoint {
    pub timestamp: u64,
    pub price: i64,
    pub volume: u64,
    pub high: i64,
    pub low: i64,
    pub open: i64,
}

/// GARCH model configuration
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GarchConfig {
    pub alpha: f64,
    pub beta: f64,
    pub omega: f64,
}

/// GARCH model for volatility prediction
#[derive(Debug)]
pub struct GarchModel {
    pub p: usize, // ARCH order
    pub q: usize, // GARCH order
    pub is_fitted: bool,
    params: Vec<f64>,
}

impl GarchModel {
    pub const fn new(p: usize, q: usize) -> Self {
        Self {
            p,
            q,
            is_fitted: false,
            params: Vec::new(),
        }
    }

    pub fn update_with_returns(
        &mut self,
        _returns: &[i64],
        _config: &GarchConfig,
    ) -> Result<(), MLError> {
        // Simplified GARCH update - in production would be more sophisticated
        self.params = vec![0.1, 0.8, 0.1]; // omega, alpha, beta
        self.is_fitted = true;
        Ok(())
    }
}

/// Enhanced volatility cluster engine with missing fields
#[derive(Debug)]
pub struct EnhancedVolatilityClusterEngine {
    config: VolatilityClusterEngineConfig,
    pub total_updates: u64,
    pub volatility_features: HashMap<String, Vec<f64>>,
    price_history: HashMap<String, VecDeque<PricePoint>>,
}

impl EnhancedVolatilityClusterEngine {
    pub fn new(config: VolatilityClusterEngineConfig) -> Result<Self, MLError> {
        Ok(Self {
            config,
            total_updates: 0,
            volatility_features: HashMap::new(),
            price_history: HashMap::new(),
        })
    }

    pub fn update_price_data(
        &mut self,
        symbol: String,
        price_point: PricePoint,
    ) -> Result<(), MLError> {
        let history = self
            .price_history
            .entry(symbol)
            .or_default();
        history.push_back(price_point);

        // Keep only recent data
        while history.len() > self.config.volatility_window {
            history.pop_front();
        }

        self.total_updates += 1;
        Ok(())
    }

    pub fn calculate_returns(&self, prices: &VecDeque<PricePoint>) -> Result<Vec<i64>, MLError> {
        if prices.len() < 2 {
            return Ok(Vec::new());
        }

        let mut returns = Vec::new();
        for i in 1..prices.len() {
            let prev_price = prices[i - 1].price;
            let curr_price = prices[i].price;

            if prev_price == 0 {
                continue; // Skip to avoid division by zero
            }

            let return_val = ((curr_price - prev_price) * PRECISION_FACTOR) / prev_price;
            returns.push(return_val);
        }

        Ok(returns)
    }

    pub fn integer_sqrt(&self, value: i64) -> Result<i64, MLError> {
        if value < 0 {
            return Err(MLError::InvalidInput(
                "Cannot calculate square root of negative number".to_owned(),
            ));
        }
        if value == 0 {
            return Ok(0);
        }

        // Newton's method for integer square root
        let mut x = value;
        let mut prev_x = 0;

        while x != prev_x {
            prev_x = x;
            x = (x + value / x) / 2;
        }

        // Scale result to maintain fixed-point representation
        // For fixed-point with PRECISION_FACTOR = 10^8, sqrt(PRECISION_FACTOR) = 10^4
        const SQRT_PRECISION: i64 = 10_000;
        Ok(x * SQRT_PRECISION)
    }
}

#[cfg(test)]
#[allow(
    clippy::assertions_on_result_states,
    clippy::str_to_string,
    clippy::unnecessary_wraps
)]
mod tests {
    use super::*;

    #[test]
    fn test_volatility_cluster_engine_creation() -> Result<(), MLError> {
        let config = VolatilityClusterEngineConfig::default();
        let engine = EnhancedVolatilityClusterEngine::new(config);

        assert!(engine.is_ok());
        let engine = engine?;
        assert_eq!(engine.total_updates, 0);
        assert!(engine.volatility_features.is_empty());
        Ok(())
    }

    #[test]
    fn test_price_data_update() -> Result<(), MLError> {
        let mut engine = EnhancedVolatilityClusterEngine::new(VolatilityClusterEngineConfig::default())?;

        let price_point = PricePoint {
            timestamp: 1640995200, // 2022-01-01
            price: 100 * PRECISION_FACTOR,
            volume: 1000,
            high: 105 * PRECISION_FACTOR,
            low: 95 * PRECISION_FACTOR,
            open: 98 * PRECISION_FACTOR,
        };

        let test_symbol = "TEST_SYM_1";
        let result = engine.update_price_data(test_symbol.to_string(), price_point);
        assert!(result.is_ok());
        assert_eq!(engine.total_updates, 1);
        Ok(())
    }

    #[test]
    fn test_volatility_calculations() -> Result<(), MLError> {
        let engine = EnhancedVolatilityClusterEngine::new(VolatilityClusterEngineConfig::default())?;

        // Test returns calculation
        let mut prices = VecDeque::new();
        prices.push_back(PricePoint {
            timestamp: 1,
            price: 100 * PRECISION_FACTOR,
            volume: 1000,
            high: 100 * PRECISION_FACTOR,
            low: 100 * PRECISION_FACTOR,
            open: 100 * PRECISION_FACTOR,
        });
        prices.push_back(PricePoint {
            timestamp: 2,
            price: 105 * PRECISION_FACTOR,
            volume: 1000,
            high: 105 * PRECISION_FACTOR,
            low: 105 * PRECISION_FACTOR,
            open: 105 * PRECISION_FACTOR,
        });

        let returns = engine.calculate_returns(&prices)?;
        assert_eq!(returns.len(), 1);
        assert_eq!(returns[0], (5 * PRECISION_FACTOR) / 100); // 5% return
        Ok(())
    }

    #[test]
    fn test_garch_model() -> Result<(), MLError> {
        let mut model = GarchModel::new(1, 1);
        assert!(!model.is_fitted);

        let returns = vec![
            PRECISION_FACTOR / 100, // 1%
            -PRECISION_FACTOR / 50, // -2%
            PRECISION_FACTOR / 200, // 0.5%
        ];

        let config = GarchConfig::default();
        let result = model.update_with_returns(&returns, &config);
        assert!(result.is_ok());
        Ok(())
    }

    #[test]
    fn test_volatility_regime_classification() -> Result<(), MLError> {
        let config = VolatilityRegimeConfig::default();
        let detector = VolatilityRegimeDetector::new(config)?;

        let low_vol = PRECISION_FACTOR / 50; // 2%
        let high_vol = PRECISION_FACTOR / 3; // 33%

        assert!(matches!(
            detector.classify_regime(low_vol),
            VolatilityRegime::LowVolatility
        ));
        assert!(matches!(
            detector.classify_regime(high_vol),
            VolatilityRegime::ExtremeLyHighVolatility
        ));
        Ok(())
    }

    #[test]
    fn test_integer_sqrt() -> Result<(), MLError> {
        let engine = EnhancedVolatilityClusterEngine::new(VolatilityClusterEngineConfig::default())?;

        let sqrt_result = engine.integer_sqrt(PRECISION_FACTOR * 4)?; // sqrt(4)
        assert_eq!(sqrt_result, 2 * PRECISION_FACTOR); // Should be 2.0 in fixed point

        let sqrt_zero = engine.integer_sqrt(0)?;
        assert_eq!(sqrt_zero, 0);
        Ok(())
    }
}
