"""开仓资金可开判定：只看交易账户（永续 USDT / 期权 USDC）。"""

from __future__ import annotations

import logging
import time
from typing import Any

from ..config import get_settings
from ..models.db import Database, get_db
from ..sim.funds_wallets import SimFundsWallets
from ..sim.ledger import Ledger

logger = logging.getLogger(__name__)

_live_bal_cache: dict[str, Any] = {"ts": 0.0, "data": None}
_LIVE_BAL_TTL_SEC = 8.0

# 不足期间只推一次；资金恢复后清零，下次再不足可再推一次
_notified_while_short: bool = False


def invalidate_live_balance_cache() -> None:
    """兑换/划转后强制下次重拉交易账户余额。"""
    _live_bal_cache["ts"] = 0.0
    _live_bal_cache["data"] = None


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


def _index_and_option_ask() -> tuple[float | None, float | None]:
    """指数价 + 期权卖一粗估（取 Call/Put 卖一较大者，偏保守）。"""
    try:
        from .session import get_session

        snap = get_session().snapshot()
    except Exception:
        return None, None
    idx = _f(getattr(snap, "index_px", None))
    if idx is None and snap.perp:
        idx = _f(snap.perp.mark_px) or _f(snap.perp.ask) or _f(snap.perp.bid)
    asks: list[float] = []
    for leg in (snap.call, snap.put):
        if leg is None:
            continue
        a = _f(leg.ask)
        if a is not None and a > 0:
            asks.append(a)
    ask = max(asks) if asks else None
    return idx, ask


def _live_balances() -> dict[str, float | None]:
    now = time.time()
    if _live_bal_cache["data"] is not None and now - float(_live_bal_cache["ts"]) < _LIVE_BAL_TTL_SEC:
        return dict(_live_bal_cache["data"])
    out: dict[str, float | None] = {
        "trading_usdt": None,
        "trading_usdc": None,
    }
    try:
        from ..exchange.runtime import load_runtime_settings

        ex = str(load_runtime_settings().exchange or "").strip().lower()
        if ex in ("binance", "bn"):
            # 币安资金接口尚未接入；返回 None 使可开判定为「未知」而非误用 OKX
            logger.debug("open_capacity: binance live balance not wired; treating as unknown")
        else:
            from ..live.okx_funds import OkxFundsClient

            client = OkxFundsClient()
            try:
                bal = client.fetch_balances()
                out["trading_usdt"] = _f(bal.get("trading_usdt"))
                out["trading_usdc"] = _f(bal.get("trading_usdc"))
            finally:
                client.close()
    except Exception as e:
        logger.warning("open_capacity live balance failed: %s", e)
    _live_bal_cache["ts"] = now
    _live_bal_cache["data"] = dict(out)
    return out


def _sim_balances(db: Database) -> dict[str, float]:
    """只看交易账户，不看资金账户。"""
    w = SimFundsWallets(db).view()
    return {
        "perp_usdt": float(w.get("trading_usdt") or 0),
        "option_usdc": float(w.get("trading_usdc") or 0),
    }


