"""
Real option chain fetcher for the Strategy Builder — reuses the same yfinance
proxy/resolution logic as iv_engine.py (futures/indices → optionable ETFs).
"""
import logging
import math
from datetime import date, datetime
from typing import Any, Dict, List, Optional

import yfinance as yf

from services.iv_engine import _resolve_ticker, _get_current_price

logger = logging.getLogger(__name__)


def _num(v: Any, default: float = 0.0) -> float:
    try:
        f = float(v)
        return default if math.isnan(f) else f
    except (TypeError, ValueError):
        return default


def _rows_from_df(df) -> List[Dict[str, Any]]:
    rows = []
    for _, r in df.iterrows():
        bid = _num(r.get("bid"))
        ask = _num(r.get("ask"))
        rows.append({
            "strike": _num(r.get("strike")),
            "bid": bid,
            "ask": ask,
            "mid": round((bid + ask) / 2, 4) if (bid > 0 and ask > 0) else _num(r.get("lastPrice")),
            "last": _num(r.get("lastPrice")),
            "iv": _num(r.get("impliedVolatility")),
            "open_interest": int(_num(r.get("openInterest"))),
            "volume": int(_num(r.get("volume"))),
        })
    return sorted(rows, key=lambda x: x["strike"])


def get_chain_slice(symbol: str, target_days: int = 8, n_expiries: int = 3) -> Dict[str, Any]:
    """
    Fetch the real option chain for `symbol` around a target horizon (days).
    Returns the `n_expiries` expirations closest to target_days, each with
    normalized calls/puts rows (strike, bid, ask, mid, last, iv, open_interest, volume).
    """
    proxy = _resolve_ticker(symbol)
    t = yf.Ticker(proxy)
    spot = _get_current_price(t)
    if not spot:
        raise ValueError(f"Impossible d'obtenir le prix spot pour {symbol} ({proxy})")

    expirations = t.options
    if not expirations:
        raise ValueError(f"Aucune chaîne d'options disponible pour {symbol} ({proxy})")

    today = date.today()
    dated = sorted(
        expirations,
        key=lambda e: abs((datetime.strptime(e, "%Y-%m-%d").date() - today).days - target_days),
    )[:max(1, n_expiries)]

    expiries_out = []
    for exp in dated:
        try:
            chain = t.option_chain(exp)
            days_to_expiry = (datetime.strptime(exp, "%Y-%m-%d").date() - today).days
            expiries_out.append({
                "expiry_date": exp,
                "days_to_expiry": days_to_expiry,
                "calls": _rows_from_df(chain.calls),
                "puts": _rows_from_df(chain.puts),
            })
        except Exception as e:
            logger.debug(f"[OptionChain] {proxy} {exp}: {e}")

    if not expiries_out:
        raise ValueError(f"Aucune chaîne exploitable pour {symbol} ({proxy})")

    return {
        "symbol": symbol.upper(),
        "proxy": proxy,
        "spot": round(float(spot), 4),
        "expiries": expiries_out,
    }


def find_quote(chain_slice: Dict[str, Any], expiry_date: str, strike: float, option_type: str) -> Optional[Dict[str, Any]]:
    """Look up a single contract's quote row within a previously fetched chain slice."""
    for exp in chain_slice["expiries"]:
        if exp["expiry_date"] != expiry_date:
            continue
        rows = exp["calls"] if option_type == "call" else exp["puts"]
        for row in rows:
            if abs(row["strike"] - strike) < 1e-6:
                return row
    return None
