//! Expected Shortfall (ES) / Conditional Value at Risk (`CVaR`) calculation
//! Production implementation for tail risk assessment

use std::collections::HashMap;
// REMOVED: Direct Decimal usage - use canonical types
use anyhow::Result;
use common::types::Price;
use rust_decimal::Decimal;
use tracing::warn;

// Removed types::operations - using common::types::prelude instead

/// Expected Shortfall calculator for tail risk measurement
///
/// Expected Shortfall (ES), also known as Conditional Value at Risk (`CVaR`),
/// measures the expected loss given that a loss exceeds the Value at Risk (`VaR`)
/// threshold. This provides a more comprehensive view of tail risk than `VaR` alone.
///
/// # Mathematical Foundation
///
/// For a given confidence level α, Expected Shortfall is defined as:
/// `ES_α` = E[X | X ≤ `VaR_α`]
///
/// Where X represents portfolio returns and `VaR_α` is the Value at Risk at confidence level α.
///
/// # Use Cases
///
/// - Regulatory capital calculations
/// - Risk-adjusted performance measurement
/// - Portfolio optimization with tail risk constraints
/// - Stress testing and scenario analysis
///
/// # Example
///
/// ```rust
/// use risk::var_calculator::expected_shortfall::ExpectedShortfall;
/// use std::collections::HashMap;
/// use common::types::Price;
///
/// let mut es_calculator = ExpectedShortfall::new(0.95);
///
/// let mut returns_data = HashMap::new();
/// returns_data.insert("AAPL".to_string(), vec![-0.02, 0.01, -0.05, 0.03]);
/// returns_data.insert("MSFT".to_string(), vec![-0.01, 0.02, -0.03, 0.01]);
///
/// es_calculator.update_returns_data(returns_data);
///
/// let weights = vec![0.6, 0.4];
/// let portfolio_value = Price::from(1_000_000);
///
/// let es = es_calculator.calculate_expected_shortfall(&weights, portfolio_value)?;
/// ```
#[derive(Debug)]
pub struct ExpectedShortfall {
    /// Confidence level for Expected Shortfall calculation
    ///
    /// Typical values:
    /// - 0.95 (95%): Standard risk management
    /// - 0.99 (99%): Regulatory requirements (Basel III)
    /// - 0.975 (97.5%): Internal risk limits
    confidence_level: f64,

    /// Historical returns data by symbol
    ///
    /// Key: Symbol identifier (e.g., "AAPL", "MSFT")
    /// Value: Vector of historical returns (as decimals, e.g., 0.02 for 2%)
    ///
    /// Returns should be:
    /// - Chronologically ordered (oldest to newest)
    /// - Same frequency (daily, weekly, etc.)
    /// - Clean of corporate actions adjustments
    returns_data: HashMap<String, Vec<f64>>,
}

impl ExpectedShortfall {
    /// Creates a new Expected Shortfall calculator with specified confidence level
    ///
    /// # Arguments
    ///
    /// * `confidence_level` - Confidence level between 0.0 and 1.0 (e.g., 0.95 for 95%)
    ///
    /// # Returns
    ///
    /// New `ExpectedShortfall` instance with empty returns data
    ///
    /// # Examples
    ///
    /// ```rust
    /// use risk::var_calculator::expected_shortfall::ExpectedShortfall;
    ///
    /// // Create 95% confidence level ES calculator
    /// let es_calc = ExpectedShortfall::new(0.95);
    ///
    /// // Create 99% confidence level for regulatory compliance
    /// let regulatory_es = ExpectedShortfall::new(0.99);
    /// ```
    ///
    /// # Panics
    ///
    /// Does not panic, but confidence levels outside [0, 1] will produce
    /// meaningless results in subsequent calculations.
    #[must_use]
    pub fn new(confidence_level: f64) -> Self {
        Self {
            confidence_level,
            returns_data: HashMap::new(),
        }
    }

