"""OKX USDⓈ 期权:张数与权利金计算."""
from __future__ import annotations

import math
from typing import Any


def ct_mult_from_meta(meta: dict[str, Any] | None) -> float:
    if not meta:
        return 0.01
    try:
        return float(meta.get("ctMult") or 0.01)
    except (TypeError, ValueError):
        return 0.01


def min_sz_from_meta(meta: dict[str, Any] | None) -> int:
    if not meta:
        return 1
    try:
        return max(1, int(float(meta.get("minSz") or 1)))
    except (TypeError, ValueError):
        return 1


def premium_per_sheet(quote_per_unit: float, ct_mult: float = 0.01) -> float:
    """报价为每 1 ETH/BTC;每张权利金 = 报价 × ctMult."""
    return float(quote_per_unit) * float(ct_mult)


def format_quote_liquidity(px: float | None, sz: float | None, *, px_decimals: int = 4) -> str | None:
    """盘口展示:价格/张数,如 17.2/150."""
    if px is None:
        return None
    try:
        price = f"{float(px):.{px_decimals}f}".rstrip("0").rstrip(".")
    except (TypeError, ValueError):
        return None
    if sz is None:
        return price
    try:
        s = float(sz)
        size = str(int(s)) if abs(s - int(s)) < 1e-9 else str(s).rstrip("0").rstrip(".")
    except (TypeError, ValueError):
        return price
    return f"{price}/{size}"


def total_premium(quote_per_unit: float, eth_amount: float, ct_mult: float = 0.01) -> float:
    return float(quote_per_unit) * float(eth_amount)


# 买一相对标记价/内在价值低于该比例 → 视为残档,禁止按买盘自动/多档平仓
BID_CLOSE_MIN_RATIO = 0.3


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


def intrinsic_px_per_unit(opt_type: str | None, strike: float | None, index_px: float | None) -> float | None:
    o = (opt_type or "").strip().upper()
    if strike is None or index_px is None:
        return None
    try:
        k = float(strike)
        idx = float(index_px)
    except (TypeError, ValueError):
        return None
    if o == "C" and idx > k:
        return idx - k
    if o == "P" and idx < k:
        return k - idx
    return None


def is_stub_bid_px(
    bid_px: float | None,
    *,
    mark_px: float | None = None,
    intrinsic_px: float | None = None,
    min_ratio: float = BID_CLOSE_MIN_RATIO,
) -> tuple[bool, str]:
    """
    判断买一是否为无效残档(如标记 42、买一 0.2).
    返回 (is_stub, reason).
    """
    bid = _safe_px(bid_px)
    if bid is None:
        return True, "无买一"
    ref = _safe_px(mark_px)
    ref_name = "标记价"
    intrinsic = _safe_px(intrinsic_px)
    if intrinsic is not None and (ref is None or intrinsic > ref):
        ref = intrinsic
        ref_name = "内在价值"
    if ref is None:
        return False, ""
    ratio = float(min_ratio) if min_ratio and min_ratio > 0 else BID_CLOSE_MIN_RATIO
    if bid < ref * ratio:
        return True, f"买一{bid:g}远低于{ref_name}{ref:g},属无效残档,禁止按买盘自动平仓"
    return False, ""


def fetch_option_mark_px(ex: Any, inst_id: str) -> float | None:
    """优先 mark-price 接口,失败则 None."""
    inst_id = (inst_id or "").strip()
    if not inst_id or ex is None:
        return None
    try:
        rows = ex.public_get_public_mark_price({"instType": "OPTION", "instId": inst_id}).get("data") or []
        if rows:
            return _safe_px(rows[0].get("markPx"))
    except Exception:
        pass
    return None


def close_ref_prices(
    *,
    mark_px: float | None = None,
    opt_type: str | None = None,
    strike: float | None = None,
    index_px: float | None = None,
) -> tuple[float | None, float | None]:
    """返回 (mark_px, intrinsic_px) 供残档判断."""
    return _safe_px(mark_px), intrinsic_px_per_unit(opt_type, strike, index_px)


def filter_bids_for_close(
    bids: list[dict[str, Any]] | None,
    *,
    mark_px: float | None = None,
    intrinsic_px: float | None = None,
    min_ratio: float = BID_CLOSE_MIN_RATIO,
) -> tuple[list[dict[str, Any]], bool, str]:
    """过滤不可用于平仓的残档买盘.返回 (usable_bids, had_stub_only, reason)."""
    raw = list(bids or [])
    usable: list[dict[str, Any]] = []
    stub_reason = ""
    for level in raw:
        px = _safe_px(level.get("px") if isinstance(level, dict) else None)
        stub, reason = is_stub_bid_px(px, mark_px=mark_px, intrinsic_px=intrinsic_px, min_ratio=min_ratio)
        if stub:
            if not stub_reason:
                stub_reason = reason or "买一无效"
            continue
        usable.append(level)
    if raw and not usable:
        return [], True, stub_reason or "暂无有效买盘"
    return usable, False, ""


