//! Comprehensive `VaR` calculation edge case tests
//! Target: 95%+ coverage for `VaR` calculations and risk edge cases

#![allow(
    dead_code,
    unused_assignments,
    unused_crate_dependencies,
    clippy::indexing_slicing,
    clippy::shadow_reuse,
    clippy::similar_names,
    clippy::unnecessary_wraps
)]

use std::collections::HashMap;

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

    #[test]
    fn test_var_with_empty_returns() {
        let returns_data: HashMap<String, Vec<f64>> = HashMap::new();
        let result = calculate_parametric_var(&returns_data, 0.95);

        // Should handle empty data gracefully
        assert!(result.is_err() || result.unwrap() == 0.0);
    }

    #[test]
    fn test_var_with_single_asset() {
        let mut returns_data = HashMap::new();
        returns_data.insert("AAPL".to_owned(), vec![0.01, -0.02, 0.015, -0.01, 0.02]);

        let var = calculate_parametric_var(&returns_data, 0.95).unwrap();
        assert!(var > 0.0);
        assert!(var.is_finite());
    }

    #[test]
    fn test_var_with_zero_volatility() {
        let mut returns_data = HashMap::new();
        // All returns are identical (zero volatility)
        returns_data.insert("STABLE".to_owned(), vec![0.01, 0.01, 0.01, 0.01, 0.01]);

        let var = calculate_parametric_var(&returns_data, 0.95).unwrap();
        // VaR should be very small or zero
        assert!(var >= 0.0);
        assert!(var < 0.001);
    }

    #[test]
    fn test_var_with_extreme_returns() {
        let mut returns_data = HashMap::new();
        // Extreme market crash scenario
        returns_data.insert("CRASH".to_owned(), vec![-0.20, -0.30, -0.15, -0.25, -0.10]);

        let var = calculate_parametric_var(&returns_data, 0.95).unwrap();
        assert!(var > 0.1); // High VaR expected
        assert!(var.is_finite());
    }

    #[test]
    fn test_var_with_mixed_correlations() {
        let mut returns_data = HashMap::new();
        returns_data.insert("TECH".to_owned(), vec![0.02, -0.01, 0.03, -0.02, 0.015]);
        returns_data.insert("UTIL".to_owned(), vec![-0.01, 0.015, -0.02, 0.01, -0.005]);
        returns_data.insert("BOND".to_owned(), vec![0.005, -0.002, 0.003, 0.001, 0.002]);

        let var = calculate_parametric_var(&returns_data, 0.95).unwrap();
        assert!(var > 0.0);
        assert!(var.is_finite());
    }

    #[test]
    fn test_var_different_confidence_levels() {
        let mut returns_data = HashMap::new();
        returns_data.insert("SPY".to_owned(), vec![0.01, -0.02, 0.015, -0.01, 0.02]);

        let var_90 = calculate_parametric_var(&returns_data, 0.90).unwrap();
        let var_95 = calculate_parametric_var(&returns_data, 0.95).unwrap();
        let var_99 = calculate_parametric_var(&returns_data, 0.99).unwrap();

        // Higher confidence should yield higher VaR
        assert!(var_95 > var_90);
        assert!(var_99 > var_95);
    }

    #[test]
    fn test_var_with_outliers() {
        let mut returns_data = HashMap::new();
        // Most returns normal, one extreme outlier
        returns_data.insert("OUTLIER".to_owned(), vec![0.01, 0.015, 0.012, -0.50, 0.013]);

        let var = calculate_parametric_var(&returns_data, 0.95).unwrap();
        // Outlier should significantly impact VaR
        assert!(var > 0.05);
    }

    #[test]
    fn test_var_with_insufficient_data() {
        let mut returns_data = HashMap::new();
        // Only 2 data points
        returns_data.insert("LOWDATA".to_owned(), vec![0.01, -0.02]);

        let result = calculate_parametric_var(&returns_data, 0.95);
        // Should handle insufficient data gracefully
        assert!(result.is_ok() || result.is_err());
    }

    #[test]
    fn test_var_with_nan_returns() {
        let mut returns_data = HashMap::new();
        returns_data.insert("NAN".to_owned(), vec![0.01, f64::NAN, 0.02]);

        let result = calculate_parametric_var(&returns_data, 0.95);
        // Should reject NaN values
        assert!(result.is_err() || !result.unwrap().is_nan());
    }

    #[test]
    fn test_var_with_infinite_returns() {
        let mut returns_data = HashMap::new();
        returns_data.insert("INF".to_owned(), vec![0.01, f64::INFINITY, 0.02]);

        let result = calculate_parametric_var(&returns_data, 0.95);
        // Should reject infinite values
        assert!(result.is_err() || result.unwrap().is_finite());
    }
}

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

    #[test]
    fn test_historical_var_with_sorted_data() {
        let returns = vec![-0.05, -0.03, -0.02, -0.01, 0.0, 0.01, 0.02, 0.03, 0.05];
        let var = calculate_historical_var(&returns, 0.95).unwrap();

        assert!(var > 0.0);
        assert!(var <= 0.05);
    }

    #[test]
    fn test_historical_var_with_unsorted_data() {
        let returns = vec![0.02, -0.03, 0.01, -0.05, 0.03, -0.01, -0.02, 0.0, 0.05];
        let var = calculate_historical_var(&returns, 0.95).unwrap();

        assert!(var > 0.0);
        assert!(var.is_finite());
    }

    #[test]
    fn test_historical_var_all_positive_returns() {
        let returns = vec![0.01, 0.02, 0.015, 0.03, 0.025];
        let var = calculate_historical_var(&returns, 0.95).unwrap();

        // Even with all positive returns, VaR should be calculated
        assert!(var >= 0.0);
    }

    #[test]
    fn test_historical_var_all_negative_returns() {
        let returns = vec![-0.01, -0.02, -0.015, -0.03, -0.025];
        let var = calculate_historical_var(&returns, 0.95).unwrap();

        // All losses should result in significant VaR
        assert!(var > 0.01);
    }

    #[test]
    fn test_historical_var_single_observation() {
        let returns = vec![0.02];
        let result = calculate_historical_var(&returns, 0.95);

        // Should handle single observation
        assert!(result.is_ok() || result.is_err());
    }

    #[test]
    fn test_historical_var_percentile_calculation() {
        let returns: Vec<f64> = (1..=100).map(|i| i as f64 / 1000.0 - 0.05).collect();
        let var_95 = calculate_historical_var(&returns, 0.95).unwrap();
        let var_99 = calculate_historical_var(&returns, 0.99).unwrap();

        // Higher percentile should give larger VaR
        assert!(var_99 > var_95);
    }
}

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

    #[test]
    fn test_monte_carlo_var_convergence() {
        let params = MonteCarloParams {
            num_simulations: 10000,
            time_horizon: 1,
            confidence_level: 0.95,
        };

        let var = calculate_monte_carlo_var(&params).unwrap();
        assert!(var > 0.0);
        assert!(var.is_finite());
    }

    #[test]
    fn test_monte_carlo_var_different_horizons() {
        let params_1d = MonteCarloParams {
            num_simulations: 1000,
            time_horizon: 1,
            confidence_level: 0.95,
        };

        let params_10d = MonteCarloParams {
            num_simulations: 1000,
            time_horizon: 10,
            confidence_level: 0.95,
        };

        let var_1d = calculate_monte_carlo_var(&params_1d).unwrap();
        let var_10d = calculate_monte_carlo_var(&params_10d).unwrap();

        // Longer horizon should generally result in higher VaR
        assert!(var_10d >= var_1d);
    }

    #[test]
    fn test_monte_carlo_var_few_simulations() {
        let params = MonteCarloParams {
            num_simulations: 10, // Very few simulations
            time_horizon: 1,
            confidence_level: 0.95,
        };

        let result = calculate_monte_carlo_var(&params);
        // Should either work with warning or require minimum simulations
        assert!(result.is_ok() || result.is_err());
    }

    #[test]
    fn test_monte_carlo_var_many_simulations() {
        let params = MonteCarloParams {
            num_simulations: 100000, // Very many simulations
            time_horizon: 1,
            confidence_level: 0.95,
        };

        let var = calculate_monte_carlo_var(&params).unwrap();
        assert!(var > 0.0);
        assert!(var.is_finite());
    }
}

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

    #[test]
    fn test_expected_shortfall_relationship_to_var() {
        let returns = vec![-0.05, -0.03, -0.02, -0.01, 0.0, 0.01, 0.02, 0.03, 0.05];

        let var = calculate_historical_var(&returns, 0.95).unwrap();
        let es = calculate_expected_shortfall(&returns, 0.95).unwrap();

        // Expected Shortfall should be >= VaR
        assert!(es >= var);
    }

    #[test]
    fn test_expected_shortfall_extreme_losses() {
        let returns = vec![-0.10, -0.15, -0.20, -0.08, -0.05, 0.01, 0.02, 0.03];

        let es = calculate_expected_shortfall(&returns, 0.95).unwrap();

        // ES should capture extreme losses
        assert!(es > 0.05);
    }

    #[test]
    fn test_expected_shortfall_no_tail_losses() {
        let returns = vec![0.01, 0.02, 0.015, 0.03, 0.025];

        let es = calculate_expected_shortfall(&returns, 0.95).unwrap();

        // Even with no tail losses, should compute
        assert!(es >= 0.0);
    }
}

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

    #[test]
    fn test_portfolio_var_diversification_benefit() {
        let mut single_asset = HashMap::new();
        single_asset.insert("STOCK".to_owned(), 100000.0);

        let mut diversified = HashMap::new();
        diversified.insert("STOCK".to_owned(), 50000.0);
        diversified.insert("BOND".to_owned(), 50000.0);

        // Assuming uncorrelated assets with similar volatility
        let var_single = calculate_portfolio_var(&single_asset, 0.95).unwrap();
        let var_diversified = calculate_portfolio_var(&diversified, 0.95).unwrap();

        // Diversified portfolio should have lower VaR (in proportion to value)
        assert!(var_diversified < var_single * 1.5);
    }

    #[test]
    fn test_portfolio_var_zero_positions() {
        let empty_portfolio = HashMap::new();
        let var = calculate_portfolio_var(&empty_portfolio, 0.95).unwrap();

        assert_eq!(var, 0.0);
    }

    #[test]
    fn test_portfolio_var_negative_positions() {
        let mut portfolio = HashMap::new();
        portfolio.insert("LONG".to_owned(), 100000.0);
        portfolio.insert("SHORT".to_owned(), -50000.0);

        let var = calculate_portfolio_var(&portfolio, 0.95).unwrap();
        assert!(var > 0.0);
    }

    #[test]
    fn test_portfolio_var_large_concentrated_position() {
        let mut portfolio = HashMap::new();
        portfolio.insert("MAIN".to_owned(), 900000.0);
        portfolio.insert("SMALL1".to_owned(), 50000.0);
        portfolio.insert("SMALL2".to_owned(), 50000.0);

        let var = calculate_portfolio_var(&portfolio, 0.95).unwrap();

        // Concentration should result in higher relative VaR
        assert!(var > 0.0);
    }
}

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

    #[test]
    fn test_position_limit_validation_within_bounds() {
        let position_size = 50000.0;
        let position_limit = 100000.0;

        assert!(validate_position_limit(position_size, position_limit));
    }

    #[test]
    fn test_position_limit_validation_exceeds_limit() {
        let position_size = 150000.0;
        let position_limit = 100000.0;

        assert!(!validate_position_limit(position_size, position_limit));
    }

    #[test]
    fn test_position_limit_validation_exact_limit() {
        let position_size = 100000.0;
        let position_limit = 100000.0;

        // At exact limit should be valid
        assert!(validate_position_limit(position_size, position_limit));
    }

    #[test]
    fn test_position_limit_validation_negative_position() {
        let position_size: f64 = -50000.0; // Short position
        let position_limit = 100000.0;

        // Absolute value should be checked
        assert!(validate_position_limit(position_size.abs(), position_limit));
    }

    #[test]
    fn test_var_limit_validation() {
        let portfolio_var = 25000.0;
        let var_limit = 50000.0;

        assert!(validate_var_limit(portfolio_var, var_limit));
    }

    #[test]
    fn test_var_limit_validation_exceeds() {
        let portfolio_var = 75000.0;
        let var_limit = 50000.0;

        assert!(!validate_var_limit(portfolio_var, var_limit));
    }
}

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

    #[test]
    fn test_stress_scenario_market_crash() {
        let scenario = StressScenario {
            name: "Market Crash".to_owned(),
            shock_percentage: -0.20,
        };

        let initial_value = 1000000.0;
        let stressed_value = apply_stress_scenario(initial_value, &scenario);

        assert_eq!(stressed_value, 800000.0);
    }

    #[test]
    fn test_stress_scenario_rally() {
        let scenario = StressScenario {
            name: "Bull Rally".to_owned(),
            shock_percentage: 0.15,
        };

        let initial_value = 1000000.0;
        let stressed_value = apply_stress_scenario(initial_value, &scenario);

        assert_eq!(stressed_value, 1150000.0);
    }

    #[test]
    fn test_stress_scenario_extreme_crash() {
        let scenario = StressScenario {
            name: "Black Swan".to_owned(),
            shock_percentage: -0.50,
        };

        let initial_value = 1000000.0;
        let stressed_value = apply_stress_scenario(initial_value, &scenario);

        assert_eq!(stressed_value, 500000.0);
        assert!(stressed_value > 0.0); // Should never go negative
    }

    #[test]
    fn test_stress_scenario_total_loss() {
        let scenario = StressScenario {
            name: "Total Loss".to_owned(),
            shock_percentage: -1.0,
        };

        let initial_value = 1000000.0;
        let stressed_value = apply_stress_scenario(initial_value, &scenario);

        assert_eq!(stressed_value, 0.0);
    }
}

