//! `VaR` (Value at Risk) Repository
//!
//! Manages `VaR` calculations, historical data storage, and risk metrics for
//! high-frequency trading systems with multiple calculation methods.

// Allow pedantic lints for VaR calculation complexity
#![allow(clippy::missing_docs_in_private_items)]
#![allow(clippy::arithmetic_side_effects)]
#![allow(clippy::as_conversions)]
#![allow(clippy::default_numeric_fallback)]
#![allow(clippy::str_to_string)]
#![allow(clippy::float_arithmetic)]
#![allow(clippy::indexing_slicing)]
#![allow(missing_docs)]
#![allow(clippy::expect_used)]
#![allow(clippy::module_name_repetitions)]

use async_trait::async_trait;
use chrono::{DateTime, Duration, Utc};
use redis::aio::ConnectionManager;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::collections::HashMap;
use tracing::{info, warn};
use uuid::Uuid;

use crate::{RiskDataError, RiskDataResult};

/// `VaR` calculation methods
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "var_method", rename_all = "snake_case")]
pub enum VarMethod {
    /// Historical simulation method using past returns
    Historical,
    /// Parametric method using normal distribution
    Parametric,
    /// Monte Carlo simulation method
    MonteCarlo,
    /// Extreme Value Theory method
    ExtremeValue,
}

/// `VaR` confidence levels
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConfidenceLevel {
    /// 95% confidence level
    P95,
    /// 97.5% confidence level
    P97_5,
    /// 99% confidence level
    P99,
    /// 99.9% confidence level
    P99_9,
}

impl ConfidenceLevel {
    pub const fn as_decimal(&self) -> Decimal {
        match self {
            Self::P95 => Decimal::from_parts(95, 0, 0, false, 2), // 0.95
            Self::P97_5 => Decimal::from_parts(975, 0, 0, false, 3), // 0.975
            Self::P99 => Decimal::from_parts(99, 0, 0, false, 2), // 0.99
            Self::P99_9 => Decimal::from_parts(999, 0, 0, false, 3), // 0.999
        }
    }
}

/// `VaR` calculation request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VarRequest {
    /// Portfolio identifier for `VaR` calculation
    pub portfolio_id: String,
    /// `VaR` calculation method to use
    pub method: VarMethod,
    /// Confidence level for the `VaR` calculation
    pub confidence_level: ConfidenceLevel,
    /// Holding period in days
    pub holding_period_days: i32,
    /// Number of historical days to look back for data
    pub lookback_days: i32,
    /// Currency for the `VaR` result (ISO 3-letter code)
    pub currency: String,
}

/// `VaR` calculation result
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct VarResult {
    /// Unique identifier for this `VaR` calculation
    pub id: Uuid,
    /// Portfolio this `VaR` calculation applies to
    pub portfolio_id: String,
    /// `VaR` calculation method used
    pub method: VarMethod,
    /// Confidence level used in the calculation
    pub confidence_level: Decimal,
    /// Holding period in days used for the calculation
    pub holding_period_days: i32,
    /// Number of historical days used for the calculation
    pub lookback_days: i32,
    /// Calculated `VaR` amount
    pub var_amount: Decimal,
    /// Currency of the `VaR` amount (ISO 3-letter code)
    pub currency: String,
    /// Date and time when this calculation was performed
    pub calculation_date: DateTime<Utc>,
    /// Portfolio volatility (if calculated)
    pub volatility: Option<Decimal>,
    /// Correlation adjustment factor (if applied)
    pub correlation_adjustment: Option<Decimal>,
    /// Stress test multiplier (if this is a stress test result)
    pub stress_test_multiplier: Option<Decimal>,
    /// Additional calculation metadata as JSON
    pub metadata: serde_json::Value,
}

/// Portfolio position for `VaR` calculation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortfolioPosition {
    /// Instrument symbol for this position
    pub symbol: String,
    /// Number of shares/contracts held
    pub quantity: Decimal,
    /// Current market value of the position
    pub market_value: Decimal,
    /// Currency of the position (ISO 3-letter code)
    pub currency: String,
    /// Asset class classification
    pub asset_class: String,
}

