use super::{Indicator, IncrementalIndicator, IndicatorResult, IndicatorError, PriceData};
use super::sma::SMA;
use super::common::RollingWindow;

/// Bollinger Bands Indicator
/// 
/// Middle Band = SMA(n)
/// Upper Band = SMA(n) + (k × σ)
/// Lower Band = SMA(n) - (k × σ)
/// where σ is the standard deviation and k is typically 2
pub struct BollingerBands {
    period: usize,
    std_dev_multiplier: f64,
    sma: SMA,
    window: RollingWindow<f64>,
}

impl BollingerBands {
    pub fn new(period: usize, std_dev_multiplier: f64) -> Result<Self, IndicatorError> {
        if period == 0 {
            return Err(IndicatorError::InvalidParameter(
                "Period must be greater than 0".to_string()
            ));
        }
        
        if std_dev_multiplier <= 0.0 {
            return Err(IndicatorError::InvalidParameter(
                "Standard deviation multiplier must be positive".to_string()
            ));
        }
        
        Ok(Self {
            period,
            std_dev_multiplier,
            sma: SMA::new(period)?,
            window: RollingWindow::new(period),
        })
    }
    
    /// Standard Bollinger Bands with 20 period and 2 standard deviations
    pub fn standard() -> Result<Self, IndicatorError> {
        Self::new(20, 2.0)
    }
    
    /// Calculate standard deviation
    fn calculate_std_dev(values: &[f64], mean: f64) -> f64 {
        if values.is_empty() {
            return 0.0;
        }
        
        let variance = values.iter()
            .map(|x| (*x - mean).powi(2))
            .sum::<f64>() / values.len() as f64;
        
        variance.sqrt()
    }
    
    /// Calculate Bollinger Bands for a series of values
    pub fn calculate_series(
        values: &[f64], 
        period: usize, 
        std_dev_multiplier: f64
    ) -> Result<(Vec<f64>, Vec<f64>, Vec<f64>), IndicatorError> {
        if period == 0 {
            return Err(IndicatorError::InvalidParameter(
                "Period must be greater than 0".to_string()
            ));
        }
        
        if values.len() < period {
            return Err(IndicatorError::InsufficientData {
                required: period,
                actual: values.len(),
            });
        }
        
        // Calculate SMA (middle band)
        let middle_band = SMA::calculate_series(values, period)?;
        let mut upper_band = Vec::with_capacity(middle_band.len());
        let mut lower_band = Vec::with_capacity(middle_band.len());
        
        // Calculate bands
        for i in 0..middle_band.len() {
            // Get the window of values for this position
            let start_idx = i;
            let end_idx = i + period;
            let window = &values[start_idx..end_idx];
            
            // Calculate standard deviation
            let std_dev = Self::calculate_std_dev(window, middle_band[i]);
            let band_width = std_dev * std_dev_multiplier;
            
            upper_band.push(middle_band[i] + band_width);
            lower_band.push(middle_band[i] - band_width);
        }
        
        Ok((middle_band, upper_band, lower_band))
    }
    
    /// Calculate the current bandwidth (distance between upper and lower bands)
    pub fn bandwidth(&self) -> Option<f64> {
        if let Some(values) = self.get_current_bands() {
            Some(values.upper - values.lower)
        } else {
            None
        }
    }
    
    /// Calculate %B (percent b) - position of price relative to bands
    /// %B = (Price - Lower Band) / (Upper Band - Lower Band)
    pub fn percent_b(&self, price: f64) -> Option<f64> {
        if let Some(values) = self.get_current_bands() {
            let width = values.upper - values.lower;
            if width > 0.0 {
                Some((price - values.lower) / width)
            } else {
                None
            }
        } else {
            None
        }
    }
    
    fn get_current_bands(&self) -> Option<BollingerBandsValues> {
        if let Some(middle) = self.sma.current() {
            if self.window.is_full() {
                let values = self.window.as_slice();
                let std_dev = Self::calculate_std_dev(&values, middle);
                let band_width = std_dev * self.std_dev_multiplier;
                
                Some(BollingerBandsValues {
                    middle,
                    upper: middle + band_width,
                    lower: middle - band_width,
                })
            } else {
                None
            }
        } else {
            None
        }
    }
}

impl Indicator for BollingerBands {
    fn calculate(&mut self, data: &PriceData) -> Result<IndicatorResult, IndicatorError> {
        let values = &data.close;
        
        let (middle, upper, lower) = Self::calculate_series(
            values, 
            self.period, 
            self.std_dev_multiplier
        )?;
        
        Ok(IndicatorResult::BollingerBands {
            middle,
            upper,
            lower,
        })
    }
    
    fn reset(&mut self) {
        self.sma.reset();
        self.window.clear();
    }
    
    fn is_ready(&self) -> bool {
        self.sma.is_ready() && self.window.is_full()
    }
}

impl IncrementalIndicator for BollingerBands {
    fn update(&mut self, value: f64) -> Result<Option<f64>, IndicatorError> {
        // Update window
        self.window.push(value);
        
        // Update SMA
        let _sma_result = self.sma.update(value)?;
        
        // Return bandwidth if ready
        if self.is_ready() {
            Ok(self.bandwidth())
        } else {
            Ok(None)
        }
    }
    
    fn current(&self) -> Option<f64> {
        self.bandwidth()
    }
}

/// Structure to hold all Bollinger Bands values
pub struct BollingerBandsValues {
    pub middle: f64,
    pub upper: f64,
    pub lower: f64,
}

impl BollingerBands {
    /// Get all current band values
    pub fn current_values(&self) -> Option<BollingerBandsValues> {
        self.get_current_bands()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_bollinger_bands_calculation() {
        let values = vec![
            20.0, 21.0, 22.0, 23.0, 24.0,
            25.0, 24.0, 23.0, 22.0, 21.0,
            20.0, 21.0, 22.0, 23.0, 24.0,
            25.0, 24.0, 23.0, 22.0, 21.0
        ];
        
        let (middle, upper, lower) = BollingerBands::calculate_series(&values, 5, 2.0).unwrap();
        
        assert_eq!(middle.len(), 16);
        assert_eq!(upper.len(), 16);
        assert_eq!(lower.len(), 16);
        
        // Upper band should always be above middle
        for i in 0..middle.len() {
            assert!(upper[i] > middle[i]);
            assert!(lower[i] < middle[i]);
        }
    }
    
    #[test]
    fn test_percent_b() {
        let mut bb = BollingerBands::standard().unwrap();
        
        // Create some test data
        for i in 0..25 {
            let _ = bb.update(20.0 + (i as f64 % 5.0));
        }
        
        // Price at upper band should give %B ≈ 1.0
        if let Some(bands) = bb.current_values() {
            let percent_b = bb.percent_b(bands.upper).unwrap();
            assert!((percent_b - 1.0).abs() < 1e-10);
            
            // Price at lower band should give %B ≈ 0.0
            let percent_b = bb.percent_b(bands.lower).unwrap();
            assert!(percent_b.abs() < 1e-10);
            
            // Price at middle band should give %B ≈ 0.5
            let percent_b = bb.percent_b(bands.middle).unwrap();
            assert!((percent_b - 0.5).abs() < 0.1);
        }
    }
}