//! Technical Indicator Features
//!
//! Dual-API implementations of core technical indicators:
//! - RSI (Relative Strength Index)
//! - EMA (Exponential Moving Average)
//! - MACD (Moving Average Convergence Divergence)
//! - Bollinger Bands
//! - ATR (Average True Range)
//! - ADX (Average Directional Index)
//!
//! Each indicator provides:
//! 1. Streaming API: Stateful struct with update() method (O(1) per update)
//! 2. Batch API: Process vector of prices (convenience wrapper)

use std::collections::VecDeque;

// ===== RSI (Relative Strength Index) =====

/// Relative Strength Index calculator (streaming API)
#[derive(Debug, Clone)]
pub struct RSI {
    period: usize,
    gains: VecDeque<f64>,
    losses: VecDeque<f64>,
    prev_close: Option<f64>,
}

impl RSI {
    /// Create a new RSI calculator
    ///
    /// ## Arguments
    /// - `period`: RSI period (typical: 14)
    pub fn new(period: usize) -> Self {
        Self {
            period,
            gains: VecDeque::with_capacity(period),
            losses: VecDeque::with_capacity(period),
            prev_close: None,
        }
    }

    /// Update RSI with a new price
    ///
    /// ## Returns
    /// Current RSI value (0-100 scale)
    pub fn update(&mut self, price: f64) -> f64 {
        if let Some(prev) = self.prev_close {
            let change = price - prev;
            let gain = if change > 0.0 { change } else { 0.0 };
            let loss = if change < 0.0 { -change } else { 0.0 };

            self.gains.push_back(gain);
            self.losses.push_back(loss);
            if self.gains.len() > self.period {
                self.gains.pop_front();
                self.losses.pop_front();
            }

            if self.gains.len() == self.period {
                let avg_gain: f64 = self.gains.iter().sum::<f64>() / self.period as f64;
                let avg_loss: f64 = self.losses.iter().sum::<f64>() / self.period as f64;
                if avg_loss > 0.0 {
                    let rs = avg_gain / avg_loss;
                    self.prev_close = Some(price);
                    return 100.0 - (100.0 / (1.0 + rs));
                }
            }
        }
        self.prev_close = Some(price);
        50.0 // Default neutral RSI
    }
}

/// RSI batch API: Calculate RSI for a vector of prices
pub fn rsi_batch(prices: &[f64], period: usize) -> Vec<f64> {
    let mut rsi = RSI::new(period);
    prices.iter().map(|&p| rsi.update(p)).collect()
}

// ===== EMA (Exponential Moving Average) =====

/// Exponential Moving Average calculator (streaming API)
#[derive(Debug, Clone)]
pub struct EMA {
    _period: usize,
    multiplier: f64,
    ema: Option<f64>,
}

impl EMA {
    /// Create a new EMA calculator
    ///
    /// ## Arguments
    /// - `period`: EMA period (typical: 12, 26)
    pub fn new(period: usize) -> Self {
        Self {
            _period: period,
            multiplier: 2.0 / (period as f64 + 1.0),
            ema: None,
        }
    }

    /// Update EMA with a new price
    ///
    /// ## Returns
    /// Current EMA value
    pub fn update(&mut self, price: f64) -> f64 {
        match self.ema {
            None => {
                self.ema = Some(price);
                price
            }
            Some(prev_ema) => {
                let new_ema = price * self.multiplier + prev_ema * (1.0 - self.multiplier);
                self.ema = Some(new_ema);
                new_ema
            }
        }
    }
}

/// EMA batch API: Calculate EMA for a vector of prices
pub fn ema_batch(prices: &[f64], period: usize) -> Vec<f64> {
    let mut ema = EMA::new(period);
    prices.iter().map(|&p| ema.update(p)).collect()
}

// ===== MACD (Moving Average Convergence Divergence) =====

/// MACD calculator (streaming API)
#[derive(Debug, Clone)]
pub struct MACD {
    ema_fast: EMA,
    ema_slow: EMA,
    signal: EMA,
}

