//! Portfolio Greeks Tests - Black-Scholes Options Greeks Validation
//!
//! Comprehensive test suite for options Greeks calculations including:
//! - Delta (first derivative)
//! - Gamma (second derivative)
//! - Vega (volatility sensitivity)
//! - Theta (time decay)
//! - Rho (interest rate sensitivity)
//!
//! Tests cover ITM, ATM, OTM scenarios across different expiration dates

#![allow(
    unused_crate_dependencies,
    clippy::similar_names,
    clippy::unwrap_used
)]
#![allow(clippy::tests_outside_test_module)]

use approx::assert_relative_eq;
use config::structures::RiskConfig;
use risk::risk_engine::RiskEngine;

/// Helper function to create a basic risk engine for testing
fn create_test_risk_engine() -> RiskEngine {
    let config = RiskConfig::default();

    // Create a mock market data service
    struct MockMarketDataService;
    impl risk::risk_engine::MarketDataService for MockMarketDataService {}

    let market_data = std::sync::Arc::new(MockMarketDataService);

    tokio::runtime::Runtime::new()
        .unwrap()
        .block_on(async { RiskEngine::new(config, market_data, None).await.unwrap() })
}

// ==================== DELTA TESTS ====================

#[test]
fn test_delta_atm_call() {
    let engine = create_test_risk_engine();

    // ATM call option should have delta around 0.5
    let delta = engine
        .calculate_delta(
            100.0, // spot = strike (ATM)
            100.0, 0.25, // 3 months
            0.25, // 25% vol
            0.05, // 5% rate
            true, // call
        )
        .unwrap();

    // ATM call delta should be around 0.5 (50 delta)
    assert_relative_eq!(delta, 0.5, epsilon = 0.1);
    assert!(
        delta > 0.4 && delta < 0.6,
        "ATM call delta should be near 0.5, got {}",
        delta
    );
}

#[test]
fn test_delta_atm_put() {
    let engine = create_test_risk_engine();

    // ATM put option should have delta around -0.5
    let delta = engine
        .calculate_delta(
            100.0, // spot = strike (ATM)
            100.0, 0.25,  // 3 months
            0.25,  // 25% vol
            0.05,  // 5% rate
            false, // put
        )
        .unwrap();

    // ATM put delta should be around -0.5 (-50 delta)
    assert_relative_eq!(delta, -0.5, epsilon = 0.1);
    assert!(
        delta > -0.6 && delta < -0.4,
        "ATM put delta should be near -0.5, got {}",
        delta
    );
}

#[test]
fn test_delta_itm_call() {
    let engine = create_test_risk_engine();

    // Deep ITM call (spot > strike) should have high delta
    let delta = engine
        .calculate_delta(
            120.0, // spot > strike (ITM)
            100.0, 0.25, // 3 months
            0.25, // 25% vol
            0.05, // 5% rate
            true, // call
        )
        .unwrap();

    // Deep ITM call should have delta approaching 1.0
    assert!(
        delta > 0.8,
        "Deep ITM call delta should be high, got {}",
        delta
    );
    assert!(delta <= 1.0, "Call delta cannot exceed 1.0");
}

#[test]
fn test_delta_otm_call() {
    let engine = create_test_risk_engine();

    // Deep OTM call (spot < strike) should have low delta
    let delta = engine
        .calculate_delta(
            80.0, // spot < strike (OTM)
            100.0, 0.25, // 3 months
            0.25, // 25% vol
            0.05, // 5% rate
            true, // call
        )
        .unwrap();

    // Deep OTM call should have delta approaching 0.0
    assert!(
        delta < 0.2,
        "Deep OTM call delta should be low, got {}",
        delta
    );
    assert!(delta >= 0.0, "Call delta cannot be negative");
}

