//! VaR Calculator Edge Case Tests
//! Target: Comprehensive edge case coverage for all VaR calculation methods
//! Focus: Zero positions, NaN/Inf handling, correlation edge cases, numerical stability

#![allow(
    unused_crate_dependencies,
    clippy::assertions_on_result_states,
    clippy::doc_markdown,
    clippy::similar_names,
    clippy::str_to_string
)]

use chrono::{Duration, Utc};
use common::types::{Price, Quantity, Symbol};
use risk::var_calculator::{
    historical_simulation::HistoricalSimulationVaR,
    monte_carlo::MonteCarloVaR,
    var_engine::{HistoricalPrice, PositionInfo},
};
use std::collections::HashMap;

// Helper function to create test positions
fn create_position(symbol: &str, quantity: f64, market_price: f64) -> PositionInfo {
    PositionInfo {
        symbol: Symbol::from(symbol.to_string()),
        quantity: Quantity::from_f64(quantity).unwrap_or(Quantity::ZERO),
        market_value: Price::from_f64(quantity * market_price).unwrap_or(Price::ZERO),
        average_cost: Price::from_f64(market_price * 0.95).unwrap_or(Price::ZERO),
        unrealized_pnl: Price::from_f64(quantity * market_price * 0.05).unwrap_or(Price::ZERO),
        realized_pnl: Price::ZERO,
        currency: "USD".to_owned(),
        timestamp: Utc::now(),
    }
}