/// Internal row type for SQLX query mapping from `broker_positions` table
#[derive(Debug, sqlx::FromRow)]
struct PortfolioPositionRow {
    symbol: String,
    quantity: Decimal,
    market_value: Option<Decimal>,
}

/// Historical price data
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct PriceData {
    /// Instrument symbol for this price point
    pub symbol: String,
    /// Price at this timestamp
    pub price: Decimal,
    /// Timestamp when this price was recorded
    pub timestamp: DateTime<Utc>,
    /// Trading volume at this price (if available)
    pub volume: Option<Decimal>,
}

/// `VaR` repository trait
#[async_trait]
pub trait VarRepository: Send + Sync + std::fmt::Debug {
    /// Calculate `VaR` for a portfolio
    async fn calculate_var(&self, request: VarRequest) -> RiskDataResult<VarResult>;

    /// Store `VaR` calculation result
    async fn store_var_result(&self, result: VarResult) -> RiskDataResult<()>;

    /// Get `VaR` history for a portfolio
    async fn get_var_history(
        &self,
        portfolio_id: &str,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> RiskDataResult<Vec<VarResult>>;

    /// Get latest `VaR` for a portfolio
    async fn get_latest_var(&self, portfolio_id: &str) -> RiskDataResult<Option<VarResult>>;

    /// Store historical price data
    async fn store_price_data(&self, prices: Vec<PriceData>) -> RiskDataResult<()>;

    /// Get price history for `VaR` calculations
    async fn get_price_history(
        &self,
        symbols: Vec<String>,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> RiskDataResult<HashMap<String, Vec<PriceData>>>;

    /// Get portfolio positions
    async fn get_portfolio_positions(
        &self,
        portfolio_id: &str,
    ) -> RiskDataResult<Vec<PortfolioPosition>>;

    /// Calculate portfolio volatility matrix
    async fn calculate_volatility_matrix(
        &self,
        symbols: Vec<String>,
        lookback_days: i32,
    ) -> RiskDataResult<HashMap<String, HashMap<String, Decimal>>>;

    /// Run stress test scenarios
    async fn run_stress_test(
        &self,
        portfolio_id: &str,
        scenario_name: &str,
        stress_factors: HashMap<String, Decimal>,
    ) -> RiskDataResult<VarResult>;
}

/// `VaR` repository implementation
#[derive(Clone)]
pub struct VarRepositoryImpl {
    db_pool: PgPool,
    redis_conn: ConnectionManager,
}

impl std::fmt::Debug for VarRepositoryImpl {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("VarRepositoryImpl")
            .field("db_pool", &"<PgPool>")
            .field("redis_conn", &"<ConnectionManager>")
            .finish()
    }
}

impl VarRepositoryImpl {
    pub const fn new(db_pool: PgPool, redis_conn: ConnectionManager) -> Self {
        Self {
            db_pool,
            redis_conn,
        }
    }