#[test]
fn test_delta_itm_put() {
    let engine = create_test_risk_engine();

    // Deep ITM put (spot < strike) should have delta near -1.0
    let delta = engine
        .calculate_delta(
            80.0, // spot < strike (ITM for put)
            100.0, 0.25,  // 3 months
            0.25,  // 25% vol
            0.05,  // 5% rate
            false, // put
        )
        .unwrap();

    // Deep ITM put should have delta approaching -1.0
    assert!(
        delta < -0.8,
        "Deep ITM put delta should be near -1.0, got {}",
        delta
    );
    assert!(delta >= -1.0, "Put delta cannot be less than -1.0");
}

#[test]
fn test_delta_otm_put() {
    let engine = create_test_risk_engine();

    // Deep OTM put (spot > strike) should have delta near 0.0
    let delta = engine
        .calculate_delta(
            120.0, // spot > strike (OTM for put)
            100.0, 0.25,  // 3 months
            0.25,  // 25% vol
            0.05,  // 5% rate
            false, // put
        )
        .unwrap();

    // Deep OTM put should have delta approaching 0.0
    assert!(
        delta > -0.2 && delta <= 0.0,
        "Deep OTM put delta should be near 0.0, got {}",
        delta
    );
}

// ==================== GAMMA TESTS ====================

#[test]
fn test_gamma_atm_highest() {
    let engine = create_test_risk_engine();

    // ATM options have highest gamma
    let gamma_atm = engine
        .calculate_gamma(
            100.0, // ATM
            100.0, 0.25, // 3 months
            0.25, // 25% vol
            0.05,
        )
        .unwrap();

    let gamma_otm = engine
        .calculate_gamma(
            80.0, // OTM
            100.0, 0.25, 0.25, 0.05,
        )
        .unwrap();

    // ATM gamma should be higher than OTM gamma
    assert!(
        gamma_atm > gamma_otm,
        "ATM gamma ({}) should be higher than OTM gamma ({})",
        gamma_atm,
        gamma_otm
    );
    assert!(gamma_atm > 0.0, "Gamma must be positive for long options");
}

#[test]
fn test_gamma_always_positive() {
    let engine = create_test_risk_engine();

    // Test gamma for various scenarios - should always be positive for long options
    let scenarios = vec![
        (80.0, 100.0),  // OTM
        (100.0, 100.0), // ATM
        (120.0, 100.0), // ITM
    ];

    for (spot, strike) in scenarios {
        let gamma = engine
            .calculate_gamma(spot, strike, 0.25, 0.25, 0.05)
            .unwrap();
        assert!(
            gamma > 0.0,
            "Gamma must be positive, got {} for spot={} strike={}",
            gamma,
            spot,
            strike
        );
    }
}

#[test]
fn test_gamma_increases_near_expiry() {
    let engine = create_test_risk_engine();

    // ATM gamma increases as expiration approaches
    let gamma_far = engine
        .calculate_gamma(
            100.0, // ATM
            100.0, 1.0, // 1 year
            0.25, 0.05,
        )
        .unwrap();

    let gamma_near = engine
        .calculate_gamma(
            100.0, // ATM
            100.0, 0.08, // 1 month
            0.25, 0.05,
        )
        .unwrap();

    // Near-term ATM gamma should be higher than far-term
    assert!(
        gamma_near > gamma_far,
        "Near-term gamma ({}) should exceed far-term gamma ({})",
        gamma_near,
        gamma_far
    );
}

// ==================== VEGA TESTS ====================

#[test]
fn test_vega_atm_highest() {
    let engine = create_test_risk_engine();

    // ATM options have highest vega
    let vega_atm = engine
        .calculate_vega(
            100.0, // ATM
            100.0, 0.5, // 6 months
            0.25, 0.05,
        )
        .unwrap();

    let vega_otm = engine
        .calculate_vega(
            80.0, // OTM
            100.0, 0.5, 0.25, 0.05,
        )
        .unwrap();

    // ATM vega should be higher than OTM vega
    assert!(
        vega_atm > vega_otm,
        "ATM vega ({}) should be higher than OTM vega ({})",
        vega_atm,
        vega_otm
    );
    assert!(vega_atm > 0.0, "Vega must be positive for long options");
}

