//! Options & Futures Pricing
//!
//! Black-Scholes-Merton model for European options pricing,
//! along with Greeks calculation and futures pricing.

use crate::error::{EconomicsError, Result};
use crate::SynorDecimal;
use chrono::{DateTime, Duration, Timelike, Utc};
use rust_decimal::Decimal;
use rust_decimal::prelude::ToPrimitive;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::f64::consts::PI;

/// Option type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OptionType {
    Call,
    Put,
}

/// Option style
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OptionStyle {
    /// Can only be exercised at expiration
    European,
    /// Can be exercised any time before expiration
    American,
}

/// Option contract specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionContract {
    /// Underlying asset symbol
    pub underlying: String,
    /// Strike price
    pub strike: SynorDecimal,
    /// Expiration timestamp
    pub expiration: DateTime<Utc>,
    /// Call or Put
    pub option_type: OptionType,
    /// European or American
    pub style: OptionStyle,
    /// Contract multiplier (e.g., 100 for stock options)
    pub multiplier: SynorDecimal,
}

impl OptionContract {
    /// Create a new option contract
    pub fn new(
        underlying: impl Into<String>,
        strike: SynorDecimal,
        expiration: DateTime<Utc>,
        option_type: OptionType,
    ) -> Self {
        Self {
            underlying: underlying.into(),
            strike,
            expiration,
            option_type,
            style: OptionStyle::European,
            multiplier: Decimal::ONE,
        }
    }

    /// Time to expiration in years
    pub fn time_to_expiry(&self) -> f64 {
        let seconds = (self.expiration - Utc::now()).num_seconds() as f64;
        seconds / (365.25 * 24.0 * 60.0 * 60.0)
    }

    /// Check if option is expired
    pub fn is_expired(&self) -> bool {
        Utc::now() >= self.expiration
    }

    /// Check if option is in the money
    pub fn is_itm(&self, spot: SynorDecimal) -> bool {
        match self.option_type {
            OptionType::Call => spot > self.strike,
            OptionType::Put => spot < self.strike,
        }
    }

    /// Calculate intrinsic value
    pub fn intrinsic_value(&self, spot: SynorDecimal) -> SynorDecimal {
        match self.option_type {
            OptionType::Call => (spot - self.strike).max(Decimal::ZERO),
            OptionType::Put => (self.strike - spot).max(Decimal::ZERO),
        }
    }
}

/// The Greeks - option sensitivities
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionGreeks {
    /// Price sensitivity to underlying (1st derivative)
    pub delta: f64,
    /// Delta sensitivity to underlying (2nd derivative)
    pub gamma: f64,
    /// Price sensitivity to time decay
    pub theta: f64,
    /// Price sensitivity to volatility
    pub vega: f64,
    /// Price sensitivity to interest rate
    pub rho: f64,
    /// Vega sensitivity to volatility
    pub vanna: f64,
    /// Gamma sensitivity to volatility
    pub volga: f64,
}

/// Full option pricing result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionPricing {
    /// Theoretical price
    pub price: SynorDecimal,
    /// Intrinsic value
    pub intrinsic: SynorDecimal,
    /// Time value (extrinsic)
    pub time_value: SynorDecimal,
    /// Option greeks
    pub greeks: OptionGreeks,
    /// Implied volatility (if calculated from market price)
    pub implied_vol: Option<f64>,
    /// Pricing model used
    pub model: String,
    /// Calculation timestamp
    pub timestamp: DateTime<Utc>,
}

/// Standard normal CDF approximation (Abramowitz & Stegun)
fn norm_cdf(x: f64) -> f64 {
    let a1 = 0.254829592;
    let a2 = -0.284496736;
    let a3 = 1.421413741;
    let a4 = -1.453152027;
    let a5 = 1.061405429;
    let p = 0.3275911;

    let sign = if x < 0.0 { -1.0 } else { 1.0 };
    let x = x.abs();

    let t = 1.0 / (1.0 + p * x);
    let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x / 2.0).exp();

    0.5 * (1.0 + sign * y)
}