    /// Calculate historical `VaR`
    async fn calculate_historical_var(
        &self,
        positions: Vec<PortfolioPosition>,
        confidence_level: ConfidenceLevel,
        lookback_days: i32,
    ) -> RiskDataResult<Decimal> {
        info!(
            "Calculating historical VaR with {} day lookback",
            lookback_days
        );

        // Get symbols from positions
        let symbols: Vec<String> = positions.iter().map(|p| p.symbol.clone()).collect();

        // Get historical prices
        let to_date = Utc::now();
        let from_date = to_date - Duration::days(i64::from(lookback_days));

        let price_history = self.get_price_history(symbols, from_date, to_date).await?;

        // Calculate daily returns for each position
        let mut portfolio_returns = Vec::new();

        for position in positions {
            if let Some(prices) = price_history.get(&position.symbol) {
                if prices.len() < 2 {
                    warn!("Insufficient price data for {}", position.symbol);
                    continue;
                }

                // Calculate daily returns
                for window in prices.windows(2) {
                    let prev_price = window[0].price;
                    let curr_price = window[1].price;

                    if prev_price > Decimal::ZERO {
                        let return_rate = (curr_price - prev_price) / prev_price;
                        let position_return = return_rate * position.market_value;
                        portfolio_returns.push(position_return);
                    }
                }
            }
        }

        if portfolio_returns.is_empty() {
            return Err(RiskDataError::VarCalculation(
                "No returns data available".to_owned(),
            ));
        }

        // Sort returns and find percentile
        portfolio_returns.sort();
        let confidence_f64 = confidence_level
            .as_decimal()
            .to_string()
            .parse::<f64>()
            .map_err(|e| {
                RiskDataError::VarCalculation(format!("Invalid confidence level conversion: {}", e))
            })?;

        #[allow(clippy::cast_precision_loss)]
        let returns_len_f64 = portfolio_returns.len() as f64;
        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
        let percentile_index = ((1.0 - confidence_f64) * returns_len_f64) as usize;

        let var_amount = portfolio_returns
            .get(percentile_index)
            .copied()
            .ok_or_else(|| {
                RiskDataError::VarCalculation("No VaR data at calculated percentile".to_owned())
            })?
            .abs();

        info!("Historical VaR calculated: {}", var_amount);
        Ok(var_amount)
    }

