"""期权按买盘平仓门控:可回收/净盈亏换算为 USDT 后校验,并持续 hold 秒才允许平仓."""
from __future__ import annotations

import os
import threading
import time
from typing import Any


def _env_float(key: str, default: float) -> float:
    try:
        return float(os.getenv(key, str(default)))
    except (TypeError, ValueError):
        return default


def _env_optional_float(key: str) -> float | None:
    raw = os.getenv(key)
    if raw is None or str(raw).strip() == "":
        return None
    try:
        return float(raw)
    except (TypeError, ValueError):
        return None


CLOSE_RECYCLE_HOLD_SECONDS = _env_float("OKX_OPTIONS_CLOSE_HOLD_SECONDS", 120.0)


def close_net_pnl_min_u() -> float:
    return _env_float("OKX_OPTIONS_CLOSE_NET_PNL_MIN_U", 0.0)


def close_gate_mode() -> str:
    m = (os.getenv("OKX_OPTIONS_CLOSE_GATE_MODE") or "premium").strip().lower()
    return m if m in ("premium", "net_pnl") else "premium"


def resolve_close_recycle_mult(premium_ccy: str | None, min_mult: float | None = None) -> float:
    if min_mult is not None:
        m = float(min_mult)
        return m if m > 0 else 1.05
    global_mult = _env_optional_float("OKX_OPTIONS_CLOSE_RECYCLE_MULT")
    if global_mult is not None and global_mult > 0:
        return global_mult
    ccy = (premium_ccy or "USDC").strip().upper() or "USDC"
    if ccy in ("ETH", "BTC"):
        return _env_float("OKX_OPTIONS_CLOSE_RECYCLE_MULT_COIN", 1.05)
    return _env_float("OKX_OPTIONS_CLOSE_RECYCLE_MULT_USDC", 2.0)


# 兼容旧引用
CLOSE_RECYCLE_MIN_MULT = resolve_close_recycle_mult("USDC")

_lock = threading.Lock()
# inst_id -> {"ok_since": float|None, "recycle": float, "premium": float, "updated": float}
_gates: dict[str, dict[str, Any]] = {}


def _safe_float(v: Any) -> float | None:
    if v is None or v == "":
        return None
    try:
        return float(v)
    except (TypeError, ValueError):
        return None


def _normalize_premium_ccy(premium_ccy: str | None) -> str:
    ccy = (premium_ccy or "USDC").strip().upper() or "USDC"
    if ccy not in ("ETH", "BTC", "USDC"):
        ccy = "USDC"
    return ccy


def _to_usdt(amount: float | None, ccy: str, index_px: float | None) -> float | None:
    if amount is None:
        return None
    unit = _normalize_premium_ccy(ccy)
    if unit in ("ETH", "BTC"):
        idx = _safe_float(index_px)
        if idx is None or idx <= 0:
            return None
        return float(amount) * float(idx)
    return float(amount)


def _fmt_gate_amt(v: float, *, ccy: str) -> str:
    unit = _normalize_premium_ccy(ccy)
    if unit in ("ETH", "BTC"):
        txt = f"{float(v):.8f}".rstrip("0").rstrip(".")
        return txt or "0"
    return f"{float(v):.4f}"


def _fmt_usdt(v: float | None) -> str:
    if v is None:
        return "—"
    return f"{float(v):.2f}"


def clear_close_gate(inst_id: str | None = None) -> None:
    with _lock:
        if inst_id:
            _gates.pop(str(inst_id).strip(), None)
        else:
            _gates.clear()


def mark_close_gate_passed(inst_id: str) -> None:
    """标记同仓已通过门控,续批平仓只验流动性."""
    inst = (inst_id or "").strip()
    if not inst:
        return
    with _lock:
        st = _gates.get(inst) or {}
        st["passed"] = True
        st["updated"] = time.time()
        _gates[inst] = st


def is_close_gate_passed(inst_id: str) -> bool:
    inst = (inst_id or "").strip()
    if not inst:
        return False
    with _lock:
        return bool((_gates.get(inst) or {}).get("passed"))