/// Standard normal PDF
fn norm_pdf(x: f64) -> f64 {
    (-x * x / 2.0).exp() / (2.0 * PI).sqrt()
}

/// Black-Scholes-Merton pricing model
pub struct BlackScholes {
    /// Risk-free interest rate (annualized)
    pub risk_free_rate: f64,
    /// Dividend yield (annualized)
    pub dividend_yield: f64,
}

impl BlackScholes {
    pub fn new(risk_free_rate: f64, dividend_yield: f64) -> Self {
        Self {
            risk_free_rate,
            dividend_yield,
        }
    }

    /// Calculate d1 and d2 parameters
    fn d1_d2(&self, spot: f64, strike: f64, time: f64, vol: f64) -> (f64, f64) {
        let d1 = ((spot / strike).ln()
            + (self.risk_free_rate - self.dividend_yield + vol * vol / 2.0) * time)
            / (vol * time.sqrt());
        let d2 = d1 - vol * time.sqrt();
        (d1, d2)
    }

    /// Price a European option
    pub fn price(&self, contract: &OptionContract, spot: SynorDecimal, vol: f64) -> Result<OptionPricing> {
        if contract.is_expired() {
            // At expiration, option is worth intrinsic value
            let intrinsic = contract.intrinsic_value(spot);
            return Ok(OptionPricing {
                price: intrinsic,
                intrinsic,
                time_value: Decimal::ZERO,
                greeks: OptionGreeks {
                    delta: if contract.is_itm(spot) {
                        match contract.option_type {
                            OptionType::Call => 1.0,
                            OptionType::Put => -1.0,
                        }
                    } else {
                        0.0
                    },
                    gamma: 0.0,
                    theta: 0.0,
                    vega: 0.0,
                    rho: 0.0,
                    vanna: 0.0,
                    volga: 0.0,
                },
                implied_vol: None,
                model: "Black-Scholes".to_string(),
                timestamp: Utc::now(),
            });
        }

        let s = spot.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid spot price".into())
        )?;
        let k = contract.strike.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid strike price".into())
        )?;
        let t = contract.time_to_expiry();

        if t <= 0.0 || vol <= 0.0 {
            return Err(EconomicsError::InvalidPrice("Invalid parameters".into()));
        }

        let (d1, d2) = self.d1_d2(s, k, t, vol);

        let r = self.risk_free_rate;
        let q = self.dividend_yield;

        let price_f64 = match contract.option_type {
            OptionType::Call => {
                s * (-q * t).exp() * norm_cdf(d1) - k * (-r * t).exp() * norm_cdf(d2)
            }
            OptionType::Put => {
                k * (-r * t).exp() * norm_cdf(-d2) - s * (-q * t).exp() * norm_cdf(-d1)
            }
        };

        let greeks = self.calculate_greeks(contract, s, k, t, vol, d1, d2);

        let price = Decimal::from_f64_retain(price_f64)
            .ok_or_else(|| EconomicsError::InvalidPrice("Price calculation overflow".into()))?;
        let intrinsic = contract.intrinsic_value(spot);
        let time_value = (price - intrinsic).max(Decimal::ZERO);

        Ok(OptionPricing {
            price,
            intrinsic,
            time_value,
            greeks,
            implied_vol: Some(vol),
            model: "Black-Scholes".to_string(),
            timestamp: Utc::now(),
        })
    }

    /// Calculate option Greeks
    fn calculate_greeks(
        &self,
        contract: &OptionContract,
        s: f64,
        k: f64,
        t: f64,
        vol: f64,
        d1: f64,
        d2: f64,
    ) -> OptionGreeks {
        let r = self.risk_free_rate;
        let q = self.dividend_yield;
        let sqrt_t = t.sqrt();

        let n_d1 = norm_cdf(d1);
        let n_d2 = norm_cdf(d2);
        let n_prime_d1 = norm_pdf(d1);

        let (delta, _theta_sign) = match contract.option_type {
            OptionType::Call => ((-q * t).exp() * n_d1, 1.0),
            OptionType::Put => ((-q * t).exp() * (n_d1 - 1.0), -1.0),
        };

        // Gamma (same for calls and puts)
        let gamma = (-q * t).exp() * n_prime_d1 / (s * vol * sqrt_t);

        // Vega (same for calls and puts)
        let vega = s * (-q * t).exp() * n_prime_d1 * sqrt_t / 100.0; // Per 1% vol change

        // Theta
        let theta_common = -(s * vol * (-q * t).exp() * n_prime_d1) / (2.0 * sqrt_t);
        let theta = match contract.option_type {
            OptionType::Call => {
                theta_common
                    + q * s * (-q * t).exp() * n_d1
                    - r * k * (-r * t).exp() * n_d2
            }
            OptionType::Put => {
                theta_common
                    - q * s * (-q * t).exp() * (1.0 - n_d1)
                    + r * k * (-r * t).exp() * (1.0 - n_d2)
            }
        } / 365.0; // Per day

        // Rho
        let rho = match contract.option_type {
            OptionType::Call => k * t * (-r * t).exp() * n_d2 / 100.0,
            OptionType::Put => -k * t * (-r * t).exp() * (1.0 - n_d2) / 100.0,
        };

        // Vanna: d(Delta)/d(Vol)
        let vanna = -(-q * t).exp() * n_prime_d1 * d2 / vol;

        // Volga: d(Vega)/d(Vol)
        let volga = vega * d1 * d2 / vol;

        OptionGreeks {
            delta,
            gamma,
            theta,
            vega,
            rho,
            vanna,
            volga,
        }
    }

    /// Calculate implied volatility from market price using Newton-Raphson
    pub fn implied_volatility(
        &self,
        contract: &OptionContract,
        spot: SynorDecimal,
        market_price: SynorDecimal,
    ) -> Result<f64> {
        let target = market_price.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid market price".into())
        )?;

        // Initial guess based on time value
        let intrinsic = contract.intrinsic_value(spot).to_f64().unwrap_or(0.0);
        let time_value = (target - intrinsic).max(0.0);
        let s = spot.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid spot".into())
        )?;
        let t = contract.time_to_expiry();

        // Brenner-Subrahmanyam approximation for initial guess
        let mut vol = (2.0 * PI / t).sqrt() * time_value / s;
        vol = vol.max(0.01).min(5.0); // Bound between 1% and 500%

        // Newton-Raphson iteration
        for _ in 0..100 {
            let pricing = self.price(contract, spot, vol)?;
            let price = pricing.price.to_f64().unwrap_or(0.0);
            let vega = pricing.greeks.vega * 100.0; // Convert back from per-1% to per-1

            let diff = price - target;

            if diff.abs() < 0.0001 {
                return Ok(vol);
            }

            if vega.abs() < 0.0001 {
                // Vega too small, can't continue Newton-Raphson
                break;
            }

            vol -= diff / vega;
            vol = vol.max(0.001).min(10.0); // Keep bounded
        }

        // Fall back to bisection if Newton-Raphson fails
        self.implied_vol_bisection(contract, spot, market_price)
    }

    /// Bisection method for IV (more robust, slower)
    fn implied_vol_bisection(
        &self,
        contract: &OptionContract,
        spot: SynorDecimal,
        market_price: SynorDecimal,
    ) -> Result<f64> {
        let target = market_price.to_f64().unwrap_or(0.0);
        let mut low = 0.001;
        let mut high = 5.0;

        for _ in 0..100 {
            let mid = (low + high) / 2.0;
            let price = self.price(contract, spot, mid)?.price.to_f64().unwrap_or(0.0);

            if (price - target).abs() < 0.0001 {
                return Ok(mid);
            }

            if price < target {
                low = mid;
            } else {
                high = mid;
            }
        }

        Ok((low + high) / 2.0)
    }
}