#[test]
fn test_vega_increases_with_time() {
    let engine = create_test_risk_engine();

    // Vega increases with time to expiration (for ATM options)
    let vega_short = engine
        .calculate_vega(
            100.0, // ATM
            100.0, 0.08, // 1 month
            0.25, 0.05,
        )
        .unwrap();

    let vega_long = engine
        .calculate_vega(
            100.0, // ATM
            100.0, 1.0, // 1 year
            0.25, 0.05,
        )
        .unwrap();

    // Longer-dated options have higher vega
    assert!(
        vega_long > vega_short,
        "Long-term vega ({}) should exceed short-term vega ({})",
        vega_long,
        vega_short
    );
}

#[test]
fn test_vega_always_positive() {
    let engine = create_test_risk_engine();

    // Test vega for various scenarios - should always be positive for long options
    let scenarios = vec![
        (80.0, 100.0, 0.25), // OTM, short-term
        (100.0, 100.0, 0.5), // ATM, medium-term
        (120.0, 100.0, 1.0), // ITM, long-term
    ];

    for (spot, strike, time) in scenarios {
        let vega = engine
            .calculate_vega(spot, strike, time, 0.25, 0.05)
            .unwrap();
        assert!(
            vega > 0.0,
            "Vega must be positive, got {} for spot={} strike={} time={}",
            vega,
            spot,
            strike,
            time
        );
    }
}

// ==================== THETA TESTS ====================

#[test]
fn test_theta_negative_for_long_call() {
    let engine = create_test_risk_engine();

    // Long call options have negative theta (lose value over time)
    let theta = engine
        .calculate_theta(
            100.0, // ATM
            100.0, 0.25, // 3 months
            0.25, 0.05, true, // call
        )
        .unwrap();

    // Long call theta should be negative (time decay)
    assert!(
        theta < 0.0,
        "Long call theta should be negative, got {}",
        theta
    );
}

#[test]
fn test_theta_negative_for_long_put() {
    let engine = create_test_risk_engine();

    // Long put options have negative theta (lose value over time)
    let theta = engine
        .calculate_theta(
            100.0, // ATM
            100.0, 0.25, // 3 months
            0.25, 0.05, false, // put
        )
        .unwrap();

    // Long put theta should be negative (time decay)
    assert!(
        theta < 0.0,
        "Long put theta should be negative, got {}",
        theta
    );
}

#[test]
fn test_theta_accelerates_near_expiry() {
    let engine = create_test_risk_engine();

    // Theta magnitude increases (more negative) as expiration approaches
    let theta_far = engine
        .calculate_theta(
            100.0, // ATM
            100.0, 1.0, // 1 year
            0.25, 0.05, true,
        )
        .unwrap();

    let theta_near = engine
        .calculate_theta(
            100.0, // ATM
            100.0, 0.08, // 1 month
            0.25, 0.05, true,
        )
        .unwrap();

    // Near-term theta should be more negative (faster decay)
    assert!(
        theta_near.abs() > theta_far.abs(),
        "Near-term theta decay ({}) should exceed far-term ({})",
        theta_near.abs(),
        theta_far.abs()
    );
}

#[test]
fn test_theta_atm_highest_decay() {
    let engine = create_test_risk_engine();

    // ATM options have highest theta (fastest decay)
    let theta_atm = engine
        .calculate_theta(
            100.0, // ATM
            100.0, 0.25, 0.25, 0.05, true,
        )
        .unwrap();

    let theta_otm = engine
        .calculate_theta(
            80.0, // OTM
            100.0, 0.25, 0.25, 0.05, true,
        )
        .unwrap();

    // ATM theta should have higher magnitude than OTM
    assert!(
        theta_atm.abs() > theta_otm.abs(),
        "ATM theta decay ({}) should exceed OTM decay ({})",
        theta_atm.abs(),
        theta_otm.abs()
    );
}