impl MACD {
    /// Create a new MACD calculator
    ///
    /// ## Arguments
    /// - `fast_period`: Fast EMA period (typical: 12)
    /// - `slow_period`: Slow EMA period (typical: 26)
    /// - `signal_period`: Signal line period (typical: 9)
    pub fn new(fast_period: usize, slow_period: usize, signal_period: usize) -> Self {
        Self {
            ema_fast: EMA::new(fast_period),
            ema_slow: EMA::new(slow_period),
            signal: EMA::new(signal_period),
        }
    }

    /// Update MACD with a new price
    ///
    /// ## Returns
    /// Tuple of (MACD line, Signal line, Histogram)
    pub fn update(&mut self, price: f64) -> (f64, f64, f64) {
        let fast = self.ema_fast.update(price);
        let slow = self.ema_slow.update(price);
        let macd_line = fast - slow;
        let signal_line = self.signal.update(macd_line);
        let histogram = macd_line - signal_line;
        (macd_line, signal_line, histogram)
    }
}

/// MACD batch API: Calculate MACD for a vector of prices
pub fn macd_batch(
    prices: &[f64],
    fast_period: usize,
    slow_period: usize,
    signal_period: usize,
) -> Vec<(f64, f64, f64)> {
    let mut macd = MACD::new(fast_period, slow_period, signal_period);
    prices.iter().map(|&p| macd.update(p)).collect()
}

// ===== Bollinger Bands =====

/// Bollinger Bands calculator (streaming API)
#[derive(Debug, Clone)]
pub struct BollingerBands {
    period: usize,
    std_multiplier: f64,
    prices: VecDeque<f64>,
}

impl BollingerBands {
    /// Create a new Bollinger Bands calculator
    ///
    /// ## Arguments
    /// - `period`: Period for SMA and standard deviation (typical: 20)
    /// - `std_multiplier`: Number of standard deviations (typical: 2.0)
    pub fn new(period: usize, std_multiplier: f64) -> Self {
        Self {
            period,
            std_multiplier,
            prices: VecDeque::with_capacity(period),
        }
    }

    /// Update Bollinger Bands with a new price
    ///
    /// ## Returns
    /// Tuple of (Middle band, Upper band, Lower band)
    pub fn update(&mut self, price: f64) -> (f64, f64, f64) {
        self.prices.push_back(price);
        if self.prices.len() > self.period {
            self.prices.pop_front();
        }

        if self.prices.len() == self.period {
            let sum: f64 = self.prices.iter().sum();
            let middle = sum / self.period as f64;
            let variance: f64 = self
                .prices
                .iter()
                .map(|p| (p - middle).powi(2))
                .sum::<f64>()
                / self.period as f64;
            let std = variance.sqrt();
            let upper = middle + self.std_multiplier * std;
            let lower = middle - self.std_multiplier * std;
            (middle, upper, lower)
        } else {
            (price, price, price)
        }
    }
}

/// Bollinger Bands batch API
pub fn bollinger_batch(prices: &[f64], period: usize, std_multiplier: f64) -> Vec<(f64, f64, f64)> {
    let mut bb = BollingerBands::new(period, std_multiplier);
    prices.iter().map(|&p| bb.update(p)).collect()
}

// ===== ATR (Average True Range) =====

/// ATR calculator (streaming API)
#[derive(Debug, Clone)]
pub struct ATR {
    period: usize,
    true_ranges: VecDeque<f64>,
    prev_close: Option<f64>,
}

impl ATR {
    /// Create a new ATR calculator
    ///
    /// ## Arguments
    /// - `period`: ATR period (typical: 14)
    pub fn new(period: usize) -> Self {
        Self {
            period,
            true_ranges: VecDeque::with_capacity(period),
            prev_close: None,
        }
    }