    /// Updates the historical returns data used for Expected Shortfall calculations
    ///
    /// # Arguments
    ///
    /// * `returns` - `HashMap` mapping symbol identifiers to their historical returns
    ///
    /// # Data Requirements
    ///
    /// - Returns should be expressed as decimals (0.02 for 2%)
    /// - All return series should have the same frequency
    /// - Series should be chronologically ordered
    /// - Minimum 100 observations recommended for stable ES estimates
    ///
    /// # Examples
    ///
    /// ```rust
    /// use std::collections::HashMap;
    /// use risk::var_calculator::expected_shortfall::ExpectedShortfall;
    ///
    /// let mut es_calc = ExpectedShortfall::new(0.95);
    ///
    /// let mut returns = HashMap::new();
    /// returns.insert("AAPL".to_string(), vec![-0.05, 0.02, -0.01, 0.03]);
    /// returns.insert("GOOGL".to_string(), vec![-0.03, 0.01, -0.02, 0.04]);
    ///
    /// es_calc.update_returns_data(returns);
    /// ```
    pub fn update_returns_data(&mut self, returns: HashMap<String, Vec<f64>>) {
        self.returns_data = returns;
    }

    /// Calculates Expected Shortfall using historical simulation methodology
    ///
    /// This method computes the expected loss in the tail beyond the `VaR` threshold
    /// using historical return data and portfolio weights.
    ///
    /// # Arguments
    ///
    /// * `portfolio_weights` - Allocation weights for each asset (must sum to 1.0)
    /// * `portfolio_value` - Total portfolio value in base currency
    ///
    /// # Returns
    ///
    /// * `Ok(Decimal)` - Expected Shortfall amount in absolute currency terms
    /// * `Err(anyhow::Error)` - If calculation fails due to insufficient data or invalid inputs
    ///
    /// # Mathematical Process
    ///
    /// 1. Calculate weighted portfolio returns for each historical period
    /// 2. Sort returns in ascending order (worst losses first)
    /// 3. Identify `VaR` threshold at specified confidence level
    /// 4. Compute average of all returns worse than `VaR` threshold
    /// 5. Convert to absolute dollar amount using portfolio value
    ///
    /// # Examples
    ///
    /// ```rust
    /// use risk::var_calculator::expected_shortfall::ExpectedShortfall;
    /// use common::types::Price;
    /// use std::collections::HashMap;
    ///
    /// let mut es_calc = ExpectedShortfall::new(0.95);
    ///
    /// // Set up returns data
    /// let mut returns = HashMap::new();
    /// returns.insert("AAPL".to_string(), vec![-0.05, 0.02, -0.03, 0.01]);
    /// returns.insert("MSFT".to_string(), vec![-0.02, 0.03, -0.01, 0.02]);
    /// es_calc.update_returns_data(returns);
    ///
    /// // Calculate ES for 60/40 portfolio worth $1M
    /// let weights = vec![0.6, 0.4];
    /// let portfolio_value = Price::from(1_000_000);
    ///
    /// let es_amount = es_calc.calculate_expected_shortfall(&weights, portfolio_value)?;
    /// println!("95% Expected Shortfall: ${}", es_amount);
    /// ```
    ///
    /// # Errors
    ///
    /// - Returns error if no returns data is available
    /// - Returns error if portfolio weights length doesn't match number of assets
    /// - Returns error if portfolio value cannot be parsed
    /// - Returns error if `VaR` index calculation produces invalid results
    pub fn calculate_expected_shortfall(
        &self,
        portfolio_weights: &[f64],
        portfolio_value: Price,
    ) -> Result<Decimal> {
        if self.returns_data.is_empty() {
            return Err(anyhow::anyhow!(
                "No returns data available for ES calculation"
            ));
        }

        // Calculate portfolio returns
        let portfolio_returns = self.calculate_portfolio_returns(portfolio_weights)?;

        if portfolio_returns.is_empty() {
            return Err(anyhow::anyhow!("No portfolio returns calculated"));
        }

        // Sort returns in ascending order (worst first)
        let mut sorted_returns = portfolio_returns;
        sorted_returns.sort_by(|a, b| {
            a.partial_cmp(b).unwrap_or_else(|| {
                // Handle NaN values: treat NaN as the smallest value for conservative risk assessment
                if a.is_nan() && b.is_nan() {
                    std::cmp::Ordering::Equal
                } else if a.is_nan() {
                    std::cmp::Ordering::Less
                } else if b.is_nan() {
                    std::cmp::Ordering::Greater
                } else {
                    std::cmp::Ordering::Equal
                }
            })
        });

        // Find VaR threshold
        let var_index =
            ((1.0 - self.confidence_level) * sorted_returns.len() as f64).floor() as usize;

        if var_index >= sorted_returns.len() {
            return Err(anyhow::anyhow!("VaR index out of bounds"));
        }

        // Calculate Expected Shortfall as average of returns worse than VaR
        let tail_returns = &sorted_returns[0..=var_index];
        let expected_shortfall_return =
            tail_returns.iter().sum::<f64>() / tail_returns.len() as f64;

        // Convert to dollar amount
        let portfolio_value_f64 = portfolio_value
            .to_string()
            .parse::<f64>()
            .map_err(|e| anyhow::anyhow!("Failed to parse portfolio value: {e}"))?;

        let es_amount = expected_shortfall_return.abs() * portfolio_value_f64;

        Decimal::try_from(es_amount).map_err(|_| anyhow::anyhow!("Failed to convert ES to decimal"))
    }