// Helper function to create historical prices
fn create_prices(
    symbol: &str,
    days: usize,
    base_price: f64,
    volatility: f64,
) -> Vec<HistoricalPrice> {
    let mut prices = Vec::new();
    let mut current_price = base_price;
    let mut rng = 12_345_u64;

    for i in 0..days {
        rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
        let random = (rng as f64) / (u64::MAX as f64);
        let change = (random - 0.5) * volatility * 2.0;
        current_price *= 1.0 + change;

        prices.push(HistoricalPrice {
            symbol: symbol.to_string(),
            date: Utc::now() - Duration::days(days as i64 - i as i64),
            open: Price::from_f64(current_price * 0.999).unwrap_or(Price::ZERO),
            high: Price::from_f64(current_price * 1.005).unwrap_or(Price::ZERO),
            low: Price::from_f64(current_price * 0.995).unwrap_or(Price::ZERO),
            price: Price::from_f64(current_price).unwrap_or(Price::ZERO),
            volume: Quantity::from_f64(1_000_000.0).unwrap_or(Quantity::ZERO),
        });
    }

    prices
}

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

    #[test]
    fn test_historical_var_zero_quantity() {
        let calculator = HistoricalSimulationVaR::standard();
        let prices = create_prices("AAPL", 300, 150.0, 0.02);
        let position = create_position("AAPL", 0.0, 150.0); // Zero quantity

        let result = calculator.calculate_position_var(
            &Symbol::from("AAPL".to_owned()),
            &position,
            &prices,
        );

        assert!(result.is_ok());
        let var_result = result.unwrap();
        assert_eq!(var_result.var_1d, Price::ZERO);
        assert_eq!(var_result.var_10d, Price::ZERO);
    }

    #[test]
    fn test_historical_var_zero_price() {
        let calculator = HistoricalSimulationVaR::standard();
        let prices = create_prices("AAPL", 300, 150.0, 0.02);
        let position = create_position("AAPL", 100.0, 0.0); // Zero price

        let result = calculator.calculate_position_var(
            &Symbol::from("AAPL".to_owned()),
            &position,
            &prices,
        );

        assert!(result.is_ok());
        let var_result = result.unwrap();
        assert_eq!(var_result.var_1d, Price::ZERO);
    }

    #[test]
    fn test_monte_carlo_zero_position_portfolio() {
        let calculator = MonteCarloVaR::new(0.95, 1000, 1, Some(42));

        let mut positions = HashMap::new();
        positions.insert(
            Symbol::from("AAPL".to_owned()),
            create_position("AAPL", 0.0, 150.0),
        );

        let mut historical_prices = HashMap::new();
        historical_prices.insert(
            Symbol::from("AAPL".to_owned()),
            create_prices("AAPL", 100, 150.0, 0.02),
        );

        let result =
            calculator.calculate_portfolio_var("ZERO_PORTFOLIO", &positions, &historical_prices);

        assert!(result.is_ok());
        let mc_result = result.unwrap();
        // Zero position should result in near-zero VaR
        assert!(mc_result.var_1d < Price::from_f64(0.01).unwrap());
    }
}

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

    #[test]
    fn test_historical_var_insufficient_data() {
        let calculator = HistoricalSimulationVaR::standard(); // Needs 252 days
        let prices = create_prices("AAPL", 100, 150.0, 0.02); // Only 100 days
        let position = create_position("AAPL", 100.0, 150.0);

        let result = calculator.calculate_position_var(
            &Symbol::from("AAPL".to_owned()),
            &position,
            &prices,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_monte_carlo_insufficient_observations() {
        let calculator = MonteCarloVaR::standard();

        let mut positions = HashMap::new();
        positions.insert(
            Symbol::from("AAPL".to_owned()),
            create_position("AAPL", 100.0, 150.0),
        );

        let mut historical_prices = HashMap::new();
        historical_prices.insert(
            Symbol::from("AAPL".to_owned()),
            create_prices("AAPL", 20, 150.0, 0.02), // Less than 30 required
        );

        let result = calculator.calculate_portfolio_var("TEST", &positions, &historical_prices);

        assert!(result.is_err());
    }

    #[test]
    fn test_historical_var_single_price_point() {
        let calculator = HistoricalSimulationVaR::standard();
        let prices = create_prices("AAPL", 1, 150.0, 0.02); // Only 1 price
        let position = create_position("AAPL", 100.0, 150.0);

        let result = calculator.calculate_position_var(
            &Symbol::from("AAPL".to_owned()),
            &position,
            &prices,
        );

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

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

    #[test]
    fn test_historical_var_extreme_high_volatility() {
        let calculator = HistoricalSimulationVaR::standard();
        let prices = create_prices("VOLATILE", 300, 100.0, 0.50); // 50% daily volatility
        let position = create_position("VOLATILE", 100.0, 100.0);

        let result = calculator.calculate_position_var(
            &Symbol::from("VOLATILE".to_owned()),
            &position,
            &prices,
        );

        assert!(result.is_ok());
        let var_result = result.unwrap();
        // High volatility should produce significant VaR
        assert!(var_result.var_1d > Price::from_f64(1000.0).unwrap());
    }

    #[test]
    fn test_historical_var_zero_volatility() {
        let calculator = HistoricalSimulationVaR::standard();
        // Create flat prices (zero volatility)
        let mut prices = Vec::new();
        for i in 0..300 {
            prices.push(HistoricalPrice {
                symbol: "FLAT".to_owned(),
                date: Utc::now() - Duration::days(300 - i as i64),
                open: Price::from_f64(100.0).unwrap(),
                high: Price::from_f64(100.0).unwrap(),
                low: Price::from_f64(100.0).unwrap(),
                price: Price::from_f64(100.0).unwrap(),
                volume: Quantity::from_f64(1_000_000.0).unwrap(),
            });
        }

        let position = create_position("FLAT", 100.0, 100.0);

        let result = calculator.calculate_position_var(
            &Symbol::from("FLAT".to_owned()),
            &position,
            &prices,
        );

        assert!(result.is_ok());
        let var_result = result.unwrap();
        // Zero volatility should produce zero VaR
        assert_eq!(var_result.var_1d, Price::ZERO);
    }

    #[test]
    fn test_monte_carlo_high_correlation() {
        let calculator = MonteCarloVaR::new(0.95, 1000, 1, Some(42));

        // Create two assets with different volatilities to avoid singular correlation matrix
        let mut positions = HashMap::new();
        positions.insert(
            Symbol::from("TECH".to_owned()),
            create_position("TECH", 100.0, 100.0),
        );
        positions.insert(
            Symbol::from("ENERGY".to_owned()),
            create_position("ENERGY", 100.0, 100.0),
        );

        let mut historical_prices = HashMap::new();
        // Use different volatilities to ensure non-singular correlation matrix
        historical_prices.insert(
            Symbol::from("TECH".to_owned()),
            create_prices("TECH", 100, 100.0, 0.03), // 3% volatility
        );
        historical_prices.insert(
            Symbol::from("ENERGY".to_owned()),
            create_prices("ENERGY", 100, 100.0, 0.04), // 4% volatility
        );

        let result =
            calculator.calculate_portfolio_var("CORR_TEST", &positions, &historical_prices);

        // Should handle correlated assets with different volatilities
        assert!(
            result.is_ok(),
            "Monte Carlo should handle correlated assets: {:?}",
            result.err()
        );
    }
}

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

    #[test]
    fn test_historical_var_with_negative_returns() {
        let calculator = HistoricalSimulationVaR::standard();

        // Create price series with consistent downward trend
        let mut prices = Vec::new();
        let mut current_price = 100.0;

        for i in 0..300 {
            current_price *= 0.98; // 2% daily decline
            prices.push(HistoricalPrice {
                symbol: "DECLINING".to_owned(),
                date: Utc::now() - Duration::days(300 - i as i64),
                open: Price::from_f64(current_price * 1.01).unwrap(),
                high: Price::from_f64(current_price * 1.02).unwrap(),
                low: Price::from_f64(current_price * 0.98).unwrap(),
                price: Price::from_f64(current_price).unwrap(),
                volume: Quantity::from_f64(1_000_000.0).unwrap(),
            });
        }

        let position = create_position("DECLINING", 100.0, current_price);

        let result = calculator.calculate_position_var(
            &Symbol::from("DECLINING".to_owned()),
            &position,
            &prices,
        );

        assert!(result.is_ok());
        let var_result = result.unwrap();
        assert!(var_result.var_1d > Price::ZERO);
    }
}

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

    #[test]
    fn test_historical_var_95_vs_99_confidence() {
        let prices = create_prices("AAPL", 300, 150.0, 0.02);
        let position = create_position("AAPL", 100.0, 150.0);

        let calc_95 = HistoricalSimulationVaR::new(0.95, 252);
        let calc_99 = HistoricalSimulationVaR::new(0.99, 252);

        let result_95 = calc_95
            .calculate_position_var(&Symbol::from("AAPL".to_owned()), &position, &prices)
            .unwrap();

        let result_99 = calc_99
            .calculate_position_var(&Symbol::from("AAPL".to_owned()), &position, &prices)
            .unwrap();

        // 99% VaR should be higher than 95% VaR
        assert!(result_99.var_1d > result_95.var_1d);
    }

    #[test]
    fn test_monte_carlo_confidence_levels() {
        let mut positions = HashMap::new();
        positions.insert(
            Symbol::from("AAPL".to_owned()),
            create_position("AAPL", 100.0, 150.0),
        );

        let mut historical_prices = HashMap::new();
        historical_prices.insert(
            Symbol::from("AAPL".to_owned()),
            create_prices("AAPL", 100, 150.0, 0.02),
        );

        let calc_95 = MonteCarloVaR::new(0.95, 10000, 1, Some(42));
        let calc_99 = MonteCarloVaR::new(0.99, 10000, 1, Some(42));

        let result_95 = calc_95
            .calculate_portfolio_var("TEST", &positions, &historical_prices)
            .unwrap();

        let result_99 = calc_99
            .calculate_portfolio_var("TEST", &positions, &historical_prices)
            .unwrap();

        // 99% VaR should be higher than 95% VaR
        assert!(result_99.var_1d > result_95.var_1d);
    }
}

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

    #[test]
    fn test_var_10d_scaling_relationship() {
        let calculator = HistoricalSimulationVaR::standard();
        let prices = create_prices("AAPL", 300, 150.0, 0.02);
        let position = create_position("AAPL", 100.0, 150.0);

        let result = calculator
            .calculate_position_var(&Symbol::from("AAPL".to_owned()), &position, &prices)
            .unwrap();

        // 10-day VaR should be approximately sqrt(10) * 1-day VaR
        let expected_var_10d = result.var_1d.to_f64() * (10.0_f64).sqrt();
        let actual_var_10d = result.var_10d.to_f64();

        let ratio = actual_var_10d / expected_var_10d;
        // Allow 1% tolerance for floating point arithmetic
        assert!((ratio - 1.0).abs() < 0.01);
    }

    #[test]
    fn test_monte_carlo_time_horizon_scaling() {
        let mut positions = HashMap::new();
        positions.insert(
            Symbol::from("AAPL".to_owned()),
            create_position("AAPL", 100.0, 150.0),
        );

        let mut historical_prices = HashMap::new();
        historical_prices.insert(
            Symbol::from("AAPL".to_owned()),
            create_prices("AAPL", 100, 150.0, 0.02),
        );

        let calc_1d = MonteCarloVaR::new(0.95, 10000, 1, Some(42));
        let calc_10d = MonteCarloVaR::new(0.95, 10000, 10, Some(42));

        let result_1d = calc_1d
            .calculate_portfolio_var("TEST", &positions, &historical_prices)
            .unwrap();

        let result_10d = calc_10d
            .calculate_portfolio_var("TEST", &positions, &historical_prices)
            .unwrap();

        // Multi-day VaR should be larger
        assert!(result_10d.var_1d > result_1d.var_1d);
    }
}

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

    #[test]
    fn test_expected_shortfall_exceeds_var() {
        let calculator = HistoricalSimulationVaR::standard();
        let prices = create_prices("AAPL", 300, 150.0, 0.02);
        let position = create_position("AAPL", 100.0, 150.0);

        let result = calculator
            .calculate_position_var(&Symbol::from("AAPL".to_owned()), &position, &prices)
            .unwrap();

        // Expected Shortfall should always be >= VaR
        assert!(result.expected_shortfall >= result.var_1d);
    }

    #[test]
    fn test_monte_carlo_expected_shortfall_relationship() {
        let mut positions = HashMap::new();
        positions.insert(
            Symbol::from("AAPL".to_owned()),
            create_position("AAPL", 100.0, 150.0),
        );

        let mut historical_prices = HashMap::new();
        historical_prices.insert(
            Symbol::from("AAPL".to_owned()),
            create_prices("AAPL", 100, 150.0, 0.02),
        );

        let calculator = MonteCarloVaR::new(0.95, 10000, 1, Some(42));
        let result = calculator
            .calculate_portfolio_var("TEST", &positions, &historical_prices)
            .unwrap();

        // ES >= VaR (mathematical requirement)
        assert!(result.expected_shortfall >= result.var_1d);

        // Worst case >= ES (mathematical requirement)
        assert!(result.worst_case_scenario >= result.expected_shortfall);
    }
}

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

    #[test]
    fn test_diversification_benefit_positive() {
        let calculator = HistoricalSimulationVaR::standard();

        let mut positions = HashMap::new();
        positions.insert(
            Symbol::from("TECH".to_owned()),
            create_position("TECH", 100.0, 100.0),
        );
        positions.insert(
            Symbol::from("ENERGY".to_owned()),
            create_position("ENERGY", 100.0, 50.0),
        );

        let mut historical_prices = HashMap::new();
        historical_prices.insert(
            Symbol::from("TECH".to_owned()),
            create_prices("TECH", 300, 100.0, 0.03),
        );
        historical_prices.insert(
            Symbol::from("ENERGY".to_owned()),
            create_prices("ENERGY", 300, 50.0, 0.04),
        );

        let result = calculator
            .calculate_portfolio_var("DIVERSIFIED", &positions, &historical_prices)
            .unwrap();

        // Diversification benefit should be non-negative (can be zero or positive depending on correlation)
        // With random data, diversification benefit may be minimal or zero
        assert!(result.diversification_benefit >= Price::ZERO);
    }
}

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

    #[test]
    fn test_rolling_var_window_sizes() {
        let calculator = HistoricalSimulationVaR::new(0.95, 60);
        let prices = create_prices("AAPL", 200, 150.0, 0.02);
        let position = create_position("AAPL", 100.0, 150.0);

        let result = calculator.calculate_rolling_var(
            &Symbol::from("AAPL".to_owned()),
            &position,
            &prices,
            60,
        );

        assert!(result.is_ok());
        let rolling_vars = result.unwrap();

        // Should produce rolling estimates
        assert_eq!(rolling_vars.len(), 200 - 60);

        // All VaRs should be positive
        assert!(rolling_vars.iter().all(|v| v.var_1d > Price::ZERO));
    }

    #[test]
    fn test_rolling_var_insufficient_data() {
        let calculator = HistoricalSimulationVaR::new(0.95, 60);
        let prices = create_prices("AAPL", 50, 150.0, 0.02); // Less than window
        let position = create_position("AAPL", 100.0, 150.0);

        let result = calculator.calculate_rolling_var(
            &Symbol::from("AAPL".to_owned()),
            &position,
            &prices,
            60,
        );

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