impl Default for BlackScholes {
    fn default() -> Self {
        Self::new(0.05, 0.0) // 5% risk-free rate, no dividend
    }
}

/// Futures contract specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FuturesContract {
    /// Underlying asset symbol
    pub underlying: String,
    /// Contract expiration
    pub expiration: DateTime<Utc>,
    /// Contract size
    pub contract_size: SynorDecimal,
    /// Tick size (minimum price movement)
    pub tick_size: SynorDecimal,
    /// Tick value (dollar value per tick)
    pub tick_value: SynorDecimal,
}

impl FuturesContract {
    pub fn new(
        underlying: impl Into<String>,
        expiration: DateTime<Utc>,
        contract_size: SynorDecimal,
    ) -> Self {
        Self {
            underlying: underlying.into(),
            expiration,
            contract_size,
            tick_size: Decimal::new(1, 2),   // 0.01
            tick_value: Decimal::new(10, 0), // $10 per tick
        }
    }

    /// Time to expiration in years
    pub fn time_to_expiry(&self) -> f64 {
        let seconds = (self.expiration - Utc::now()).num_seconds() as f64;
        seconds / (365.25 * 24.0 * 60.0 * 60.0)
    }

    /// Check if contract is expired
    pub fn is_expired(&self) -> bool {
        Utc::now() >= self.expiration
    }
}