// ==================== RHO TESTS ====================

#[test]
fn test_rho_call_positive() {
    let engine = create_test_risk_engine();

    // Call options have positive rho (benefit from rising rates)
    let rho = engine
        .calculate_rho(
            100.0, // ATM
            100.0, 1.0, // 1 year (longer = higher rho)
            0.25, 0.05, true, // call
        )
        .unwrap();

    // Call rho should be positive
    assert!(rho > 0.0, "Call rho should be positive, got {}", rho);
}

#[test]
fn test_rho_put_negative() {
    let engine = create_test_risk_engine();

    // Put options have negative rho (hurt by rising rates)
    let rho = engine
        .calculate_rho(
            100.0, // ATM
            100.0, 1.0, // 1 year
            0.25, 0.05, false, // put
        )
        .unwrap();

    // Put rho should be negative
    assert!(rho < 0.0, "Put rho should be negative, got {}", rho);
}

#[test]
fn test_rho_increases_with_time() {
    let engine = create_test_risk_engine();

    // Rho magnitude increases with time to expiration
    let rho_short = engine
        .calculate_rho(
            100.0, // ATM
            100.0, 0.25, // 3 months
            0.25, 0.05, true,
        )
        .unwrap();

    let rho_long = engine
        .calculate_rho(
            100.0, // ATM
            100.0, 2.0, // 2 years (LEAPS)
            0.25, 0.05, true,
        )
        .unwrap();

    // LEAPS should have higher rho than short-term
    assert!(
        rho_long > rho_short,
        "Long-term rho ({}) should exceed short-term rho ({})",
        rho_long,
        rho_short
    );
}

#[test]
fn test_rho_itm_vs_otm() {
    let engine = create_test_risk_engine();

    // ITM options have higher rho magnitude than OTM
    let rho_itm = engine
        .calculate_rho(
            120.0, // ITM call
            100.0, 1.0, 0.25, 0.05, true,
        )
        .unwrap();

    let rho_otm = engine
        .calculate_rho(
            80.0, // OTM call
            100.0, 1.0, 0.25, 0.05, true,
        )
        .unwrap();

    // ITM call rho should be higher than OTM call rho
    assert!(
        rho_itm > rho_otm,
        "ITM rho ({}) should exceed OTM rho ({})",
        rho_itm,
        rho_otm
    );
}

// ==================== EDGE CASES & VALIDATION TESTS ====================

#[test]
fn test_negative_spot_price_rejected() {
    let engine = create_test_risk_engine();

    let result = engine.calculate_delta(-100.0, 100.0, 0.25, 0.25, 0.05, true);
    assert!(result.is_err(), "Negative spot price should be rejected");
}

#[test]
fn test_negative_strike_price_rejected() {
    let engine = create_test_risk_engine();

    let result = engine.calculate_delta(100.0, -100.0, 0.25, 0.25, 0.05, true);
    assert!(result.is_err(), "Negative strike price should be rejected");
}

#[test]
fn test_zero_time_to_expiry_rejected() {
    let engine = create_test_risk_engine();

    let result = engine.calculate_delta(100.0, 100.0, 0.0, 0.25, 0.05, true);
    assert!(result.is_err(), "Zero time to expiry should be rejected");
}

#[test]
fn test_negative_volatility_rejected() {
    let engine = create_test_risk_engine();

    let result = engine.calculate_delta(100.0, 100.0, 0.25, -0.25, 0.05, true);
    assert!(result.is_err(), "Negative volatility should be rejected");
}

#[test]
fn test_very_short_expiry() {
    let engine = create_test_risk_engine();

    // Test with 1 day to expiry (0.0027 years)
    let delta = engine
        .calculate_delta(
            100.0, // ATM
            100.0,
            1.0 / 365.0, // 1 day
            0.25,
            0.05,
            true,
        )
        .unwrap();

    // Very short-term ATM option should still have delta around 0.5
    assert!(
        delta > 0.3 && delta < 0.7,
        "Short-term ATM delta should be reasonable, got {}",
        delta
    );
}