def assess_open_capacity(
    db: Database | None = None,
    *,
    option_ask: float | None = None,
    option_qty_eth: float | None = None,
    perp_qty_eth: float | None = None,
) -> dict[str, Any]:
    """
    返回永续/期权是否有足够交易账户资金开新仓。
    - 永续：交易账户 USDT >= 名义/杠杆
    - 期权：交易账户 USDC >= 卖一×名义×(1+费率)
    可选覆盖 ask/名义（选约后应用选中腿卖一，避免与 max(call,put) 打架）。
    """
    global _notified_while_short
    db = db or get_db()
    s = get_settings()
    ledger = Ledger(db)
    lev = float(ledger.get_setting_float("leverage", s.leverage) or 3)
    if lev <= 0:
        lev = 3.0
    perp_qty = float(
        perp_qty_eth
        if perp_qty_eth is not None
        else (ledger.get_setting_float("perp_qty_eth", s.perp_qty_eth) or 1)
    )
    opt_qty = float(
        option_qty_eth
        if option_qty_eth is not None
        else (ledger.get_setting_float("option_qty_eth", s.option_qty_eth) or 2)
    )
    fee_rate = float(ledger.get_setting_float("fee_rate", s.fee_rate) or 0.0005)

    idx, ask_book = _index_and_option_ask()
    ask = float(option_ask) if option_ask is not None and float(option_ask) > 0 else ask_book
    margin_need = (float(idx) * perp_qty / lev) if idx and idx > 0 else None
    premium_need = (
        float(ask) * opt_qty * (1.0 + fee_rate) if ask is not None and ask > 0 else None
    )

    if s.is_sim:
        bal = _sim_balances(db)
        have_perp = float(bal["perp_usdt"])
        have_opt = float(bal["option_usdc"])
    else:
        live = _live_balances()
        t_usdt = live.get("trading_usdt")
        t_usdc = live.get("trading_usdc")
        have_perp = float(t_usdt) if t_usdt is not None else None
        have_opt = float(t_usdc) if t_usdc is not None else None

    perp_ok: bool | None
    if margin_need is None or have_perp is None:
        perp_ok = None
    else:
        perp_ok = have_perp + 1e-9 >= margin_need

    opt_ok: bool | None
    if premium_need is None or have_opt is None:
        opt_ok = None
    else:
        opt_ok = have_opt + 1e-9 >= premium_need

    funds_ok = perp_ok is True and opt_ok is True
    # 资金恢复后允许下次不足再通知一次
    if funds_ok:
        _notified_while_short = False

    lev_i = int(round(lev)) if abs(lev - round(lev)) < 1e-9 else lev
    if perp_ok is True:
        perp_label = f"永续{lev_i}x 可开"
    elif perp_ok is False:
        perp_label = f"永续{lev_i}x 不可开"
    else:
        perp_label = f"永续{lev_i}x —"

    if opt_ok is True:
        opt_label = "期权可开"
    elif opt_ok is False:
        opt_label = "期权不可开"
    else:
        opt_label = "期权 —"

    return {
        "leverage": lev,
        "perp_qty_eth": perp_qty,
        "option_qty_eth": opt_qty,
        "index_px": idx,
        "option_ask": ask,
        "perp_need_usdt": round(margin_need, 2) if margin_need is not None else None,
        "option_need_usdc": round(premium_need, 2) if premium_need is not None else None,
        "perp_have_usdt": round(have_perp, 2) if have_perp is not None else None,
        "option_have_usdc": round(have_opt, 2) if have_opt is not None else None,
        "perp_can_open": perp_ok,
        "option_can_open": opt_ok,
        "perp_label": perp_label,
        "option_label": opt_label,
        "funds_ok": funds_ok,
        "source": "trading",
    }


def funds_gate_blocks(cap: dict[str, Any] | None) -> tuple[bool, str]:
    """
    Fail-closed：仅当永续与期权均为 True 才放行。
    None（未知，如币安未接余额）或 False → 拦截。
    """
    if not cap:
        return True, "资金可开判定结果为空，拒绝开仓"
    if cap.get("perp_can_open") is not True or cap.get("option_can_open") is not True:
        detail = (
            f"{cap.get('perp_label')} · {cap.get('option_label')}；"
            f"永续需≈{cap.get('perp_need_usdt')}U/有{cap.get('perp_have_usdt')}U，"
            f"期权需≈{cap.get('option_need_usdc')}U/有{cap.get('option_have_usdc')}U"
        )
        if cap.get("perp_can_open") is None or cap.get("option_can_open") is None:
            detail += "（余额/盘口未知，fail-closed 拒绝开仓）"
        return True, f"资金不足或状态未知，暂不可开新仓：{detail}"
    return False, ""


def maybe_notify_funds_short(cap: dict[str, Any] | None = None) -> None:
    """仅在「不能开」时推送一次；能开绝不通知。"""
    global _notified_while_short
    cap = cap or assess_open_capacity()
    # 能开 / 未知：不通知；资金恢复则重置，便于下次不足再提醒一次
    if cap.get("funds_ok") is True:
        _notified_while_short = False
        return
    cannot = cap.get("perp_can_open") is False or cap.get("option_can_open") is False
    if not cannot:
        return
    if _notified_while_short:
        return
    _notified_while_short = True
    parts: list[str] = []
    if cap.get("perp_can_open") is False:
        parts.append(
            f"交易账户 USDT 不足：需约 {cap.get('perp_need_usdt')}U，现有 {cap.get('perp_have_usdt')}U"
        )
    if cap.get("option_can_open") is False:
        parts.append(
            f"交易账户 USDC 不足：需约 {cap.get('option_need_usdc')}U，现有 {cap.get('option_have_usdc')}U"
        )
    try:
        from ..notify import wecom

        wecom.notify_async(
            wecom.build_markdown(
                tag=wecom.TAG_FAULT,
                title="交易账户资金不足 · 无法开新仓",
                lines=[
                    f"**永续**: {cap.get('perp_label')}",
                    f"**期权**: {cap.get('option_label')}",
                    *[f"**详情**: {p}" for p in parts],
                    "OKX 开仓前会尝试交易账户市价兑 USDC；仍不足请检查交易账户余额。",
                ],
            )
        )
    except Exception:
        logger.exception("wecom funds notify failed")