/// Futures pricing result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FuturesPricing {
    /// Fair value (theoretical price)
    pub fair_value: SynorDecimal,
    /// Basis (futures - spot)
    pub basis: SynorDecimal,
    /// Basis in percentage
    pub basis_pct: f64,
    /// Cost of carry
    pub cost_of_carry: SynorDecimal,
    /// Calculation timestamp
    pub timestamp: DateTime<Utc>,
}

/// Futures pricing model
pub struct FuturesModel {
    /// Risk-free interest rate
    pub risk_free_rate: f64,
    /// Storage cost (for commodities)
    pub storage_cost: f64,
    /// Convenience yield (for commodities)
    pub convenience_yield: f64,
}

impl FuturesModel {
    pub fn new(risk_free_rate: f64) -> Self {
        Self {
            risk_free_rate,
            storage_cost: 0.0,
            convenience_yield: 0.0,
        }
    }

    /// For commodities with storage costs
    pub fn with_costs(risk_free_rate: f64, storage_cost: f64, convenience_yield: f64) -> Self {
        Self {
            risk_free_rate,
            storage_cost,
            convenience_yield,
        }
    }

    /// Calculate futures fair value using cost-of-carry model
    /// F = S * e^((r + u - y) * T)
    /// where r = risk-free rate, u = storage cost, y = convenience yield
    pub fn price(&self, contract: &FuturesContract, spot: SynorDecimal) -> Result<FuturesPricing> {
        let s = spot.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid spot price".into())
        )?;

        let t = contract.time_to_expiry();
        if t < 0.0 {
            return Err(EconomicsError::InvalidPrice("Contract expired".into()));
        }

        let cost_of_carry = self.risk_free_rate + self.storage_cost - self.convenience_yield;
        let fair_value_f64 = s * (cost_of_carry * t).exp();

        let fair_value = Decimal::from_f64_retain(fair_value_f64)
            .ok_or_else(|| EconomicsError::InvalidPrice("Overflow in calculation".into()))?;

        let basis = fair_value - spot;
        let basis_pct = if s > 0.0 {
            (fair_value_f64 - s) / s * 100.0
        } else {
            0.0
        };

        let coc = Decimal::from_f64_retain(cost_of_carry * t * s)
            .unwrap_or(Decimal::ZERO);

        Ok(FuturesPricing {
            fair_value,
            basis,
            basis_pct,
            cost_of_carry: coc,
            timestamp: Utc::now(),
        })
    }

    /// Calculate implied repo rate from futures price
    /// R = (F/S - 1) / T
    pub fn implied_repo_rate(&self, contract: &FuturesContract, spot: SynorDecimal, futures_price: SynorDecimal) -> Result<f64> {
        let s = spot.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid spot".into())
        )?;
        let f = futures_price.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid futures price".into())
        )?;
        let t = contract.time_to_expiry();

        if t <= 0.0 || s <= 0.0 {
            return Err(EconomicsError::InvalidPrice("Invalid parameters".into()));
        }

        Ok((f / s).ln() / t)
    }
}

