"""Option premium simulation — pure-python Black-Scholes.

Used to mark option paper positions to market: we reprice the contract from
the current underlying price, remaining time, and the IV captured at entry,
then scale the trade's actual entry premium by the model ratio. This keeps
the simulation anchored to the real (or mock) quoted premium while modeling
delta moves and theta decay realistically.
"""
import math
from datetime import date, datetime

RISK_FREE_RATE = 0.045
MIN_PREMIUM = 0.01


def _norm_cdf(x: float) -> float:
    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))


def bs_price(option_type: str, underlying: float, strike: float,
             years_to_expiry: float, iv: float, r: float = RISK_FREE_RATE) -> float:
    """Black-Scholes European option price. Falls back to intrinsic at expiry."""
    is_call = option_type.lower() == "call"
    if underlying <= 0 or strike <= 0:
        return MIN_PREMIUM
    if years_to_expiry <= 0 or iv <= 0:
        intrinsic = max(0.0, underlying - strike) if is_call else max(0.0, strike - underlying)
        return max(MIN_PREMIUM, intrinsic)

    sqrt_t = math.sqrt(years_to_expiry)
    d1 = (math.log(underlying / strike) + (r + iv * iv / 2) * years_to_expiry) / (iv * sqrt_t)
    d2 = d1 - iv * sqrt_t
    if is_call:
        price = underlying * _norm_cdf(d1) - strike * math.exp(-r * years_to_expiry) * _norm_cdf(d2)
    else:
        price = strike * math.exp(-r * years_to_expiry) * _norm_cdf(-d2) - underlying * _norm_cdf(-d1)
    return max(MIN_PREMIUM, price)


def years_between(start: date | datetime, end_iso: str) -> float:
    """Years from `start` to an ISO date string (YYYY-MM-DD), floored at 0."""
    if isinstance(start, datetime):
        start = start.date()
    try:
        end = date.fromisoformat(end_iso[:10])
    except ValueError:
        return 0.0
    return max(0.0, (end - start).days / 365.0)


def reprice_premium(contract: dict, entry_premium: float, entry_underlying: float,
                    entry_date: date | datetime, current_underlying: float,
                    as_of: date | datetime | None = None) -> float:
    """Current estimated premium for a held contract.

    premium_now = entry_premium × BS(now) / BS(entry) — anchored to the real
    quoted entry price, so model error cancels out at t=0.
    """
    as_of = as_of or date.today()
    opt_type = contract.get("type", "call")
    strike = float(contract.get("strike", 0))
    iv = float(contract.get("iv", 0.5)) or 0.5
    expiry = str(contract.get("expiry", ""))

    t_entry = years_between(entry_date, expiry)
    t_now = years_between(as_of, expiry)
    model_entry = bs_price(opt_type, entry_underlying, strike, t_entry, iv)
    model_now = bs_price(opt_type, current_underlying, strike, t_now, iv)
    if model_entry <= MIN_PREMIUM:
        return max(MIN_PREMIUM, model_now)
    return round(max(MIN_PREMIUM, entry_premium * model_now / model_entry), 2)


def size_contracts(entry_premium: float, max_risk_usd: float) -> int:
    """Contracts such that total premium (max loss on a long option) stays
    within the per-trade risk budget. 1 contract = 100 shares."""
    if entry_premium <= 0:
        return 0
    return max(0, int(max_risk_usd / (entry_premium * 100)))