def update_close_gate(
    inst_id: str,
    *,
    recycle_usdc: float | None,
    premium_paid: float | None,
    now: float | None = None,
    min_mult: float | None = None,
    hold_seconds: float | None = None,
    premium_ccy: str | None = None,
    index_px: float | None = None,
) -> dict[str, Any]:
    """
    根据当前买盘可回收金额刷新门控(比较口径均为 USDT 估值).
    premium 模式:可回收(U) ≥ 权利金(U) × 倍数
    net_pnl 模式:净盈亏(U) > OKX_OPTIONS_CLOSE_NET_PNL_MIN_U
    """
    inst = (inst_id or "").strip()
    if not inst:
        return {
            "ok": False,
            "ready": False,
            "recycle_ok": False,
            "msg": "缺少合约",
        }
    ts = float(now if now is not None else time.time())
    mode = close_gate_mode()
    hold = float(hold_seconds if hold_seconds is not None else CLOSE_RECYCLE_HOLD_SECONDS)
    if hold < 0:
        hold = 0.0

    with _lock:
        prev_ccy = (_gates.get(inst) or {}).get("premium_ccy")
    ccy = _normalize_premium_ccy(premium_ccy or prev_ccy)
    mult = resolve_close_recycle_mult(ccy, min_mult)
    min_pnl_u = close_net_pnl_min_u()

    prem = _safe_float(premium_paid)
    recv = _safe_float(recycle_usdc)
    recv_u = _to_usdt(recv, ccy, index_px)
    prem_u = _to_usdt(prem, ccy, index_px)
    net_u = round(recv_u - prem_u, 4) if recv_u is not None and prem_u is not None else None
    need_u = round(prem_u * mult, 4) if prem_u is not None and prem_u > 0 and mode == "premium" else None

    missing_index = ccy in ("ETH", "BTC") and (index_px is None or _safe_float(index_px) is None or _safe_float(index_px) <= 0)

    if missing_index and prem is not None and prem > 0 and recv is not None:
        recycle_ok = False
    elif mode == "net_pnl":
        recycle_ok = bool(net_u is not None and net_u > min_pnl_u + 1e-9)
    else:
        recycle_ok = bool(
            prem_u is not None
            and prem_u > 0
            and recv_u is not None
            and need_u is not None
            and recv_u + 1e-9 >= need_u
        )

    with _lock:
        prev = _gates.get(inst) or {}
        ok_since = prev.get("ok_since")
        if recycle_ok:
            if ok_since is None:
                ok_since = ts
        else:
            ok_since = None
        held = (ts - float(ok_since)) if ok_since is not None else 0.0
        ready = bool(recycle_ok and held + 1e-9 >= hold)
        prev_passed = bool(prev.get("passed"))
        passed = prev_passed or ready
        state = {
            "ok_since": ok_since,
            "recycle": recv,
            "premium": prem,
            "need": need_u,
            "updated": ts,
            "min_mult": mult,
            "hold_seconds": hold,
            "passed": passed,
            "premium_ccy": ccy,
            "gate_mode": mode,
            "index_px": _safe_float(index_px),
            "recycle_usdt": recv_u,
            "premium_usdt": prem_u,
            "need_recycle_usdt": need_u,
            "net_pnl_usdt": net_u,
            "net_pnl_min_u": min_pnl_u if mode == "net_pnl" else None,
        }
        _gates[inst] = state

    remain = max(0.0, hold - held) if recycle_ok and not ready else None
    if prem is None or prem <= 0:
        msg = "缺少权利金,无法校验平仓门控"
    elif recv is None:
        msg = "暂无有效买盘可回收金额"
    elif missing_index:
        msg = "缺少指数价,无法按 USDT 校验目标平仓门控"
    elif mode == "net_pnl":
        if not recycle_ok:
            msg = (
                f"净盈亏 {_fmt_usdt(net_u)}U(估) ≤ {_fmt_usdt(min_pnl_u)}U,"
                f"目标平仓门控未过"
            )
        elif not ready:
            msg = (
                f"净盈亏 {_fmt_usdt(net_u)}U(估) 已>{_fmt_usdt(min_pnl_u)}U,"
                f"需再持续 {remain:.0f}s(已 {held:.0f}/{hold:.0f}s)门控才通过"
            )
        else:
            msg = (
                f"净盈亏 {_fmt_usdt(net_u)}U(估) 已>{_fmt_usdt(min_pnl_u)}U"
                f"且持续≥{hold:.0f}s,目标触达后可按买一平仓"
            )
    elif not recycle_ok:
        msg = (
            f"可回收 {_fmt_usdt(recv_u)}U(估) < 权利金×{mult:g}"
            f"({_fmt_usdt(need_u)}U),目标平仓门控未过"
        )
    elif not ready:
        msg = (
            f"可回收 {_fmt_usdt(recv_u)}U(估) 已达×{mult:g}"
            f"({_fmt_usdt(recv_u)}/{_fmt_usdt(need_u)}U),"
            f"需再持续 {remain:.0f}s(已 {held:.0f}/{hold:.0f}s)门控才通过"
        )
    else:
        msg = (
            f"可回收 {_fmt_usdt(recv_u)}U(估) 已达×{mult:g}且持续≥{hold:.0f}s,"
            f"目标触达后可按买一平仓"
        )

    auto_blocked = not (ready or passed)
    return {
        "ok": True,
        "ready": ready,
        "passed": passed,
        "recycle_ok": recycle_ok,
        "recycle_usdc": recv,
        "premium_paid": prem,
        "need_recycle_usdc": need_u,
        "premium_ccy": ccy,
        "min_mult": mult,
        "hold_seconds": hold,
        "held_seconds": round(held, 1) if recycle_ok else 0.0,
        "remain_seconds": round(remain, 1) if remain is not None else None,
        "ok_since": ok_since,
        "msg": msg,
        "gate_mode": mode,
        "index_px": _safe_float(index_px),
        "recycle_usdt": recv_u,
        "premium_usdt": prem_u,
        "need_recycle_usdt": need_u,
        "net_pnl_usdt": net_u,
        "net_pnl_min_u": min_pnl_u if mode == "net_pnl" else None,
        "auto_close_blocked": auto_blocked,
        "close_gate_blocked": auto_blocked,
    }