def estimate_close_by_bids(
    bids: list[dict[str, Any]] | None,
    sheets: int | float,
    *,
    ct_mult: float = 0.01,
    premium_paid: float | None = None,
    mark_px: float | None = None,
    intrinsic_px: float | None = None,
    min_bid_ratio: float = BID_CLOSE_MIN_RATIO,
    max_levels: int = 1,
) -> dict[str, Any]:
    """按买盘估算限价卖出可收回金额;默认只估算买一(与实盘平仓一致);残档不参与."""
    target = max(0, int(float(sheets or 0)))
    remaining = target
    total_received = 0.0
    levels: list[dict[str, Any]] = []
    max_lv = max(1, int(max_levels or 1))
    empty = {
        "levels": [],
        "covered_sheets": 0,
        "uncovered_sheets": target,
        "total_received": 0.0,
        "avg_px": None,
        "estimated_pnl": None,
        "estimated_pnl_ratio_pct": None,
        "bid_invalid": False,
        "bid_invalid_reason": None,
        "auto_close_blocked": False,
        "max_levels": max_lv,
    }
    if target <= 0 or ct_mult <= 0:
        return empty
    usable, stub_only, stub_reason = filter_bids_for_close(
        bids, mark_px=mark_px, intrinsic_px=intrinsic_px, min_ratio=min_bid_ratio
    )
    if stub_only:
        out = dict(empty)
        out["bid_invalid"] = True
        out["bid_invalid_reason"] = stub_reason
        out["auto_close_blocked"] = True
        out["raw_bid_px"] = _safe_px((bids or [{}])[0].get("px")) if bids else None
        return out
    for i, level in enumerate(usable[:max_lv], start=1):
        if remaining <= 0:
            break
        try:
            px = float(level.get("px"))
            sz = int(float(level.get("sz")))
        except (AttributeError, TypeError, ValueError):
            continue
        if px <= 0 or sz <= 0:
            continue
        take = min(remaining, sz)
        eth_amount = eth_amount_from_sheets(take, ct_mult)
        received = total_premium(px, eth_amount)
        levels.append(
            {
                "level": i,
                "px": px,
                "available_sheets": sz,
                "sheets": take,
                "eth_amount": eth_amount,
                "received": round(received, 4),
            }
        )
        total_received += received
        remaining -= take
    covered = target - remaining
    avg_px = (total_received / eth_amount_from_sheets(covered, ct_mult)) if covered > 0 else None
    # 净盈亏 = 本轮买盘可回收 − 全部权利金(买一不够时剩余张数计入 uncovered)
    estimated_pnl = None
    estimated_pnl_ratio_pct = None
    if premium_paid is not None and covered > 0:
        paid = float(premium_paid)
        estimated_pnl = round(total_received - paid, 4)
        if paid > 0:
            estimated_pnl_ratio_pct = round(estimated_pnl / paid * 100.0, 2)
    return {
        "levels": levels,
        "covered_sheets": covered,
        "uncovered_sheets": remaining,
        "total_received": round(total_received, 4),
        "avg_px": round(avg_px, 4) if avg_px is not None else None,
        "estimated_pnl": estimated_pnl,
        "estimated_pnl_ratio_pct": estimated_pnl_ratio_pct,
        "bid_invalid": False,
        "bid_invalid_reason": None,
        "auto_close_blocked": False,
        "max_levels": max_lv,
    }


def sheets_from_eth_amount(eth_amount: float, ct_mult: float = 0.01) -> int:
    if eth_amount <= 0 or ct_mult <= 0:
        return 0
    return int(math.floor(eth_amount / ct_mult + 1e-12))


def eth_amount_from_sheets(sheets: int, ct_mult: float = 0.01) -> float:
    return round(int(sheets) * float(ct_mult), 8)