    /// Calculate parametric `VaR` using correlation matrix
    async fn calculate_parametric_var(
        &self,
        positions: Vec<PortfolioPosition>,
        confidence_level: ConfidenceLevel,
        lookback_days: i32,
    ) -> RiskDataResult<Decimal> {
        info!("Calculating parametric VaR");

        let symbols: Vec<String> = positions.iter().map(|p| p.symbol.clone()).collect();
        let vol_matrix = self
            .calculate_volatility_matrix(symbols, lookback_days)
            .await?;

        // Calculate portfolio variance using correlation matrix
        let mut portfolio_variance = Decimal::ZERO;

        for i in 0..positions.len() {
            for j in 0..positions.len() {
                let pos_i = &positions[i];
                let pos_j = &positions[j];

                let vol_i = vol_matrix
                    .get(&pos_i.symbol)
                    .and_then(|row| row.get(&pos_i.symbol))
                    .copied()
                    .ok_or_else(|| {
                        RiskDataError::VarCalculation(format!(
                            "Missing volatility data for symbol: {}",
                            pos_i.symbol
                        ))
                    })?;

                let correlation = if i == j {
                    Decimal::ONE
                } else {
                    vol_matrix
                        .get(&pos_i.symbol)
                        .and_then(|row| row.get(&pos_j.symbol))
                        .copied()
                        .ok_or_else(|| {
                            RiskDataError::VarCalculation(format!(
                                "Missing correlation data between {} and {}",
                                pos_i.symbol, pos_j.symbol
                            ))
                        })?
                };

                let vol_j = vol_matrix
                    .get(&pos_j.symbol)
                    .and_then(|row| row.get(&pos_j.symbol))
                    .copied()
                    .ok_or_else(|| {
                        RiskDataError::VarCalculation(format!(
                            "Missing volatility data for symbol: {}",
                            pos_j.symbol
                        ))
                    })?;

                portfolio_variance +=
                    pos_i.market_value * pos_j.market_value * vol_i * vol_j * correlation;
            }
        }

        // Calculate square root of portfolio variance using f64 conversion
        let portfolio_volatility = if portfolio_variance == Decimal::ZERO {
            // CRITICAL: Zero portfolio variance indicates a serious risk calculation problem
            // This could be due to:
            // 1. Missing volatility data (dangerous)
            // 2. All positions are zero risk (unlikely in real trading)
            // 3. Data corruption or missing correlation matrix
            warn!("Portfolio variance is zero - this indicates missing volatility data or data corruption");

            // Return error instead of silently using zero volatility
            return Err(RiskDataError::VarCalculation(
                "Portfolio variance is zero - cannot calculate meaningful VaR. \
                 This may indicate missing volatility data, data corruption, or incorrect position data.".to_owned()
            ));
        } else {
            let variance_f64: f64 = portfolio_variance.try_into().map_err(|e| {
                RiskDataError::VarCalculation(format!(
                    "Portfolio variance conversion failed: {}",
                    e
                ))
            })?;
            let volatility_f64 = variance_f64.sqrt();
            Decimal::try_from(volatility_f64).map_err(|e| {
                RiskDataError::VarCalculation(format!("Volatility calculation failed: {}", e))
            })?
        };

        // Apply confidence level multiplier (normal distribution quantiles)
        let z_score = match confidence_level {
            ConfidenceLevel::P95 => Decimal::from_parts(1645, 0, 0, false, 3), // 1.645
            ConfidenceLevel::P97_5 => Decimal::from_parts(196, 0, 0, false, 2), // 1.96
            ConfidenceLevel::P99 => Decimal::from_parts(2326, 0, 0, false, 3), // 2.326
            ConfidenceLevel::P99_9 => Decimal::from_parts(309, 0, 0, false, 2), // 3.09
        };

        let var_amount = portfolio_volatility * z_score;
        info!("Parametric VaR calculated: {}", var_amount);
        Ok(var_amount)
    }
}

#[async_trait]
impl VarRepository for VarRepositoryImpl {
    async fn calculate_var(&self, request: VarRequest) -> RiskDataResult<VarResult> {
        info!("Calculating VaR for portfolio {}", request.portfolio_id);

        let positions = self.get_portfolio_positions(&request.portfolio_id).await?;
        if positions.is_empty() {
            return Err(RiskDataError::VarCalculation(
                "No positions found for portfolio".to_owned(),
            ));
        }

        let var_amount = match request.method {
            VarMethod::Historical => {
                self.calculate_historical_var(
                    positions,
                    request.confidence_level,
                    request.lookback_days,
                )
                .await?
            },
            VarMethod::Parametric => {
                self.calculate_parametric_var(
                    positions,
                    request.confidence_level,
                    request.lookback_days,
                )
                .await?
            },
            VarMethod::MonteCarlo => {
                // Simplified Monte Carlo - would need more sophisticated implementation
                warn!("Monte Carlo VaR not fully implemented, using parametric method");
                self.calculate_parametric_var(
                    positions,
                    request.confidence_level,
                    request.lookback_days,
                )
                .await?
            },
            VarMethod::ExtremeValue => {
                // Extreme Value Theory - would need specialized implementation
                warn!("Extreme Value VaR not fully implemented, using historical method");
                self.calculate_historical_var(
                    positions,
                    request.confidence_level,
                    request.lookback_days,
                )
                .await?
            },
        };

        // Store currency before moving request
        let currency = request.currency.clone();

        let result = VarResult {
            id: Uuid::new_v4(),
            portfolio_id: request.portfolio_id,
            method: request.method,
            confidence_level: request.confidence_level.as_decimal(),
            holding_period_days: request.holding_period_days,
            lookback_days: request.lookback_days,
            var_amount,
            currency: request.currency,
            calculation_date: Utc::now(),
            volatility: None, // Could be calculated based on method
            correlation_adjustment: None,
            stress_test_multiplier: None,
            metadata: serde_json::json!({}),
        };

        // Store result
        self.store_var_result(result.clone()).await?;

        info!("VaR calculation completed: {} {}", var_amount, currency);
        Ok(result)
    }