    /// Update ATR with a new OHLC bar
    ///
    /// ## Arguments
    /// - `high`: High price
    /// - `low`: Low price
    /// - `close`: Close price
    ///
    /// ## Returns
    /// Current ATR value
    pub fn update(&mut self, high: f64, low: f64, close: f64) -> f64 {
        if let Some(prev) = self.prev_close {
            let tr = (high - low)
                .max((high - prev).abs())
                .max((low - prev).abs());
            self.true_ranges.push_back(tr);
            if self.true_ranges.len() > self.period {
                self.true_ranges.pop_front();
            }
            if self.true_ranges.len() == self.period {
                self.prev_close = Some(close);
                return self.true_ranges.iter().sum::<f64>() / self.period as f64;
            }
        }
        self.prev_close = Some(close);
        high - low // Fallback to simple range
    }
}

/// ATR batch API: Calculate ATR for a vector of (high, low, close) tuples
pub fn atr_batch(bars: &[(f64, f64, f64)], period: usize) -> Vec<f64> {
    let mut atr = ATR::new(period);
    bars.iter()
        .map(|&(high, low, close)| atr.update(high, low, close))
        .collect()
}

// ===== ADX (Average Directional Index) =====

/// ADX calculator (streaming API)
///
/// Based on ml/src/regime/trending.rs and ml/src/features/regime_adx.rs
#[derive(Debug, Clone)]
pub struct ADX {
    _period: usize,
    _alpha: f64,
    alpha_wilder: f64,
    atr: Option<f64>,
    plus_dm_smooth: Option<f64>,
    minus_dm_smooth: Option<f64>,
    adx: Option<f64>,
    prev_high: Option<f64>,
    prev_low: Option<f64>,
    prev_close: Option<f64>,
    bar_count: usize,
}

impl ADX {
    /// Create a new ADX calculator
    ///
    /// ## Arguments
    /// - `period`: ADX period (typical: 14)
    pub fn new(period: usize) -> Self {
        Self {
            _period: period,
            _alpha: 1.0 / period as f64,
            alpha_wilder: 1.0 / period as f64,
            atr: None,
            plus_dm_smooth: None,
            minus_dm_smooth: None,
            adx: None,
            prev_high: None,
            prev_low: None,
            prev_close: None,
            bar_count: 0,
        }
    }

    /// Update ADX with a new OHLC bar
    ///
    /// ## Arguments
    /// - `high`: High price
    /// - `low`: Low price
    /// - `close`: Close price
    ///
    /// ## Returns
    /// Tuple of (ADX, +DI, -DI)
    pub fn update(&mut self, high: f64, low: f64, close: f64) -> (f64, f64, f64) {
        self.bar_count += 1;

        // Need at least 2 bars for calculation
        let (Some(prev_high), Some(prev_low), Some(prev_close)) =
            (self.prev_high, self.prev_low, self.prev_close)
        else {
            self.prev_high = Some(high);
            self.prev_low = Some(low);
            self.prev_close = Some(close);
            return (0.0, 0.0, 0.0);
        };

        // 1. Calculate True Range
        let tr = (high - low)
            .max((high - prev_close).abs())
            .max((low - prev_close).abs());

        // 2. Calculate Directional Movement
        let plus_dm = if high > prev_high {
            (high - prev_high).max(0.0)
        } else {
            0.0
        };
        let minus_dm = if low < prev_low {
            (prev_low - low).max(0.0)
        } else {
            0.0
        };

        // 3. Smooth using Wilder's method
        self.atr = Some(match self.atr {
            None => tr,
            Some(prev_atr) => prev_atr * (1.0 - self.alpha_wilder) + tr * self.alpha_wilder,
        });

        self.plus_dm_smooth = Some(match self.plus_dm_smooth {
            None => plus_dm,
            Some(prev) => prev * (1.0 - self.alpha_wilder) + plus_dm * self.alpha_wilder,
        });

        self.minus_dm_smooth = Some(match self.minus_dm_smooth {
            None => minus_dm,
            Some(prev) => prev * (1.0 - self.alpha_wilder) + minus_dm * self.alpha_wilder,
        });

        // 4. Calculate +DI and -DI
        let atr_val = self.atr.unwrap_or(1.0).max(1e-8);
        let plus_di = 100.0 * self.plus_dm_smooth.unwrap_or(0.0) / atr_val;
        let minus_di = 100.0 * self.minus_dm_smooth.unwrap_or(0.0) / atr_val;

        // 5. Calculate DX
        let di_sum = plus_di + minus_di;
        let dx = if di_sum > 1e-8 {
            100.0 * (plus_di - minus_di).abs() / di_sum
        } else {
            0.0
        };

        // 6. Smooth DX to get ADX
        self.adx = Some(match self.adx {
            None => dx,
            Some(prev_adx) => prev_adx * (1.0 - self.alpha_wilder) + dx * self.alpha_wilder,
        });

        // Update state
        self.prev_high = Some(high);
        self.prev_low = Some(low);
        self.prev_close = Some(close);

        let adx_val = self.adx.unwrap_or(0.0).clamp(0.0, 100.0);
        (adx_val, plus_di.clamp(0.0, 100.0), minus_di.clamp(0.0, 100.0))
    }
}