def calc_order_size(
    *,
    quote_per_unit: float,
    ct_mult: float,
    min_sz: int,
    budget_usdc: float | None = None,
    budget_buffer: float = 0.95,
    eth_amount: float | None = None,
    sheets: int | None = None,
    budget_cap: float | None = None,
) -> dict[str, Any]:
    """
    返回 sheets, eth_amount, total_premium.
    mode: budget_full / eth_amount / sheets.
    """
    if quote_per_unit <= 0:
        return {"ok": False, "msg": "卖一价无效", "sheets": 0, "eth_amount": 0.0, "total_premium": 0.0}

    if sheets is not None and int(sheets) > 0:
        sheets = int(sheets)
    elif eth_amount is not None and eth_amount > 0:
        sheets = sheets_from_eth_amount(eth_amount, ct_mult)
    elif budget_usdc is not None and budget_usdc > 0:
        eff = float(budget_usdc) * float(budget_buffer)
        per_sheet = premium_per_sheet(quote_per_unit, ct_mult)
        if per_sheet <= 0:
            return {"ok": False, "msg": "无法计算单张权利金", "sheets": 0, "eth_amount": 0.0, "total_premium": 0.0}
        sheets = int(math.floor(eff / per_sheet))
    else:
        return {"ok": False, "msg": "请指定预算,币数量或张数", "sheets": 0, "eth_amount": 0.0, "total_premium": 0.0}

    if sheets < min_sz:
        per = premium_per_sheet(quote_per_unit, ct_mult)
        return {
            "ok": False,
            "msg": f"预算不足,无法买入 {min_sz} 张(单张约 {per:.4f} USDC)",
            "sheets": sheets,
            "eth_amount": eth_amount_from_sheets(sheets, ct_mult),
            "total_premium": total_premium(quote_per_unit, eth_amount_from_sheets(sheets, ct_mult)),
        }

    eth = eth_amount_from_sheets(sheets, ct_mult)
    prem = total_premium(quote_per_unit, eth)
    if budget_cap is not None and prem > float(budget_cap) + 1e-9:
        return {
            "ok": False,
            "msg": f"权利金 {prem:.4f} 超过单笔上限 {budget_cap} USDC",
            "sheets": sheets,
            "eth_amount": eth,
            "total_premium": prem,
        }
    return {"ok": True, "msg": "", "sheets": sheets, "eth_amount": eth, "total_premium": prem}


def is_shallow_itm(
    *,
    opt_type: str,
    strike: float,
    index_px: float,
    max_dist_usd: float,
) -> bool:
    o = (opt_type or "").upper()
    if o == "C":
        if strike >= index_px:
            return False
        return (index_px - strike) <= max_dist_usd
    if o == "P":
        if strike <= index_px:
            return False
        return (strike - index_px) <= max_dist_usd
    return False


def option_moneyness(*, opt_type: str, strike: float, index_px: float) -> str:
    """返回 itm / otm / atm."""
    o = (opt_type or "").upper()
    if strike is None or index_px is None or index_px <= 0:
        return "unknown"
    atm_band = max(index_px * 0.002, 2.0)
    if abs(strike - index_px) <= atm_band:
        return "atm"
    if o == "C":
        return "itm" if strike < index_px else "otm"
    if o == "P":
        return "itm" if strike > index_px else "otm"
    return "unknown"


def option_moneyness_label(moneyness: str) -> str:
    return {"itm": "实值", "otm": "虚值", "atm": "平值"}.get((moneyness or "").lower(), "")


def expiry_breakeven_from_ask(
    *,
    opt_type: str,
    strike: float | None,
    ask_px: float | None,
    mark_px: float | None = None,
) -> float | None:
    """买入前预估到期平衡:权利金按卖一;无卖一时回退标记价."""
    prem = ask_px if ask_px is not None and ask_px > 0 else mark_px
    return expiry_breakeven_px(opt_type=opt_type, strike=strike, avg_px=prem)


def expiry_breakeven_px(
    *,
    opt_type: str,
    strike: float | None,
    avg_px: float | None,
    be_px_api: float | None = None,
) -> float | None:
    """到期平衡点:持有至到期时标的指数盈亏为 0 的价格.优先 OKX bePx."""
    if be_px_api is not None and be_px_api > 0:
        return round(float(be_px_api), 2)
    if strike is None or avg_px is None:
        return None
    o = (opt_type or "").upper()
    if o == "C":
        return round(strike + avg_px, 2)
    if o == "P":
        return round(strike - avg_px, 2)
    return None


def close_breakeven_idx(
    *,
    opt_type: str,
    idx_px: float | None,
    mark_px: float | None,
    avg_px: float | None,
    delta_pa: float | None = None,
    pos: float = 0,
    ct_mult: float = 0.01,
) -> float | None:
    """
    平掉回本:标的指数达到该价位时,按标记价平仓近似盈亏为 0.
    优先用 deltaPA 线性外推,否则用时间价值近似(适合短期轻度实值).
    """
    if idx_px is None or mark_px is None or avg_px is None:
        return None
    eth_amt = abs(float(pos)) * float(ct_mult)
    if eth_amt > 1e-12 and delta_pa is not None and abs(float(delta_pa)) > 1e-12:
        slope = float(delta_pa) / eth_amt
        return round(float(idx_px) + (float(avg_px) - float(mark_px)) / slope, 2)
    o = (opt_type or "").upper()
    if o == "C":
        return round(float(idx_px) + float(avg_px) - float(mark_px), 2)
    if o == "P":
        return round(float(idx_px) + float(mark_px) - float(avg_px), 2)
    return None


