"""LIVE 期权到期交割：从交易所账单/行权记录取结算现金，禁止本地 intrinsic 发明。"""

from __future__ import annotations

import logging
import time
from dataclasses import dataclass
from typing import Any

logger = logging.getLogger(__name__)

# OKX bills subType：行权 / 对手行权 / 虚值到期
_OKX_EXERCISE_SUBTYPES = frozenset({"170", "171", "172"})


@dataclass(slots=True)
class OptionSettlement:
    """qty_eth>0 时 fill_px = notional/qty_eth；cash 为入账净额(已扣费)。"""

    found: bool
    notional: float = 0.0  # 结算毛额（入账前）
    fee: float = 0.0
    cash: float = 0.0  # notional - fee
    fill_px: float = 0.0
    source: str = ""
    detail: str = ""


def settlement_to_fill(
    st: OptionSettlement, *, qty_eth: float
) -> tuple[float, float, float]:
    """返回 (fill_px, fee, notional)。未找到则全 0。"""
    if not st.found:
        return 0.0, 0.0, 0.0
    q = float(qty_eth)
    if q > 1e-12 and st.fill_px <= 0 and st.notional > 0:
        return float(st.notional) / q, float(st.fee), float(st.notional)
    return float(st.fill_px), float(st.fee), float(st.notional)


def fetch_option_settlement(
    client: Any,
    *,
    exchange: str,
    option_inst_id: str,
    qty_eth: float,
    begin_ms: int | None,
    end_ms: int | None = None,
) -> OptionSettlement:
    """查交易所期权交割/行权入账。查不到 → found=False（调用方零价镜像、不发明）。"""
    inst = str(option_inst_id or "").strip()
    if not inst or client is None:
        return OptionSettlement(found=False, detail="no_inst_or_client")
    begin = int(begin_ms or 0)
    end = int(end_ms or int(time.time() * 1000))
    if begin <= 0:
        # 无开仓时间：收窄到近 48h，避免扫全量
        begin = end - 48 * 3600 * 1000
    ex = (exchange or "").strip().lower()
    try:
        if ex in ("binance", "bn"):
            return _bn_settlement(client, inst, qty_eth=qty_eth, begin=begin, end=end)
        return _okx_settlement(client, inst, qty_eth=qty_eth, begin=begin, end=end)
    except Exception as e:
        logger.warning("fetch_option_settlement failed %s %s: %s", ex, inst, e)
        return OptionSettlement(found=False, detail=str(e)[:160])


def _okx_settlement(
    client: Any, inst_id: str, *, qty_eth: float, begin: int, end: int
) -> OptionSettlement:
    from .money import to_usdt
    from ..exchange.okx.parse import safe_float

    getter = getattr(client, "get_option_settlement_bills", None)
    if callable(getter):
        rows = getter(inst_id, begin_ms=begin, end_ms=end)
    else:
        rows = None
    if rows is None:
        return OptionSettlement(found=False, detail="okx_bills_unavailable")

    cash = 0.0
    fee = 0.0
    hit = False
    for row in rows:
        if not isinstance(row, dict):
            continue
        sub = str(row.get("subType") or "")
        typ = str(row.get("type") or "")
        if sub not in _OKX_EXERCISE_SUBTYPES and typ not in ("3",):
            continue
        ts = int(safe_float(row.get("ts")) or 0)
        if ts and (ts < begin - 120_000 or ts > end + 120_000):
            continue
        hit = True
        raw = safe_float(row.get("balChg"))
        if raw is None:
            raw = safe_float(row.get("pnl"))
        ccy = str(row.get("ccy") or "USDT")
        if raw is not None:
            cash += to_usdt(float(raw), ccy)
        fraw = safe_float(row.get("fee"))
        if fraw is not None:
            # OKX fee 常为负
            fee += abs(to_usdt(float(fraw), ccy))
        # 有些账单 pnl 已含费，fee 字段为 0

    if not hit:
        return OptionSettlement(found=False, detail="okx_no_exercise_bill")

    # balChg/pnl 视为账户净变动；fee 另计时用净额+费还原毛额作 fill notional
    net_cash = float(cash)
    fee = float(fee)
    if net_cash >= 0:
        notional = float(net_cash) + fee
    else:
        notional = 0.0

    q = float(qty_eth)
    fill_px = (notional / q) if q > 1e-12 and notional > 0 else 0.0
    return OptionSettlement(
        found=True,
        notional=float(notional),
        fee=float(fee),
        cash=float(net_cash),
        fill_px=float(fill_px),
        source="okx_bills",
        detail=f"subTypes exercise bills cash={net_cash:.6f}",
    )


def _bn_settlement(
    client: Any, symbol: str, *, qty_eth: float, begin: int, end: int
) -> OptionSettlement:
    from .money import to_usdt
    from ..exchange.okx.parse import safe_float

    getter = getattr(client, "get_option_exercise_records", None)
    if not callable(getter):
        return OptionSettlement(found=False, detail="bn_exercise_api_missing")
    rows = getter(symbol, begin_ms=begin, end_ms=end)
    if rows is None:
        return OptionSettlement(found=False, detail="bn_exercise_unavailable")
    if not rows:
        return OptionSettlement(found=False, detail="bn_no_exercise_record")

    amount = 0.0
    fee = 0.0
    hit = False
    for row in rows:
        if not isinstance(row, dict):
            continue
        if str(row.get("symbol") or "") and str(row.get("symbol")) != symbol:
            continue
        hit = True
        ccy = str(row.get("currency") or row.get("quoteAsset") or "USDT")
        am = safe_float(row.get("amount"))
        if am is not None:
            amount += to_usdt(float(am), ccy)
        fr = safe_float(row.get("fee"))
        if fr is not None:
            fee += abs(to_usdt(float(fr), ccy))

    if not hit:
        return OptionSettlement(found=False, detail="bn_no_matching_record")

    notional = max(0.0, float(amount))
    net = float(amount) - float(fee)
    q = float(qty_eth)
    fill_px = (notional / q) if q > 1e-12 and notional > 0 else 0.0
    return OptionSettlement(
        found=True,
        notional=notional,
        fee=float(fee),
        cash=float(net),
        fill_px=float(fill_px),
        source="binance_exerciseRecord",
        detail=f"amount={amount:.6f} fee={fee:.6f}",
    )
