"""Simple Greeks calculator (Black-76 / BS).

纯 Python/numpy 实现的希腊值计算器。
"""

import math

import pandas as pd

from .base import GreeksCalculator, GreeksModel, OptionType


class SimpleGreeksCalculator(GreeksCalculator):
    """Simple Greeks calculator using analytical formulas.

    基于解析公式的希腊值计算器，支持 Black-76（期货期权）和 BS（现货期权）模型。
    """

    def calculate_greeks(
        self,
        S: float,
        K: float,
        T: float,
        r: float,
        sigma: float,
        option_type: OptionType,
        model: GreeksModel = "black76",
    ) -> pd.DataFrame:
        """Calculate Greeks and theoretical price for a single option.

        Args:
            S: Underlying/futures price.
            K: Strike price.
            T: Time to expiry in years.
            r: Risk-free rate.
            sigma: Volatility (annualized).
            option_type: 'call' or 'put'.
            model: 'black76' or 'bs'.

        Returns:
            pd.DataFrame (single row): delta, gamma, theta, vega, rho, iv, theoretical_value

        Raises:
            ValueError: If parameters are invalid.
        """
        self._validate_params(S, K, T, sigma)

        if model == "black76":
            return self._black76_greeks(S, K, T, r, sigma, option_type)
        return self._bs_greeks(S, K, T, r, sigma, option_type)

    def calculate_iv(
        self,
        S: float,
        K: float,
        T: float,
        r: float,
        option_price: float,
        option_type: OptionType,
        model: GreeksModel = "black76",
    ) -> float:
        """Solve implied volatility via bisection method.

        Args:
            S: Underlying/futures price.
            K: Strike price.
            T: Time to expiry in years.
            r: Risk-free rate.
            option_price: Market price.
            option_type: 'call' or 'put'.
            model: 'black76' or 'bs'.

        Returns:
            float: Implied volatility.

        Raises:
            ValueError: If no solution exists.
        """
        if option_price <= 0 or S <= 0 or K <= 0 or T <= 0:
            raise ValueError("S, K, T, option_price must be positive.")

        low, high = 1e-6, 5.0

        price_low = self.calculate_greeks(S, K, T, r, low, option_type, model)[
            "theoretical_value"
        ].iloc[0]
        price_high = self.calculate_greeks(S, K, T, r, high, option_type, model)[
            "theoretical_value"
        ].iloc[0]

        if option_price < price_low or option_price > price_high:
            raise ValueError(
                f"Option price {option_price} not bracketed by sigma in "
                f"[{low}, {high}] (theo range [{price_low:.6f}, {price_high:.6f}])."
            )

        for _ in range(200):
            mid = (low + high) / 2
            greeks_df = self.calculate_greeks(S, K, T, r, mid, option_type, model)
            theo = greeks_df["theoretical_value"].iloc[0]
            if abs(theo - option_price) < 1e-8:
                return mid
            if theo > option_price:
                high = mid
            else:
                low = mid

        return (low + high) / 2

    def _black76_greeks(
        self, F: float, K: float, T: float, r: float, sigma: float, option_type: OptionType
    ) -> pd.DataFrame:
        """Black-76 model Greeks (options on futures).

        Args:
            F: Futures price.
            K: Strike.
            T: Time to expiry.
            r: Risk-free rate.
            sigma: Volatility.
            option_type: 'call' or 'put'.

        Returns:
            pd.DataFrame: Single-row Greeks.
        """
        d1 = (math.log(F / K) + 0.5 * sigma**2 * T) / (sigma * math.sqrt(T))
        d2 = d1 - sigma * math.sqrt(T)
        discount = math.exp(-r * T)

        nd1 = _norm_cdf(d1)
        nd2 = _norm_cdf(d2)
        pdf_d1 = _norm_pdf(d1)

        if option_type == "call":
            price = discount * (F * nd1 - K * nd2)
            delta = discount * nd1
        else:
            price = discount * (K * _norm_cdf(-d2) - F * _norm_cdf(-d1))
            delta = -discount * _norm_cdf(-d1)

        rho = -T * price / 100
        gamma = discount * pdf_d1 / (F * sigma * math.sqrt(T))
        vega = F * discount * pdf_d1 * math.sqrt(T) / 100
        theta = (
            -(F * discount * pdf_d1 * sigma) / (2 * math.sqrt(T)) + r * price
        ) / 365

        return pd.DataFrame([{
            "delta": delta, "gamma": gamma, "theta": theta,
            "vega": vega, "rho": rho, "iv": sigma, "theoretical_value": price,
        }])

    def _bs_greeks(
        self, S: float, K: float, T: float, r: float, sigma: float, option_type: OptionType
    ) -> pd.DataFrame:
        """Black-Scholes model Greeks (spot options).

        Args:
            S: Spot price.
            K: Strike.
            T: Time to expiry.
            r: Risk-free rate.
            sigma: Volatility.
            option_type: 'call' or 'put'.

        Returns:
            pd.DataFrame: Single-row Greeks.
        """
        d1 = (math.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * math.sqrt(T))
        d2 = d1 - sigma * math.sqrt(T)
        discount = math.exp(-r * T)

        nd1 = _norm_cdf(d1)
        nd2 = _norm_cdf(d2)
        pdf_d1 = _norm_pdf(d1)

        gamma = pdf_d1 / (S * sigma * math.sqrt(T))
        vega = S * pdf_d1 * math.sqrt(T) / 100

        if option_type == "call":
            price = S * nd1 - K * discount * nd2
            delta = nd1
            rho = K * T * discount * nd2 / 100
            theta = (
                -(S * pdf_d1 * sigma) / (2 * math.sqrt(T))
                - r * K * discount * nd2
            ) / 365
        else:
            price = K * discount * _norm_cdf(-d2) - S * _norm_cdf(-d1)
            delta = nd1 - 1
            rho = -K * T * discount * _norm_cdf(-d2) / 100
            theta = (
                -(S * pdf_d1 * sigma) / (2 * math.sqrt(T))
                + r * K * discount * _norm_cdf(-d2)
            ) / 365

        return pd.DataFrame([{
            "delta": delta, "gamma": gamma, "theta": theta,
            "vega": vega, "rho": rho, "iv": sigma, "theoretical_value": price,
        }])

    @staticmethod
    def _validate_params(S: float, K: float, T: float, sigma: float) -> None:
        """Validate input parameters.

        Raises:
            ValueError: If any parameter is non-positive.
        """
        if S <= 0 or K <= 0 or T <= 0 or sigma <= 0:
            raise ValueError("S, K, T, sigma must be positive.")


def _norm_cdf(x: float) -> float:
    """Standard normal CDF using math.erfc (no scipy dependency)."""
    return 0.5 * math.erfc(-x / math.sqrt(2))


def _norm_pdf(x: float) -> float:
    """Standard normal PDF."""
    return math.exp(-0.5 * x * x) / math.sqrt(2 * math.pi)