impl Default for FuturesModel {
    fn default() -> Self {
        Self::new(0.05)
    }
}

/// Perpetual swap pricing (no expiry)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerpetualPricing {
    /// Mark price
    pub mark_price: SynorDecimal,
    /// Index price (spot)
    pub index_price: SynorDecimal,
    /// Premium/discount from index
    pub premium: SynorDecimal,
    /// Funding rate (8h)
    pub funding_rate: f64,
    /// Next funding time
    pub next_funding: DateTime<Utc>,
    /// Open interest
    pub open_interest: SynorDecimal,
}

/// Perpetual swap model
pub struct PerpetualModel {
    /// Funding rate interval (hours)
    pub funding_interval_hours: u32,
    /// Interest rate component
    pub interest_rate: f64,
    /// Premium/discount dampener
    pub dampener: f64,
}

impl PerpetualModel {
    pub fn new() -> Self {
        Self {
            funding_interval_hours: 8,
            interest_rate: 0.0001, // 0.01% per 8h (roughly 3% annualized)
            dampener: 0.0005,      // 0.05% dampener
        }
    }

    /// Calculate funding rate
    /// Funding = (Mark - Index) / Index * factor, clamped by interest rate bounds
    pub fn calculate_funding(
        &self,
        mark_price: SynorDecimal,
        index_price: SynorDecimal,
    ) -> Result<f64> {
        let mark = mark_price.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid mark price".into())
        )?;
        let index = index_price.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid index price".into())
        )?;

        if index <= 0.0 {
            return Err(EconomicsError::InvalidPrice("Invalid index".into()));
        }

        // Premium rate
        let premium_rate = (mark - index) / index;

        // Funding rate = Premium Rate + Interest Rate (clamped)
        let funding = if premium_rate.abs() > self.dampener {
            if premium_rate > 0.0 {
                premium_rate - self.dampener + self.interest_rate
            } else {
                premium_rate + self.dampener + self.interest_rate
            }
        } else {
            self.interest_rate
        };

        // Clamp funding rate
        Ok(funding.max(-0.03).min(0.03)) // Max 3% per funding period
    }

    /// Price a perpetual position
    pub fn price(
        &self,
        index_price: SynorDecimal,
        mark_price: SynorDecimal,
        open_interest: SynorDecimal,
    ) -> Result<PerpetualPricing> {
        let funding_rate = self.calculate_funding(mark_price, index_price)?;

        let premium = mark_price - index_price;

        // Next funding time
        let now = Utc::now();
        let hours_since_midnight = now.time().hour();
        let next_funding_hour = ((hours_since_midnight / self.funding_interval_hours) + 1)
            * self.funding_interval_hours;
        let next_funding = now.date_naive()
            .and_hms_opt(next_funding_hour % 24, 0, 0)
            .map(|dt| DateTime::from_naive_utc_and_offset(dt, Utc))
            .unwrap_or(now + Duration::hours(self.funding_interval_hours as i64));

        Ok(PerpetualPricing {
            mark_price,
            index_price,
            premium,
            funding_rate,
            next_funding,
            open_interest,
        })
    }
}

impl Default for PerpetualModel {
    fn default() -> Self {
        Self::new()
    }
}

/// Derivatives pricing oracle
pub struct DerivativesOracle {
    /// Black-Scholes model
    pub options_model: BlackScholes,
    /// Futures model
    pub futures_model: FuturesModel,
    /// Perpetual model
    pub perpetual_model: PerpetualModel,
    /// Volatility surface by underlying
    vol_surfaces: HashMap<String, VolatilitySurface>,
}