    /// Calculates weighted portfolio returns from individual asset returns
    ///
    /// # Arguments
    ///
    /// * `weights` - Portfolio allocation weights (must match number of assets)
    ///
    /// # Returns
    ///
    /// * `Ok(Vec<f64>)` - Vector of portfolio returns for each time period
    /// * `Err(anyhow::Error)` - If weights don't match assets or no data available
    ///
    /// # Process
    ///
    /// 1. Validates weights length matches number of assets
    /// 2. Finds minimum length across all return series for alignment
    /// 3. Calculates weighted sum of returns for each time period
    /// 4. Uses most recent data when series have different lengths
    fn calculate_portfolio_returns(&self, weights: &[f64]) -> Result<Vec<f64>> {
        if weights.is_empty() {
            return Err(anyhow::anyhow!("Portfolio weights cannot be empty"));
        }

        let symbols: Vec<String> = self.returns_data.keys().cloned().collect();

        if symbols.len() != weights.len() {
            return Err(anyhow::anyhow!(
                "Number of weights must match number of assets"
            ));
        }

        // Find minimum length across all return series
        let min_length = self
            .returns_data
            .values()
            .map(Vec::len)
            .min()
            .unwrap_or_else(|| {
                warn!("No returns data found in expected shortfall calculation");
                0
            });

        if min_length == 0 {
            return Err(anyhow::anyhow!("No returns data available"));
        }

        let mut portfolio_returns = Vec::with_capacity(min_length);

        // Calculate weighted portfolio returns for each period
        for period in 0..min_length {
            let mut portfolio_return = 0.0;

            for (i, symbol) in symbols.iter().enumerate() {
                if let Some(asset_returns) = self.returns_data.get(symbol) {
                    // Use the most recent data by indexing from the end
                    let return_index = asset_returns.len() - min_length + period;
                    portfolio_return += weights[i] * asset_returns[return_index];
                }
            }

            portfolio_returns.push(portfolio_return);
        }

        Ok(portfolio_returns)
    }

