"""
AkDevz AI Market Intelligence — Options Intelligence Engine
Black-Scholes analytical Greeks, PCR, Max Pain, and strike-wise OI buildup analysis.
"""
import math
from typing import Dict, List, Any, Optional

class OptionsEngine:
    @staticmethod
    def normal_cdf(x: float) -> float:
        """Approximation of Cumulative Standard Normal Distribution."""
        return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))

    @staticmethod
    def normal_pdf(x: float) -> float:
        """Standard Normal Probability Density Function."""
        return (1.0 / math.sqrt(2.0 * math.pi)) * math.exp(-0.5 * x * x)

    @staticmethod
    def calculate_greeks(
        spot: float,
        strike: float,
        time_to_expiry_years: float,
        volatility: float,
        risk_free_rate: float = 0.07,
        option_type: str = "CE"
    ) -> Dict[str, float]:
        """
        Black-Scholes European analytical Greeks: Delta, Gamma, Theta, Vega.
        """
        if spot <= 0 or strike <= 0 or time_to_expiry_years <= 0 or volatility <= 0:
            return {"delta": 0.0, "gamma": 0.0, "theta": 0.0, "vega": 0.0}

        S = spot
        K = strike
        T = time_to_expiry_years
        r = risk_free_rate
        sigma = volatility

        d1 = (math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * math.sqrt(T))
        d2 = d1 - sigma * math.sqrt(T)

        pdf_d1 = OptionsEngine.normal_pdf(d1)
        gamma = pdf_d1 / (S * sigma * math.sqrt(T))
        vega = S * pdf_d1 * math.sqrt(T) / 100.0  # per 1% change in vol

        if option_type.upper() == "CE":
            delta = OptionsEngine.normal_cdf(d1)
            theta = (-(S * pdf_d1 * sigma) / (2.0 * math.sqrt(T)) - r * K * math.exp(-r * T) * OptionsEngine.normal_cdf(d2)) / 365.0
        else:
            delta = OptionsEngine.normal_cdf(d1) - 1.0
            theta = (-(S * pdf_d1 * sigma) / (2.0 * math.sqrt(T)) + r * K * math.exp(-r * T) * OptionsEngine.normal_cdf(-d2)) / 365.0

        return {
            "delta": round(delta, 4),
            "gamma": round(gamma, 6),
            "theta": round(theta, 4),
            "vega": round(vega, 4),
        }

    @staticmethod
    def calculate_pcr(chain_data: List[Dict[str, Any]]) -> Dict[str, float]:
        """
        Calculates Put-Call Ratio (PCR) by Open Interest and Volume.
        """
        total_call_oi = 0
        total_put_oi = 0
        total_call_vol = 0
        total_put_vol = 0

        for row in chain_data:
            ce = row.get("ce") or {}
            pe = row.get("pe") or {}

            total_call_oi += int(ce.get("oi", 0))
            total_put_oi += int(pe.get("oi", 0))
            total_call_vol += int(ce.get("volume", 0))
            total_put_vol += int(pe.get("volume", 0))

        pcr_oi = round(total_put_oi / total_call_oi, 3) if total_call_oi > 0 else 1.0
        pcr_vol = round(total_put_vol / total_call_vol, 3) if total_call_vol > 0 else 1.0

        return {
            "pcr_oi": pcr_oi,
            "pcr_volume": pcr_vol,
            "total_call_oi": total_call_oi,
            "total_put_oi": total_put_oi,
        }

    @staticmethod
    def find_max_pain(chain_data: List[Dict[str, Any]]) -> float:
        """
        Calculates Max Pain strike where option sellers lose the least money.
        """
        strikes = []
        for row in chain_data:
            s = float(row.get("strike_price", 0))
            if s > 0:
                strikes.append(s)

        if not strikes:
            return 0.0

        min_loss = float("inf")
        max_pain_strike = strikes[len(strikes) // 2]

        for test_strike in strikes:
            total_payout = 0.0
            for row in chain_data:
                s = float(row.get("strike_price", 0))
                ce = row.get("ce") or {}
                pe = row.get("pe") or {}
                call_oi = int(ce.get("oi", 0))
                put_oi = int(pe.get("oi", 0))

                # Call buyer payout if price ends at test_strike
                if test_strike > s:
                    total_payout += (test_strike - s) * call_oi
                # Put buyer payout if price ends at test_strike
                if test_strike < s:
                    total_payout += (s - test_strike) * put_oi

            if total_payout < min_loss:
                min_loss = total_payout
                max_pain_strike = test_strike

        return max_pain_strike

    @staticmethod
    def find_key_oi_levels(chain_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        Identifies highest Call OI (Resistance barrier) and highest Put OI (Support barrier).
        """
        max_call_oi = -1
        max_call_strike = 0.0
        max_put_oi = -1
        max_put_strike = 0.0

        for row in chain_data:
            s = float(row.get("strike_price", 0))
            ce = row.get("ce") or {}
            pe = row.get("pe") or {}
            c_oi = int(ce.get("oi", 0))
            p_oi = int(pe.get("oi", 0))

            if c_oi > max_call_oi:
                max_call_oi = c_oi
                max_call_strike = s
            if p_oi > max_put_oi:
                max_put_oi = p_oi
                max_put_strike = s

        return {
            "call_resistance_strike": max_call_strike,
            "call_resistance_oi": max_call_oi,
            "put_support_strike": max_put_strike,
            "put_support_oi": max_put_oi,
        }