/// Volatility surface for options pricing
#[derive(Debug, Clone)]
pub struct VolatilitySurface {
    /// Underlying asset
    pub underlying: String,
    /// ATM volatility
    pub atm_vol: f64,
    /// Skew (vol difference between OTM puts and calls)
    pub skew: f64,
    /// Term structure (vol change per year of expiry)
    pub term_slope: f64,
    /// Last update
    pub updated_at: DateTime<Utc>,
}

impl VolatilitySurface {
    pub fn new(underlying: impl Into<String>, atm_vol: f64) -> Self {
        Self {
            underlying: underlying.into(),
            atm_vol,
            skew: 0.0,
            term_slope: 0.0,
            updated_at: Utc::now(),
        }
    }

    /// Get volatility for specific strike and expiry
    pub fn get_vol(&self, moneyness: f64, time_to_expiry: f64) -> f64 {
        // Simple parameterization: vol = ATM + skew * moneyness + term * sqrt(T)
        let skew_adj = self.skew * moneyness;
        let term_adj = self.term_slope * time_to_expiry.sqrt();
        (self.atm_vol + skew_adj + term_adj).max(0.01)
    }
}

impl DerivativesOracle {
    pub fn new() -> Self {
        Self {
            options_model: BlackScholes::default(),
            futures_model: FuturesModel::default(),
            perpetual_model: PerpetualModel::default(),
            vol_surfaces: HashMap::new(),
        }
    }

    /// Set volatility surface for an underlying
    pub fn set_vol_surface(&mut self, surface: VolatilitySurface) {
        self.vol_surfaces.insert(surface.underlying.clone(), surface);
    }

    /// Price an option using the volatility surface
    pub fn price_option(
        &self,
        contract: &OptionContract,
        spot: SynorDecimal,
    ) -> Result<OptionPricing> {
        let s = spot.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid spot".into())
        )?;
        let k = contract.strike.to_f64().ok_or_else(||
            EconomicsError::InvalidPrice("Invalid strike".into())
        )?;

        // Get volatility from surface or use default
        let vol = if let Some(surface) = self.vol_surfaces.get(&contract.underlying) {
            let moneyness = (k / s).ln();
            let t = contract.time_to_expiry();
            surface.get_vol(moneyness, t)
        } else {
            0.30 // Default 30% vol
        };

        self.options_model.price(contract, spot, vol)
    }

    /// Price a futures contract
    pub fn price_futures(
        &self,
        contract: &FuturesContract,
        spot: SynorDecimal,
    ) -> Result<FuturesPricing> {
        self.futures_model.price(contract, spot)
    }

    /// Price a perpetual swap
    pub fn price_perpetual(
        &self,
        index_price: SynorDecimal,
        mark_price: SynorDecimal,
        open_interest: SynorDecimal,
    ) -> Result<PerpetualPricing> {
        self.perpetual_model.price(index_price, mark_price, open_interest)
    }

    /// Calculate implied vol from market price
    pub fn get_implied_vol(
        &self,
        contract: &OptionContract,
        spot: SynorDecimal,
        market_price: SynorDecimal,
    ) -> Result<f64> {
        self.options_model.implied_volatility(contract, spot, market_price)
    }
}

impl Default for DerivativesOracle {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rust_decimal_macros::dec;

    #[test]
    fn test_option_intrinsic_value() {
        let call = OptionContract::new("ETH", dec!(2000), Utc::now() + Duration::days(30), OptionType::Call);
        let put = OptionContract::new("ETH", dec!(2000), Utc::now() + Duration::days(30), OptionType::Put);

        // ITM call
        assert_eq!(call.intrinsic_value(dec!(2100)), dec!(100));
        // OTM call
        assert_eq!(call.intrinsic_value(dec!(1900)), dec!(0));

        // ITM put
        assert_eq!(put.intrinsic_value(dec!(1900)), dec!(100));
        // OTM put
        assert_eq!(put.intrinsic_value(dec!(2100)), dec!(0));
    }