/// ADX batch API: Calculate ADX for a vector of (high, low, close) tuples
pub fn adx_batch(bars: &[(f64, f64, f64)], period: usize) -> Vec<(f64, f64, f64)> {
    let mut adx = ADX::new(period);
    bars.iter()
        .map(|&(high, low, close)| adx.update(high, low, close))
        .collect()
}

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

    #[test]
    fn test_rsi() {
        let prices = vec![
            44.0, 44.25, 44.5, 43.75, 44.0, 44.5, 44.75, 44.25, 44.0, 43.5, 43.75, 44.0, 44.5,
            45.0, 45.5,
        ];
        let rsi_values = rsi_batch(&prices, 14);
        assert_eq!(rsi_values.len(), 15);
        // RSI should be between 0 and 100
        for &rsi in &rsi_values {
            assert!((0.0..=100.0).contains(&rsi));
        }
    }

    #[test]
    fn test_ema() {
        let prices = vec![10.0, 11.0, 12.0, 11.5, 12.5, 13.0];
        let ema_values = ema_batch(&prices, 3);
        assert_eq!(ema_values.len(), 6);
        // EMA should track prices
        assert!(ema_values[5] > 10.0);
    }

    #[test]
    fn test_macd() {
        let prices = vec![100.0; 30];
        let macd_values = macd_batch(&prices, 12, 26, 9);
        assert_eq!(macd_values.len(), 30);
        // For constant prices, MACD should approach 0
        let (macd, signal, hist) = macd_values[29];
        assert!(macd.abs() < 1.0);
        assert!(signal.abs() < 1.0);
        assert!(hist.abs() < 1.0);
    }

    #[test]
    fn test_bollinger_bands() {
        let prices = vec![100.0; 25];
        let bb_values = bollinger_batch(&prices, 20, 2.0);
        assert_eq!(bb_values.len(), 25);
        // For constant prices, all bands should be equal
        let (middle, upper, lower) = bb_values[24];
        assert!((middle - 100.0).abs() < 1e-6);
        assert!((upper - middle).abs() < 1e-6);
        assert!((lower - middle).abs() < 1e-6);
    }

    #[test]
    fn test_atr() {
        let bars = vec![
            (102.0, 98.0, 100.0),
            (103.0, 99.0, 101.0),
            (104.0, 100.0, 102.0),
        ];
        let atr_values = atr_batch(&bars, 2);
        assert_eq!(atr_values.len(), 3);
        // ATR should be positive
        for &atr in &atr_values {
            assert!(atr > 0.0);
        }
    }

    #[test]
    fn test_adx() {
        let bars = vec![
            (102.0, 98.0, 100.0),
            (103.0, 99.0, 101.0),
            (104.0, 100.0, 102.0),
            (105.0, 101.0, 103.0),
            (106.0, 102.0, 104.0),
        ];
        let adx_values = adx_batch(&bars, 3);
        assert_eq!(adx_values.len(), 5);
        // ADX and DI values should be in [0, 100]
        for &(adx, plus_di, minus_di) in &adx_values {
            assert!((0.0..=100.0).contains(&adx));
            assert!((0.0..=100.0).contains(&plus_di));
            assert!((0.0..=100.0).contains(&minus_di));
        }
    }
}