    /// Calculates Expected Shortfall with bootstrap confidence intervals
    ///
    /// This method provides not only the point estimate of Expected Shortfall
    /// but also confidence intervals around that estimate using bootstrap resampling.
    ///
    /// # Arguments
    ///
    /// * `portfolio_weights` - Portfolio allocation weights
    /// * `portfolio_value` - Total portfolio value
    /// * `confidence_interval` - Confidence level for the interval (e.g., 0.95 for 95%)
    ///
    /// # Returns
    ///
    /// * `Ok(ESResult)` - Complete ES results with confidence bounds
    /// * `Err(anyhow::Error)` - If calculation fails
    ///
    /// # Bootstrap Methodology
    ///
    /// 1. Performs 1000 bootstrap samples of historical returns
    /// 2. Calculates ES for each bootstrap sample
    /// 3. Derives confidence intervals from bootstrap distribution
    /// 4. Provides lower and upper bounds for risk assessment
    ///
    /// # Use Cases
    ///
    /// - Model validation and backtesting
    /// - Uncertainty quantification in risk reports
    /// - Regulatory stress testing with confidence bounds
    /// - Portfolio optimization with estimation risk
    ///
    /// # Examples
    ///
    /// ```rust
    /// use risk::var_calculator::expected_shortfall::ExpectedShortfall;
    /// use common::types::Price;
    ///
    /// let es_calc = ExpectedShortfall::new(0.95);
    /// // ... set up returns data ...
    ///
    /// let weights = vec![0.6, 0.4];
    /// let portfolio_value = Price::from(1_000_000);
    ///
    /// let es_result = es_calc.calculate_es_with_confidence(
    ///     &weights,
    ///     portfolio_value,
    ///     0.95  // 95% confidence interval
    /// )?;
    ///
    /// println!("ES: ${}", es_result.expected_shortfall);
    /// println!("95% CI: [${}, ${}]", es_result.lower_bound, es_result.upper_bound);
    /// ```
    pub fn calculate_es_with_confidence(
        &self,
        portfolio_weights: &[f64],
        portfolio_value: Price,
        confidence_interval: f64,
    ) -> Result<ESResult> {
        let base_es = self.calculate_expected_shortfall(portfolio_weights, portfolio_value)?;

        // Bootstrap confidence intervals (simplified implementation)
        let portfolio_returns = self.calculate_portfolio_returns(portfolio_weights)?;
        let n_bootstrap = 1000;
        let mut bootstrap_es = Vec::new();

        // Simple bootstrap resampling
        for _ in 0..n_bootstrap {
            let mut resampled_returns = Vec::new();
            for _ in 0..portfolio_returns.len() {
                let idx = fastrand::usize(..portfolio_returns.len());
                resampled_returns.push(portfolio_returns[idx]);
            }

            let es = self.calculate_es_from_returns(&resampled_returns, portfolio_value)?;
            bootstrap_es.push(es);
        }

        bootstrap_es.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));

        let lower_idx = ((1.0 - confidence_interval) / 2.0 * f64::from(n_bootstrap)) as usize;
        let upper_idx = (f64::midpoint(1.0, confidence_interval) * f64::from(n_bootstrap)) as usize;

        Ok(ESResult {
            expected_shortfall: base_es.into(),
            confidence_level: self.confidence_level,
            lower_bound: bootstrap_es
                .get(lower_idx)
                .copied()
                .unwrap_or_else(|| {
                    warn!("Failed to get bootstrap lower bound, using base ES");
                    base_es
                })
                .into(),
            upper_bound: bootstrap_es
                .get(upper_idx.min(bootstrap_es.len() - 1))
                .copied()
                .unwrap_or_else(|| {
                    warn!("Failed to get bootstrap upper bound, using base ES");
                    base_es
                })
                .into(),
            confidence_interval,
        })
    }

    /// Helper method to calculate Expected Shortfall from a given set of returns
    ///
    /// Used internally for bootstrap resampling and testing scenarios.
    ///
    /// # Arguments
    ///
    /// * `returns` - Vector of portfolio returns
    /// * `portfolio_value` - Portfolio value for absolute amount calculation
    ///
    /// # Returns
    ///
    /// Expected Shortfall amount as Decimal, or error if calculation fails
    fn calculate_es_from_returns(
        &self,
        returns: &[f64],
        portfolio_value: Price,
    ) -> Result<Decimal> {
        let mut sorted_returns = returns.to_vec();
        sorted_returns.sort_by(|a, b| {
            a.partial_cmp(b).unwrap_or_else(|| {
                // Handle NaN values: treat NaN as the smallest value for conservative risk assessment
                if a.is_nan() && b.is_nan() {
                    std::cmp::Ordering::Equal
                } else if a.is_nan() {
                    std::cmp::Ordering::Less
                } else if b.is_nan() {
                    std::cmp::Ordering::Greater
                } else {
                    std::cmp::Ordering::Equal
                }
            })
        });

        let var_index =
            ((1.0 - self.confidence_level) * sorted_returns.len() as f64).floor() as usize;

        if var_index >= sorted_returns.len() {
            return Ok(Decimal::ZERO);
        }

        let tail_returns = &sorted_returns[0..=var_index];
        let expected_shortfall_return =
            tail_returns.iter().sum::<f64>() / tail_returns.len() as f64;

        let portfolio_value_f64 = portfolio_value
            .to_string()
            .parse::<f64>()
            .map_err(|e| anyhow::anyhow!("Failed to parse portfolio value: {e}"))?;

        let es_amount = expected_shortfall_return.abs() * portfolio_value_f64;

        Decimal::from_f64_retain(es_amount)
            .ok_or_else(|| anyhow::anyhow!("Failed to convert ES to decimal"))
    }
}