def idx_distance_to_be(idx_px: float | None, be_px: float | None) -> float | None:
    """指数距平衡点(正=指数需上涨才到平衡点)."""
    if idx_px is None or be_px is None:
        return None
    return round(float(be_px) - float(idx_px), 2)


def format_options_breakeven_line(
    *,
    expiry_be_px: float | None,
    close_be_px: float | None,
    idx_px: float | None = None,
) -> str:
    """持仓摘要行:到期平衡 / 平掉回本."""
    parts: list[str] = []
    if expiry_be_px is not None:
        parts.append(f"到期平衡{expiry_be_px:.0f}")
    if close_be_px is not None:
        parts.append(f"平掉回本{close_be_px:.0f}")
    if idx_px is not None and parts:
        return " ".join(parts) + f"(指数{idx_px:.0f})"
    return " ".join(parts)


def estimate_expiry_value_at_index(
    *,
    opt_type: str,
    strike: float | None,
    target_idx: float | None,
    eth_amount: float | None,
) -> float | None:
    """到期测算:目标指数价下期权内在价值总额(不含已付权利金)."""
    if strike is None or target_idx is None or eth_amount is None:
        return None
    if eth_amount <= 0:
        return None
    o = (opt_type or "").upper()
    if o == "C":
        intrinsic = max(0.0, float(target_idx) - float(strike))
    elif o == "P":
        intrinsic = max(0.0, float(strike) - float(target_idx))
    else:
        return None
    return round(intrinsic * float(eth_amount), 2)


def estimate_expiry_profit_at_index(
    *,
    opt_type: str,
    strike: float | None,
    target_idx: float | None,
    entry_px: float | None,
    eth_amount: float | None,
    total_premium: float | None = None,
) -> float | None:
    """到期测算:目标指数价下净盈利 = 预计价值 − 权利金."""
    value = estimate_expiry_value_at_index(
        opt_type=opt_type,
        strike=strike,
        target_idx=target_idx,
        eth_amount=eth_amount,
    )
    if value is None:
        return None
    prem = total_premium
    if prem is None and entry_px is not None and eth_amount is not None:
        prem = float(entry_px) * float(eth_amount)
    if prem is None:
        return None
    return round(float(value) - float(prem), 2)


def equivalent_contract_leverage(
    *,
    index_px: float | None,
    eth_amount: float | None,
    total_premium: float | None,
) -> float | None:
    """名义价值 / 权利金,近似相当于永续合约杠杆倍数(测算用)."""
    if index_px is None or eth_amount is None or total_premium is None:
        return None
    if eth_amount <= 0 or total_premium <= 0:
        return None
    return round(float(index_px) * float(eth_amount) / float(total_premium), 1)


def straddle_ask_per_unit(
    call_ask: float | None,
    put_ask: float | None,
) -> float | None:
    """跨式双买:每 1 标的币的卖一报价之和."""
    if call_ask is None or put_ask is None:
        return None
    if float(call_ask) <= 0 or float(put_ask) <= 0:
        return None
    return round(float(call_ask) + float(put_ask), 4)


def straddle_premium_total(
    call_ask: float | None,
    put_ask: float | None,
    eth_amount: float | None,
) -> float | None:
    """跨式双买权利金总额(USDC)."""
    per = straddle_ask_per_unit(call_ask, put_ask)
    if per is None or eth_amount is None or float(eth_amount) <= 0:
        return None
    return round(per * float(eth_amount), 2)


def straddle_breakeven_band(
    strike: float | None,
    combined_ask_per_unit: float | None,
) -> tuple[float | None, float | None]:
    """跨式到期平衡带:下平衡 ~ 上平衡(按双卖一报价和)."""
    if strike is None or combined_ask_per_unit is None:
        return None, None
    k = float(strike)
    d = float(combined_ask_per_unit)
    return round(k - d, 2), round(k + d, 2)


def format_straddle_band(
    strike: float | None,
    combined_ask_per_unit: float | None,
) -> str:
    lo, hi = straddle_breakeven_band(strike, combined_ask_per_unit)
    if lo is None or hi is None:
        return ""
    return f"{lo:.0f} ~ {hi:.0f}"