// Helper functions for tests

fn calculate_parametric_var(
    returns_data: &HashMap<String, Vec<f64>>,
    confidence: f64,
) -> Result<f64, String> {
    if returns_data.is_empty() {
        return Err("No returns data provided".to_owned());
    }

    // Simplified parametric VaR calculation
    let all_returns: Vec<f64> = returns_data.values().flatten().copied().collect();

    if all_returns.iter().any(|r| r.is_nan() || r.is_infinite()) {
        return Err("Invalid return values".to_owned());
    }

    let mean = all_returns.iter().sum::<f64>() / all_returns.len() as f64;
    let variance =
        all_returns.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / all_returns.len() as f64;
    let std_dev = variance.sqrt();

    // Z-score for confidence level (approximation)
    let z_score = match confidence {
        x if x >= 0.99 => 2.33,
        x if x >= 0.95 => 1.65,
        x if x >= 0.90 => 1.28,
        _ => 1.0,
    };

    Ok((z_score * std_dev - mean).abs())
}

fn calculate_historical_var(returns: &[f64], confidence: f64) -> Result<f64, String> {
    if returns.is_empty() {
        return Err("No returns provided".to_owned());
    }

    let mut sorted_returns = returns.to_vec();
    sorted_returns.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));

    let index = ((1.0 - confidence) * sorted_returns.len() as f64) as usize;
    let index = index.min(sorted_returns.len() - 1);

    Ok((-sorted_returns[index]).max(0.0))
}