/// Expected Shortfall calculation result with confidence intervals
///
/// Contains the complete results of an Expected Shortfall calculation
/// including the point estimate and bootstrap confidence intervals.
///
/// # Fields Description
///
/// - `expected_shortfall`: Point estimate of ES in absolute currency terms
/// - `confidence_level`: Confidence level used for ES calculation (e.g., 0.95)
/// - `lower_bound`: Lower bound of bootstrap confidence interval
/// - `upper_bound`: Upper bound of bootstrap confidence interval  
/// - `confidence_interval`: Confidence level for the interval bounds
///
/// # Usage in Risk Management
///
/// This structure provides comprehensive ES information for:
/// - Risk reporting with uncertainty quantification
/// - Model validation and backtesting
/// - Regulatory capital calculations
/// - Portfolio optimization with estimation risk
///
/// # Example
///
/// ```rust
/// use risk::var_calculator::expected_shortfall::ESResult;
/// use common::types::Price;
///
/// let es_result = ESResult {
///     expected_shortfall: Price::from(50_000),
///     confidence_level: 0.95,
///     lower_bound: Price::from(45_000),
///     upper_bound: Price::from(55_000),
///     confidence_interval: 0.95,
/// };
///
/// println!("95% ES: ${} [${}, ${}]",
///          es_result.expected_shortfall,
///          es_result.lower_bound,
///          es_result.upper_bound);
/// ```
#[derive(Debug, Clone)]
pub struct ESResult {
    /// Point estimate of Expected Shortfall in absolute currency terms
    ///
    /// This represents the expected loss given that losses exceed the `VaR` threshold.
    /// Always expressed as a positive value representing potential loss amount.
    pub expected_shortfall: Price,

    /// Confidence level used for Expected Shortfall calculation
    ///
    /// Typical values:
    /// - 0.95 (95%): Standard risk management
    /// - 0.99 (99%): Regulatory requirements
    /// - 0.975 (97.5%): Internal risk limits
    pub confidence_level: f64,

    /// Lower bound of the bootstrap confidence interval
    ///
    /// Represents the lower estimate of ES accounting for estimation uncertainty.
    /// Used for conservative risk assessment and model validation.
    pub lower_bound: Price,

    /// Upper bound of the bootstrap confidence interval
    ///
    /// Represents the upper estimate of ES accounting for estimation uncertainty.
    /// Important for understanding the range of possible ES values.
    pub upper_bound: Price,

    /// Confidence level for the interval bounds
    ///
    /// Confidence level used to construct the bootstrap confidence interval
    /// (e.g., 0.95 means 95% of bootstrap samples fall within [`lower_bound`, `upper_bound`]).
    pub confidence_interval: f64,
}

#[cfg(test)]
#[allow(
    clippy::assertions_on_result_states,
    clippy::get_unwrap,
    clippy::single_match,
    clippy::str_to_string,
    clippy::useless_vec
)]
mod tests {
    use super::*;
    use common::types::Price;
    use std::collections::HashMap;

    #[test]
    fn test_expected_shortfall_new() {
        let es_calc = ExpectedShortfall::new(0.95);
        assert!((es_calc.confidence_level - 0.95).abs() < 1e-6);
        assert_eq!(es_calc.returns_data.len(), 0);
    }

    #[test]
    fn test_update_returns_data() {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        returns_data.insert("AAPL".to_string(), vec![0.01, -0.02, 0.03, -0.01]);
        returns_data.insert("MSFT".to_string(), vec![0.02, -0.01, 0.01, 0.00]);

        es_calc.update_returns_data(returns_data.clone());

        assert_eq!(es_calc.returns_data.len(), 2);
        assert_eq!(es_calc.returns_data.get("AAPL").unwrap().len(), 4);
        assert_eq!(es_calc.returns_data.get("MSFT").unwrap().len(), 4);
    }