#[test]
fn test_very_long_expiry() {
    let engine = create_test_risk_engine();

    // Test with 5 years to expiry
    let delta = engine
        .calculate_delta(
            100.0, // ATM
            100.0, 5.0, // 5 years
            0.25, 0.05, true,
        )
        .unwrap();

    // Long-term ATM call should have delta > 0.5 (slightly ITM effect from drift)
    assert!(
        delta > 0.5 && delta < 1.0,
        "Long-term ATM call delta should be > 0.5, got {}",
        delta
    );
}

#[test]
fn test_high_volatility_impact() {
    let engine = create_test_risk_engine();

    // Compare low vol vs high vol for ATM option
    let vega_low_vol = engine
        .calculate_vega(100.0, 100.0, 0.5, 0.10, 0.05)
        .unwrap();
    let vega_high_vol = engine
        .calculate_vega(100.0, 100.0, 0.5, 0.50, 0.05)
        .unwrap();

    // Both should be positive, but magnitudes may differ
    assert!(
        vega_low_vol > 0.0 && vega_high_vol > 0.0,
        "Vega should be positive for both volatility scenarios"
    );
}

#[test]
fn test_put_call_parity_delta() {
    let engine = create_test_risk_engine();

    // Put-Call parity: Call Delta - Put Delta = 1.0
    let call_delta = engine
        .calculate_delta(100.0, 100.0, 0.25, 0.25, 0.05, true)
        .unwrap();
    let put_delta = engine
        .calculate_delta(100.0, 100.0, 0.25, 0.25, 0.05, false)
        .unwrap();

    let delta_difference = call_delta - put_delta;
    assert_relative_eq!(delta_difference, 1.0, epsilon = 0.01);
}

#[test]
fn test_gamma_same_for_call_and_put() {
    let engine = create_test_risk_engine();

    // Gamma should be identical for calls and puts with same parameters
    // (Gamma doesn't have is_call parameter, so we test this implicitly by verifying
    // that the gamma formula doesn't depend on option type)
    let gamma = engine
        .calculate_gamma(100.0, 100.0, 0.25, 0.25, 0.05)
        .unwrap();

    // Just verify gamma is positive and reasonable
    assert!(
        gamma > 0.0 && gamma < 1.0,
        "Gamma should be reasonable positive value, got {}",
        gamma
    );
}

#[test]
fn test_vega_same_for_call_and_put() {
    let engine = create_test_risk_engine();

    // Vega should be identical for calls and puts with same parameters
    let vega = engine
        .calculate_vega(100.0, 100.0, 0.25, 0.25, 0.05)
        .unwrap();

    // Verify vega is positive and reasonable
    assert!(vega > 0.0, "Vega should be positive, got {}", vega);
}

#[test]
fn test_extreme_itm_call_delta_near_one() {
    let engine = create_test_risk_engine();

    // Extremely ITM call (spot >> strike) should have delta very close to 1.0
    let delta = engine
        .calculate_delta(
            200.0, // spot = 2x strike (very ITM)
            100.0, 0.25, 0.25, 0.05, true,
        )
        .unwrap();

    assert!(
        delta > 0.95,
        "Extreme ITM call delta should be very close to 1.0, got {}",
        delta
    );
}

#[test]
fn test_extreme_otm_call_delta_near_zero() {
    let engine = create_test_risk_engine();

    // Extremely OTM call (spot << strike) should have delta very close to 0.0
    let delta = engine
        .calculate_delta(
            50.0, // spot = 0.5x strike (very OTM)
            100.0, 0.25, 0.25, 0.05, true,
        )
        .unwrap();

    assert!(
        delta < 0.05,
        "Extreme OTM call delta should be very close to 0.0, got {}",
        delta
    );
}