fn calculate_monte_carlo_var(params: &MonteCarloParams) -> Result<f64, String> {
    if params.num_simulations < 1 {
        return Err("Number of simulations must be positive".to_owned());
    }

    // Simplified Monte Carlo VaR
    let mut simulated_returns = Vec::new();
    for _ in 0..params.num_simulations {
        // Simplified: normal distribution with mean 0, std 0.02
        let ret = (rand::random::<f64>() - 0.5) * 0.04 * (params.time_horizon as f64).sqrt();
        simulated_returns.push(ret);
    }

    calculate_historical_var(&simulated_returns, params.confidence_level)
}

fn calculate_expected_shortfall(returns: &[f64], confidence: f64) -> Result<f64, String> {
    let var = calculate_historical_var(returns, confidence)?;

    let losses: Vec<f64> = returns.iter().copied().filter(|r| -r >= var).collect();

    if losses.is_empty() {
        return Ok(var);
    }

    let es = -losses.iter().sum::<f64>() / losses.len() as f64;
    Ok(es.max(var))
}

fn calculate_portfolio_var(
    positions: &HashMap<String, f64>,
    confidence: f64,
) -> Result<f64, String> {
    if positions.is_empty() {
        return Ok(0.0);
    }

    let total_value: f64 = positions.values().map(|v| v.abs()).sum();
    // Simplified: assume 2% daily volatility
    let volatility = 0.02;
    let z_score = if confidence >= 0.95 { 1.65 } else { 1.28 };

    Ok(total_value * volatility * z_score)
}

fn validate_position_limit(position_size: f64, limit: f64) -> bool {
    position_size.abs() <= limit
}

fn validate_var_limit(portfolio_var: f64, var_limit: f64) -> bool {
    portfolio_var <= var_limit
}

fn apply_stress_scenario(value: f64, scenario: &StressScenario) -> f64 {
    (value * (1.0 + scenario.shock_percentage)).max(0.0)
}

struct MonteCarloParams {
    num_simulations: usize,
    time_horizon: usize,
    confidence_level: f64,
}

struct StressScenario {
    name: String,
    shock_percentage: f64,
}