    #[test]
    fn test_black_scholes_call() {
        let model = BlackScholes::new(0.05, 0.0);
        let contract = OptionContract::new(
            "ETH",
            dec!(2000),
            Utc::now() + Duration::days(30),
            OptionType::Call,
        );

        let pricing = model.price(&contract, dec!(2000), 0.50).unwrap();

        // ATM call should have positive price
        assert!(pricing.price > Decimal::ZERO);
        // Delta should be around 0.5 for ATM
        assert!(pricing.greeks.delta > 0.4 && pricing.greeks.delta < 0.6);
        // Gamma should be positive
        assert!(pricing.greeks.gamma > 0.0);
        // Theta should be negative (time decay)
        assert!(pricing.greeks.theta < 0.0);
        // Vega should be positive
        assert!(pricing.greeks.vega > 0.0);
    }

    #[test]
    fn test_black_scholes_put() {
        let model = BlackScholes::new(0.05, 0.0);
        let contract = OptionContract::new(
            "ETH",
            dec!(2000),
            Utc::now() + Duration::days(30),
            OptionType::Put,
        );

        let pricing = model.price(&contract, dec!(2000), 0.50).unwrap();

        // ATM put should have positive price
        assert!(pricing.price > Decimal::ZERO);
        // Delta should be around -0.5 for ATM put
        assert!(pricing.greeks.delta > -0.6 && pricing.greeks.delta < -0.4);
    }

    #[test]
    fn test_put_call_parity() {
        let model = BlackScholes::new(0.05, 0.0);
        let expiry = Utc::now() + Duration::days(30);

        let call = OptionContract::new("ETH", dec!(2000), expiry, OptionType::Call);
        let put = OptionContract::new("ETH", dec!(2000), expiry, OptionType::Put);

        let call_price = model.price(&call, dec!(2000), 0.50).unwrap().price;
        let put_price = model.price(&put, dec!(2000), 0.50).unwrap().price;

        // Put-call parity: C - P = S - K * e^(-rT)
        // At the money: C - P ≈ S * (1 - e^(-rT))
        let parity_diff = call_price - put_price;
        assert!(parity_diff.abs() < dec!(10)); // Should be small for ATM
    }

    #[test]
    fn test_implied_volatility() {
        let model = BlackScholes::new(0.05, 0.0);
        let contract = OptionContract::new(
            "ETH",
            dec!(2000),
            Utc::now() + Duration::days(30),
            OptionType::Call,
        );

        // Price with known vol
        let vol = 0.50;
        let pricing = model.price(&contract, dec!(2000), vol).unwrap();

        // Calculate IV from that price
        let iv = model.implied_volatility(&contract, dec!(2000), pricing.price).unwrap();

        // Should match original vol
        assert!((iv - vol).abs() < 0.01);
    }

    #[test]
    fn test_futures_pricing() {
        let model = FuturesModel::new(0.05);
        let contract = FuturesContract::new("ETH", Utc::now() + Duration::days(90), dec!(1));

        let pricing = model.price(&contract, dec!(2000)).unwrap();

        // Futures should be at premium (positive cost of carry)
        assert!(pricing.fair_value > dec!(2000));
        assert!(pricing.basis > Decimal::ZERO);
    }

    #[test]
    fn test_perpetual_funding() {
        let model = PerpetualModel::new();

        // Premium (mark > index) = positive funding (longs pay shorts)
        let funding = model.calculate_funding(dec!(2020), dec!(2000)).unwrap();
        assert!(funding > 0.0);

        // Discount (mark < index) = negative funding (shorts pay longs)
        let funding = model.calculate_funding(dec!(1980), dec!(2000)).unwrap();
        assert!(funding < 0.0);
    }

    #[test]
    fn test_volatility_surface() {
        let mut surface = VolatilitySurface::new("ETH", 0.50);
        surface.skew = -0.1; // Negative skew (puts more expensive)

        // OTM put (negative moneyness) should have higher vol
        let otm_put_vol = surface.get_vol(-0.2, 0.25);
        let atm_vol = surface.get_vol(0.0, 0.25);
        let otm_call_vol = surface.get_vol(0.2, 0.25);

        assert!(otm_put_vol > atm_vol);
        assert!(otm_call_vol < atm_vol);
    }
}