def check_close_gate(
    inst_id: str,
    *,
    recycle_usdc: float | None = None,
    premium_paid: float | None = None,
    premium_ccy: str | None = None,
    index_px: float | None = None,
    refresh: bool = True,
) -> dict[str, Any]:
    """检查是否允许平仓;默认先用最新回收/权利金刷新."""
    inst = (inst_id or "").strip()
    if refresh:
        if recycle_usdc is None or premium_paid is None or premium_ccy is None or index_px is None:
            with _lock:
                prev = _gates.get(inst) or {}
            if recycle_usdc is None:
                recycle_usdc = prev.get("recycle")
            if premium_paid is None:
                premium_paid = prev.get("premium")
            if premium_ccy is None:
                premium_ccy = prev.get("premium_ccy")
            if index_px is None:
                index_px = prev.get("index_px")
        return update_close_gate(
            inst,
            recycle_usdc=recycle_usdc,
            premium_paid=premium_paid,
            premium_ccy=premium_ccy,
            index_px=index_px,
        )
    with _lock:
        prev = _gates.get(inst)
    if not prev:
        return update_close_gate(
            inst,
            recycle_usdc=recycle_usdc,
            premium_paid=premium_paid,
            premium_ccy=premium_ccy,
            index_px=index_px,
        )
    return update_close_gate(
        inst,
        recycle_usdc=recycle_usdc if recycle_usdc is not None else prev.get("recycle"),
        premium_paid=premium_paid if premium_paid is not None else prev.get("premium"),
        premium_ccy=premium_ccy if premium_ccy is not None else prev.get("premium_ccy"),
        index_px=index_px if index_px is not None else prev.get("index_px"),
    )
