//! VaR Extreme Scenario Tests
//! Target: Edge cases with extreme market conditions
//! Focus: Fat tails, perfect correlation, NaN/infinity handling

#![allow(
    unused_crate_dependencies,
    clippy::doc_markdown,
    clippy::indexing_slicing,
    clippy::shadow_reuse,
    clippy::unwrap_used
)]

use std::collections::HashMap;

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

    #[test]
    fn test_var_with_extreme_outliers() {
        let returns = vec![
            0.01, 0.015, 0.012, 0.008, 0.009, // Normal returns
            -0.80, // Extreme outlier (80% crash)
            0.011, 0.013, 0.010, 0.012,
        ];

        let var = calculate_var(&returns, 0.95);

        // VaR should be dominated by outlier
        assert!(var > 0.10);
        assert!(var.is_finite());
    }

    #[test]
    fn test_var_with_multiple_fat_tail_events() {
        let returns = vec![
            0.01, 0.02, 0.015, -0.30, // Black Monday
            0.01, 0.02, -0.25, // Flash Crash
            0.015, 0.01, -0.40, // Black Swan
            0.02,
        ];

        let var = calculate_var(&returns, 0.99);

        // Multiple tail events should result in very high VaR
        assert!(var > 0.20);
    }

    #[test]
    fn test_var_kurtosis_impact() {
        // High kurtosis (fat tails) distribution
        let fat_tails = vec![
            0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.50, -0.40, // Extreme events
        ];

        // Normal distribution
        let normal = vec![
            -0.01, -0.02, -0.015, -0.018, -0.012, -0.014, -0.016, -0.019, -0.011, -0.013, -0.017,
            -0.020,
        ];

        let var_fat = calculate_var(&fat_tails, 0.95);
        let var_normal = calculate_var(&normal, 0.95);

        // Fat tail VaR should be much higher
        assert!(var_fat > var_normal);
    }

    #[test]
    fn test_var_with_power_law_tails() {
        // Simulate power law distribution (Pareto-like)
        let mut returns = Vec::new();
        for i in 1..=100 {
            let ret = -(1.0 / (i as f64).powf(0.5)) * 0.1;
            returns.push(ret);
        }

        let var = calculate_var(&returns, 0.95);
        assert!(var > 0.0);
        assert!(var.is_finite());
    }
}

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

    #[test]
    fn test_var_perfect_positive_correlation() {
        let mut portfolio = HashMap::new();

        // Two assets moving identically (correlation = 1.0)
        let returns_a = vec![0.02, -0.01, 0.03, -0.02, 0.015];
        let returns_b = vec![0.02, -0.01, 0.03, -0.02, 0.015];

        portfolio.insert("ASSET_A".to_owned(), returns_a);
        portfolio.insert("ASSET_B".to_owned(), returns_b);

        let var = calculate_portfolio_var(&portfolio, 0.95);

        // Perfect correlation means no diversification benefit
        assert!(var > 0.0);
    }

    #[test]
    fn test_var_perfect_negative_correlation() {
        let mut portfolio = HashMap::new();

        // Two assets moving inversely (correlation = -1.0)
        let returns_a = vec![0.02, -0.01, 0.03, -0.02, 0.015];
        let returns_b = vec![-0.02, 0.01, -0.03, 0.02, -0.015];

        portfolio.insert("ASSET_A".to_owned(), returns_a);
        portfolio.insert("ASSET_B".to_owned(), returns_b);

        let var = calculate_portfolio_var(&portfolio, 0.95);

        // Perfect negative correlation should result in very low VaR
        assert!(var >= 0.0);
        assert!(var < 0.01); // Near-zero due to hedging
    }

    #[test]
    fn test_var_correlation_breakdown() {
        let mut portfolio = HashMap::new();

        // Assets normally uncorrelated, but correlate in crisis
        let returns_a = vec![0.01, 0.02, 0.015, -0.30, -0.25];
        let returns_b = vec![-0.01, 0.01, -0.02, -0.28, -0.27];

        portfolio.insert("STOCK_A".to_owned(), returns_a);
        portfolio.insert("STOCK_B".to_owned(), returns_b);

        let var = calculate_portfolio_var(&portfolio, 0.95);

        // Crisis correlation should result in high VaR
        assert!(var > 0.10);
    }
}

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

    #[test]
    fn test_var_zero_variance_portfolio() {
        let returns = vec![0.02; 100]; // All identical returns

        let var = calculate_var(&returns, 0.95);

        // Zero variance should result in zero or near-zero VaR
        assert!(var < 0.001);
        assert!(var.is_finite());
    }

    #[test]
    fn test_var_near_zero_variance() {
        let mut returns = vec![0.02; 99];
        returns.push(0.020001); // Tiny variation

        let var = calculate_var(&returns, 0.95);

        assert!(var < 0.001);
    }

    #[test]
    fn test_var_zero_variance_multiple_assets() {
        let mut portfolio = HashMap::new();
        portfolio.insert("STABLE_A".to_owned(), vec![0.01; 50]);
        portfolio.insert("STABLE_B".to_owned(), vec![0.015; 50]);

        let var = calculate_portfolio_var(&portfolio, 0.95);

        // Multiple zero-variance assets
        assert!(var < 0.001);
    }
}

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

    #[test]
    fn test_var_with_negative_prices_oil_futures() {
        // Simulate negative oil prices (2020 scenario)
        let returns = vec![
            0.05, 0.03, -0.10, -0.20, -1.50, // Going below zero
            -0.30, 0.50, 0.30, 0.20,
        ];

        let var = calculate_var(&returns, 0.95);

        // Should handle extreme negative returns
        assert!(var > 0.30);
        assert!(var.is_finite());
    }

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

        let var = calculate_var(&returns, 0.95);

        // All losses should give high VaR
        assert!(var > 0.01);
    }
}

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

    #[test]
    fn test_var_with_nan_values() {
        let returns = vec![0.01, 0.02, f64::NAN, 0.03, 0.015];

        let result = validate_returns(&returns);

        // Should detect NaN
        assert!(result.is_err() || !result.unwrap());
    }

    #[test]
    fn test_var_with_positive_infinity() {
        let returns = vec![0.01, 0.02, f64::INFINITY, 0.03];

        let result = validate_returns(&returns);

        // Should detect infinity
        assert!(result.is_err() || !result.unwrap());
    }

    #[test]
    fn test_var_with_negative_infinity() {
        let returns = vec![0.01, f64::NEG_INFINITY, 0.02];

        let result = validate_returns(&returns);

        assert!(result.is_err() || !result.unwrap());
    }

    #[test]
    fn test_var_calculation_produces_nan() {
        // Edge case that might produce NaN in calculation
        let returns = vec![0.0; 5];

        let var = calculate_var(&returns, 0.95);

        // Should not produce NaN
        assert!(var.is_finite());
    }

    #[test]
    fn test_var_with_all_nan() {
        let returns = vec![f64::NAN; 10];

        let result = validate_returns(&returns);

        assert!(result.is_err() || !result.unwrap());
    }
}

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

    #[test]
    fn test_var_empty_history() {
        let returns: Vec<f64> = Vec::new();

        let result = calculate_var(&returns, 0.95);

        // Should handle empty data
        assert_eq!(result, 0.0); // Or error, depending on implementation
    }

    #[test]
    fn test_var_single_observation() {
        let returns = vec![0.02];

        let var = calculate_var(&returns, 0.95);

        // Single observation case
        assert!(var >= 0.0);
    }

    #[test]
    fn test_var_two_observations() {
        let returns = vec![0.02, -0.01];

        let var = calculate_var(&returns, 0.95);

        assert!(var >= 0.0);
    }

    #[test]
    fn test_var_insufficient_for_confidence_level() {
        // Not enough data for 99th percentile
        let returns = vec![0.01, 0.02, 0.015, 0.018];

        let var_99 = calculate_var(&returns, 0.99);

        // Should extrapolate or use conservative estimate
        assert!(var_99 >= 0.0);
    }
}

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

    #[test]
    fn test_var_single_asset_concentrated() {
        let mut portfolio = HashMap::new();
        portfolio.insert("ONLY_ASSET".to_owned(), vec![0.02, -0.05, 0.03, -0.02]);

        let var = calculate_portfolio_var(&portfolio, 0.95);

        // No diversification benefit
        assert!(var > 0.0);
    }

    #[test]
    fn test_var_single_volatile_asset() {
        let mut portfolio = HashMap::new();
        let volatile_returns = vec![0.10, -0.08, 0.12, -0.15, 0.09];
        portfolio.insert("VOLATILE".to_owned(), volatile_returns);

        let var = calculate_portfolio_var(&portfolio, 0.95);

        // High volatility should give high VaR
        assert!(var > 0.05);
    }

    #[test]
    fn test_var_concentration_risk() {
        let mut concentrated = HashMap::new();
        concentrated.insert("MAIN".to_owned(), vec![0.05, -0.03, 0.04]);
        let var_concentrated = calculate_portfolio_var(&concentrated, 0.95);

        let mut diversified = HashMap::new();
        diversified.insert("ASSET1".to_owned(), vec![0.01, -0.01, 0.015]);
        diversified.insert("ASSET2".to_owned(), vec![0.02, 0.01, -0.01]);
        diversified.insert("ASSET3".to_owned(), vec![-0.01, 0.02, 0.01]);
        let var_diversified = calculate_portfolio_var(&diversified, 0.95);

        // Concentrated should have relatively higher VaR
        // (This is a qualitative test, actual values depend on implementation)
        assert!(var_concentrated >= 0.0);
        assert!(var_diversified >= 0.0);
    }
}

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

    #[test]
    fn test_var_confidence_99_99() {
        let returns = vec![0.01, -0.02, 0.015, -0.01, 0.02, 0.012, -0.015];

        let var = calculate_var(&returns, 0.9999);

        // Very high confidence should give high VaR
        assert!(var > 0.0);
    }

    #[test]
    fn test_var_confidence_50_percent() {
        let returns = vec![0.01, -0.02, 0.015, -0.01, 0.02];

        let var_50 = calculate_var(&returns, 0.50);
        let var_95 = calculate_var(&returns, 0.95);

        // 50% confidence should be much lower than 95%
        assert!(var_50 < var_95);
    }

    #[test]
    fn test_var_confidence_boundaries() {
        let returns = vec![0.01, -0.02, 0.015, -0.01, 0.02];

        // Test near 0% and 100% confidence
        let var_01 = calculate_var(&returns, 0.01);
        let var_99_9 = calculate_var(&returns, 0.999);

        assert!(var_01 < var_99_9);
    }
}

// Helper functions for tests

fn calculate_var(returns: &[f64], confidence: f64) -> f64 {
    if returns.is_empty() {
        return 0.0;
    }

    let mut sorted = returns.to_vec();
    sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());

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

    (-sorted[index]).max(0.0)
}

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

    let all_returns: Vec<f64> = portfolio.values().flat_map(|v| v.iter().copied()).collect();

    calculate_var(&all_returns, confidence)
}

fn validate_returns(returns: &[f64]) -> Result<bool, String> {
    for &r in returns {
        if r.is_nan() {
            return Err("NaN detected".to_owned());
        }
        if r.is_infinite() {
            return Err("Infinity detected".to_owned());
        }
    }
    Ok(true)
}