    async fn store_var_result(&self, result: VarResult) -> RiskDataResult<()> {
        let query = "
            INSERT INTO var_calculations (
                id, portfolio_id, method, confidence_level, holding_period_days,
                lookback_days, var_amount, currency, calculation_date,
                volatility, correlation_adjustment, stress_test_multiplier, metadata
            ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
            ON CONFLICT (id) DO UPDATE SET
                var_amount = EXCLUDED.var_amount,
                calculation_date = EXCLUDED.calculation_date,
                metadata = EXCLUDED.metadata
        ";

        sqlx::query(query)
            .bind(result.id)
            .bind(&result.portfolio_id)
            .bind(result.method)
            .bind(result.confidence_level)
            .bind(result.holding_period_days)
            .bind(result.lookback_days)
            .bind(result.var_amount)
            .bind(&result.currency)
            .bind(result.calculation_date)
            .bind(result.volatility)
            .bind(result.correlation_adjustment)
            .bind(result.stress_test_multiplier)
            .bind(&result.metadata)
            .execute(&self.db_pool)
            .await?;

        // Cache latest result in Redis
        let cache_key = format!("var:latest:{}", result.portfolio_id);
        let serialized = serde_json::to_string(&result)?;

        let mut redis_conn = self.redis_conn.clone();
        redis::cmd("SETEX")
            .arg(&cache_key)
            .arg(3600) // 1 hour TTL
            .arg(&serialized)
            .query_async::<()>(&mut redis_conn)
            .await?;

        Ok(())
    }

    async fn get_var_history(
        &self,
        portfolio_id: &str,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> RiskDataResult<Vec<VarResult>> {
        let query = "
            SELECT * FROM var_calculations 
            WHERE portfolio_id = $1 
            AND calculation_date BETWEEN $2 AND $3
            ORDER BY calculation_date DESC
        ";

        let results = sqlx::query_as::<_, VarResult>(query)
            .bind(portfolio_id)
            .bind(from)
            .bind(to)
            .fetch_all(&self.db_pool)
            .await?;

        Ok(results)
    }

    async fn get_latest_var(&self, portfolio_id: &str) -> RiskDataResult<Option<VarResult>> {
        // Try Redis cache first
        let cache_key = format!("var:latest:{}", portfolio_id);
        let mut redis_conn = self.redis_conn.clone();

        if let Ok(cached) = redis::cmd("GET")
            .arg(&cache_key)
            .query_async::<String>(&mut redis_conn)
            .await
        {
            if let Ok(result) = serde_json::from_str::<VarResult>(&cached) {
                return Ok(Some(result));
            }
        }

        // Fallback to database
        let query = "
            SELECT * FROM var_calculations 
            WHERE portfolio_id = $1 
            ORDER BY calculation_date DESC 
            LIMIT 1
        ";

        let result = sqlx::query_as::<_, VarResult>(query)
            .bind(portfolio_id)
            .fetch_optional(&self.db_pool)
            .await?;

        Ok(result)
    }

    async fn store_price_data(&self, prices: Vec<PriceData>) -> RiskDataResult<()> {
        if prices.is_empty() {
            return Ok(());
        }

        let mut transaction = self.db_pool.begin().await?;

        for price in prices {
            let query = "
                INSERT INTO price_history (symbol, price, timestamp, volume)
                VALUES ($1, $2, $3, $4)
                ON CONFLICT (symbol, timestamp) DO UPDATE SET
                    price = EXCLUDED.price,
                    volume = EXCLUDED.volume
            ";

            sqlx::query(query)
                .bind(&price.symbol)
                .bind(price.price)
                .bind(price.timestamp)
                .bind(price.volume)
                .execute(&mut *transaction)
                .await?;
        }

        transaction.commit().await?;
        Ok(())
    }

    async fn get_price_history(
        &self,
        symbols: Vec<String>,
        from: DateTime<Utc>,
        to: DateTime<Utc>,
    ) -> RiskDataResult<HashMap<String, Vec<PriceData>>> {
        let query = "
            SELECT symbol, price, timestamp, volume
            FROM price_history
            WHERE symbol = ANY($1)
            AND timestamp BETWEEN $2 AND $3
            ORDER BY symbol, timestamp
        ";

        let rows = sqlx::query_as::<_, PriceData>(query)
            .bind(&symbols)
            .bind(from)
            .bind(to)
            .fetch_all(&self.db_pool)
            .await?;

        let mut result = HashMap::new();
        for row in rows {
            result
                .entry(row.symbol.clone())
                .or_insert_with(Vec::new)
                .push(row);
        }

        Ok(result)
    }

    async fn get_portfolio_positions(
        &self,
        portfolio_id: &str,
    ) -> RiskDataResult<Vec<PortfolioPosition>> {
        info!("Getting portfolio positions for {}", portfolio_id);

        // Query broker_positions table (populated by fill events from trading engine)
        let rows: Vec<PortfolioPositionRow> = sqlx::query_as(
            "SELECT symbol, quantity, market_value \
             FROM broker_positions \
             WHERE account_id = $1 AND quantity != 0 \
             ORDER BY symbol",
        )
        .bind(portfolio_id)
        .fetch_all(&self.db_pool)
        .await?;

        if rows.is_empty() {
            warn!(
                portfolio_id = %portfolio_id,
                "no positions found in broker_positions table"
            );
        }

        let positions = rows
            .into_iter()
            .map(|row| PortfolioPosition {
                symbol: row.symbol,
                quantity: row.quantity,
                market_value: row.market_value.unwrap_or_default(),
                currency: "USD".to_string(),
                asset_class: "futures".to_string(),
            })
            .collect();

        Ok(positions)
    }

    async fn calculate_volatility_matrix(
        &self,
        symbols: Vec<String>,
        lookback_days: i32,
    ) -> RiskDataResult<HashMap<String, HashMap<String, Decimal>>> {
        info!(
            "Calculating volatility matrix for {} symbols",
            symbols.len()
        );

        // Get price history
        let to_date = Utc::now();
        let from_date = to_date - Duration::days(i64::from(lookback_days));

        let price_history = self
            .get_price_history(symbols.clone(), from_date, to_date)
            .await?;

        // Build per-symbol log-return series from price history
        let returns: HashMap<String, Vec<f64>> = {
            let mut map = HashMap::new();
            for symbol in &symbols {
                let prices = price_history.get(symbol).map(Vec::as_slice).unwrap_or(&[]);
                if prices.len() >= 2 {
                    let mut sym_returns = Vec::with_capacity(prices.len() - 1);
                    for i in 1..prices.len() {
                        let prev: f64 = prices[i - 1].price.try_into().unwrap_or(f64::NAN);
                        let curr: f64 = prices[i].price.try_into().unwrap_or(f64::NAN);
                        if prev > 0.0 && curr > 0.0 {
                            sym_returns.push((curr / prev).ln());
                        }
                    }
                    map.insert(symbol.clone(), sym_returns);
                }
            }
            map
        };

        // Minimum observations required for a reliable Pearson estimate
        const MIN_OBS: usize = 30;

        // Helper: compute Pearson correlation between two equal-length return slices
        let pearson = |xs: &[f64], ys: &[f64]| -> f64 {
            let n = xs.len() as f64;
            let mean_x = xs.iter().sum::<f64>() / n;
            let mean_y = ys.iter().sum::<f64>() / n;
            let cov = xs.iter().zip(ys).map(|(x, y)| (x - mean_x) * (y - mean_y)).sum::<f64>();
            let std_x = (xs.iter().map(|x| (x - mean_x).powi(2)).sum::<f64>() / n).sqrt();
            let std_y = (ys.iter().map(|y| (y - mean_y).powi(2)).sum::<f64>() / n).sqrt();
            if std_x == 0.0 || std_y == 0.0 {
                0.0
            } else {
                cov / (std_x * std_y)
            }
        };

        // Calculate correlation matrix using real Pearson correlation where data is sufficient
        let mut matrix = HashMap::new();

        for symbol in &symbols {
            let mut row = HashMap::new();
            for other_symbol in &symbols {
                let correlation = if symbol == other_symbol {
                    Decimal::ONE
                } else {
                    let xs = returns.get(symbol).map(Vec::as_slice).unwrap_or(&[]);
                    let ys = returns.get(other_symbol).map(Vec::as_slice).unwrap_or(&[]);
                    // Align lengths to the shorter series
                    let len = xs.len().min(ys.len());
                    if len >= MIN_OBS {
                        let r = pearson(&xs[..len], &ys[..len]);
                        Decimal::try_from(r).unwrap_or_else(|_| {
                            warn!(
                                "Pearson correlation conversion failed for ({}, {}), using 0.5",
                                symbol, other_symbol
                            );
                            Decimal::from_str_exact("0.5").unwrap_or(Decimal::ZERO)
                        })
                    } else {
                        warn!(
                            "Insufficient observations for ({}, {}) correlation: {} < {} minimum, using 0.5 fallback",
                            symbol, other_symbol, len, MIN_OBS
                        );
                        Decimal::from_str_exact("0.5").unwrap_or(Decimal::ZERO)
                    }
                };
                row.insert(other_symbol.clone(), correlation);
            }
            matrix.insert(symbol.clone(), row);
        }

        Ok(matrix)
    }

    async fn run_stress_test(
        &self,
        portfolio_id: &str,
        scenario_name: &str,
        stress_factors: HashMap<String, Decimal>,
    ) -> RiskDataResult<VarResult> {
        info!(
            "Running stress test '{}' for portfolio {}",
            scenario_name, portfolio_id
        );

        // Simplified stress test - apply stress factors to current VaR
        let base_var = self.get_latest_var(portfolio_id).await?.ok_or_else(|| {
            RiskDataError::VarCalculation("No base VaR found for stress test".to_owned())
        })?;

        // Apply each stress factor individually and accumulate the total impact.
        // Each factor multiplies the base VaR contribution additively beyond the baseline,
        // so the combined stressed VaR reflects all factor shocks simultaneously.
        let mut cumulative_multiplier = Decimal::ONE;
        for (factor_name, factor_value) in &stress_factors {
            info!(
                "Applying stress factor '{}' = {} to scenario '{}'",
                factor_name, factor_value, scenario_name
            );
            // Each factor shifts the multiplier by its excess above 1.0 (additive shocks)
            let excess = *factor_value - Decimal::ONE;
            cumulative_multiplier += excess;
        }
        // Ensure the multiplier is at least 1.0 so stress never reduces risk below base
        if cumulative_multiplier < Decimal::ONE {
            cumulative_multiplier = Decimal::ONE;
        }
        info!(
            "Combined stress multiplier for scenario '{}': {}",
            scenario_name, cumulative_multiplier
        );

        let stressed_var = VarResult {
            id: Uuid::new_v4(),
            var_amount: base_var.var_amount * cumulative_multiplier,
            stress_test_multiplier: Some(cumulative_multiplier),
            calculation_date: Utc::now(),
            metadata: serde_json::json!({
                "stress_scenario": scenario_name,
                "stress_factors": stress_factors,
                "base_var_id": base_var.id
            }),
            ..base_var
        };

        // Store stress test result
        self.store_var_result(stressed_var.clone()).await?;

        Ok(stressed_var)
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;

    #[test]
    fn test_confidence_level_as_decimal() {
        assert_eq!(
            ConfidenceLevel::P95.as_decimal(),
            Decimal::from_str_exact("0.95").unwrap()
        );
        assert_eq!(
            ConfidenceLevel::P99.as_decimal(),
            Decimal::from_str_exact("0.99").unwrap()
        );
    }

    #[test]
    fn test_var_request_serialization() {
        let request = VarRequest {
            portfolio_id: "test_portfolio".to_string(),
            method: VarMethod::Historical,
            confidence_level: ConfidenceLevel::P95,
            holding_period_days: 1,
            lookback_days: 252,
            currency: "USD".to_string(),
        };

        let json = serde_json::to_string(&request).unwrap();
        let deserialized: VarRequest = serde_json::from_str(&json).unwrap();

        assert_eq!(request.portfolio_id, deserialized.portfolio_id);
        assert_eq!(request.method, deserialized.method);
    }
}