    #[test]
    fn test_calculate_expected_shortfall_no_data() {
        let es_calc = ExpectedShortfall::new(0.95);
        let weights = vec![1.0];
        let portfolio_value = Price::from_f64(1000000.0).unwrap();

        let result = es_calc.calculate_expected_shortfall(&weights, portfolio_value);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("No returns data"));
    }

    #[test]
    fn test_calculate_expected_shortfall_single_asset() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        // Include some negative returns to ensure ES is non-zero
        returns_data.insert(
            "AAPL".to_string(),
            vec![0.01, -0.05, 0.02, -0.03, 0.01, -0.02, 0.03, -0.01],
        );

        es_calc.update_returns_data(returns_data);

        let weights = vec![1.0];
        let portfolio_value = Price::from_f64(1000000.0)?;

        let es = es_calc.calculate_expected_shortfall(&weights, portfolio_value)?;

        // ES should be positive
        assert!(es > Decimal::ZERO);
        // ES should be reasonable (less than portfolio value)
        assert!(es < Decimal::from(1000000));

        Ok(())
    }

    #[test]
    fn test_calculate_expected_shortfall_portfolio() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        returns_data.insert(
            "AAPL".to_string(),
            vec![0.01, -0.04, 0.02, -0.02, 0.03, -0.03],
        );
        returns_data.insert(
            "MSFT".to_string(),
            vec![0.02, -0.02, 0.01, -0.01, 0.00, -0.02],
        );

        es_calc.update_returns_data(returns_data);

        let weights = vec![0.6, 0.4];
        let portfolio_value = Price::from_f64(1000000.0)?;

        let es = es_calc.calculate_expected_shortfall(&weights, portfolio_value)?;

        assert!(es > Decimal::ZERO);
        assert!(es < Decimal::from(1000000));

        Ok(())
    }

    #[test]
    fn test_expected_shortfall_different_confidence_levels() -> Result<()> {
        let mut returns_data = HashMap::new();
        returns_data.insert(
            "AAPL".to_string(),
            vec![0.01, -0.05, 0.02, -0.03, 0.03, -0.02, 0.01, -0.04],
        );

        let weights = vec![1.0];
        let portfolio_value = Price::from_f64(1000000.0)?;

        // 90% confidence
        let mut es_90 = ExpectedShortfall::new(0.90);
        es_90.update_returns_data(returns_data.clone());
        let es_90_result = es_90.calculate_expected_shortfall(&weights, portfolio_value)?;

        // 95% confidence
        let mut es_95 = ExpectedShortfall::new(0.95);
        es_95.update_returns_data(returns_data.clone());
        let es_95_result = es_95.calculate_expected_shortfall(&weights, portfolio_value)?;

        // 99% confidence
        let mut es_99 = ExpectedShortfall::new(0.99);
        es_99.update_returns_data(returns_data);
        let es_99_result = es_99.calculate_expected_shortfall(&weights, portfolio_value)?;

        // Higher confidence levels should generally produce higher ES
        // Note: This may not always hold with small samples, so we just verify all are positive
        assert!(es_90_result > Decimal::ZERO);
        assert!(es_95_result > Decimal::ZERO);
        assert!(es_99_result > Decimal::ZERO);

        Ok(())
    }

    #[test]
    fn test_weights_mismatch() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        returns_data.insert("AAPL".to_string(), vec![0.01, -0.02, 0.03]);
        returns_data.insert("MSFT".to_string(), vec![0.02, -0.01, 0.01]);

        es_calc.update_returns_data(returns_data);

        // Wrong number of weights
        let weights = vec![1.0]; // Should be 2
        let portfolio_value = Price::from_f64(1000000.0)?;

        let result = es_calc.calculate_expected_shortfall(&weights, portfolio_value);
        assert!(result.is_err());

        Ok(())
    }

    #[test]
    fn test_weights_sum_to_one() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        returns_data.insert("AAPL".to_string(), vec![0.01, -0.03, 0.02, -0.02]);
        returns_data.insert("MSFT".to_string(), vec![0.02, -0.02, 0.01, -0.01]);

        es_calc.update_returns_data(returns_data);

        // Weights that sum to 1.0
        let weights = vec![0.6, 0.4];
        let portfolio_value = Price::from_f64(1000000.0)?;

        let es = es_calc.calculate_expected_shortfall(&weights, portfolio_value)?;
        assert!(es > Decimal::ZERO);

        Ok(())
    }

    #[test]
    fn test_all_positive_returns() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        // All positive returns - ES should be zero or very small
        returns_data.insert("AAPL".to_string(), vec![0.01, 0.02, 0.03, 0.01, 0.02]);

        es_calc.update_returns_data(returns_data);

        let weights = vec![1.0];
        let portfolio_value = Price::from_f64(1000000.0)?;

        let es = es_calc.calculate_expected_shortfall(&weights, portfolio_value)?;

        // With all positive returns, ES should be zero or minimal
        assert!(es >= Decimal::ZERO);

        Ok(())
    }

    #[test]
    fn test_all_negative_returns() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        // All negative returns
        returns_data.insert("AAPL".to_string(), vec![-0.01, -0.02, -0.03, -0.01, -0.02]);

        es_calc.update_returns_data(returns_data);

        let weights = vec![1.0];
        let portfolio_value = Price::from_f64(1000000.0)?;

        let es = es_calc.calculate_expected_shortfall(&weights, portfolio_value)?;

        // With all negative returns, ES should be substantial
        assert!(es > Decimal::ZERO);
        assert!(es > Decimal::from(5000)); // Should be at least 0.5% of portfolio

        Ok(())
    }

    #[test]
    fn test_portfolio_with_zero_weight() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        returns_data.insert("AAPL".to_string(), vec![0.01, -0.03, 0.02, -0.02]);
        returns_data.insert("MSFT".to_string(), vec![0.02, -0.02, 0.01, -0.01]);

        es_calc.update_returns_data(returns_data);

        // One asset with zero weight
        let weights = vec![1.0, 0.0];
        let portfolio_value = Price::from_f64(1000000.0)?;

        let es = es_calc.calculate_expected_shortfall(&weights, portfolio_value)?;
        assert!(es >= Decimal::ZERO);

        Ok(())
    }

    #[test]
    fn test_minimum_returns_requirement() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        // Only 2 returns - might not be enough for meaningful ES
        returns_data.insert("AAPL".to_string(), vec![0.01, -0.02]);

        es_calc.update_returns_data(returns_data);

        let weights = vec![1.0];
        let portfolio_value = Price::from_f64(1000000.0)?;

        // Should still calculate something, even if not very reliable
        let result = es_calc.calculate_expected_shortfall(&weights, portfolio_value);
        // The function may or may not fail with insufficient data - either is acceptable
        match result {
            Ok(es) => assert!(es >= Decimal::ZERO),
            Err(_) => {}, // Acceptable to reject insufficient data
        }

        Ok(())
    }

    #[test]
    fn test_extreme_negative_returns() -> Result<()> {
        let mut es_calc = ExpectedShortfall::new(0.95);
        let mut returns_data = HashMap::new();
        // Mix of normal and extreme negative returns
        returns_data.insert("AAPL".to_string(), vec![0.01, -0.10, 0.02, -0.15, 0.01]);

        es_calc.update_returns_data(returns_data);

        let weights = vec![1.0];
        let portfolio_value = Price::from_f64(1000000.0)?;

        let es = es_calc.calculate_expected_shortfall(&weights, portfolio_value)?;

        // ES should capture the extreme losses
        assert!(es > Decimal::from(50000)); // Should be significant given -10% and -15% returns

        Ok(())
    }

    #[test]
    fn test_diversification_benefit() -> Result<()> {
        let mut returns_data = HashMap::new();
        // Negatively correlated assets
        returns_data.insert(
            "ASSET_A".to_string(),
            vec![0.05, -0.05, 0.03, -0.03, 0.02, -0.02],
        );
        returns_data.insert(
            "ASSET_B".to_string(),
            vec![-0.05, 0.05, -0.03, 0.03, -0.02, 0.02],
        );

        let portfolio_value = Price::from_f64(1000000.0)?;

        // Single asset ES
        let mut es_single_a = ExpectedShortfall::new(0.95);
        let mut data_a = HashMap::new();
        data_a.insert("ASSET_A".to_string(), returns_data["ASSET_A"].clone());
        es_single_a.update_returns_data(data_a);
        let es_a = es_single_a.calculate_expected_shortfall(&vec![1.0], portfolio_value)?;

        // Diversified portfolio ES
        let mut es_portfolio = ExpectedShortfall::new(0.95);
        es_portfolio.update_returns_data(returns_data);
        let es_port =
            es_portfolio.calculate_expected_shortfall(&vec![0.5, 0.5], portfolio_value)?;

        // Diversified portfolio should have lower ES (or at worst equal)
        // Due to negative correlation, portfolio ES should be lower
        assert!(es_port <= es_a || (es_a - es_port).abs() < Decimal::from(1000));

        Ok(())
    }
}
