"""币本位单笔期权:买满 USDT→币 → 开满期权 → 平后卖回."""
from __future__ import annotations

import os
import time
from typing import Any

from lib.exchange.okx_options_lib import (
    cap_option_buy_sheets_to_ask_depth,
    option_buy_liquidity_ok,
    td_mode_for_option_buy,
    wait_option_order_full_fill,
)
from lib.options.options_margin_mode_lib import (
    calc_sheets_from_coin_balance,
    compute_coin_budget_usdt,
    is_coin_margin_mode,
    margin_mode_from_inst_id,
    normalize_options_margin_mode,
    plan_coin_open_by_budget,
    premium_ccy_for_mode,
)
from lib.options.options_spot_bridge_lib import (
    BRIDGE_BOUGHT,
    BRIDGE_HOLDING,
    bridge_blocks_new_open_msg,
    fetch_trading_coin_available,
    insert_bridge,
    rollback_bought_coin_to_usdt,
    sell_residual_after_option_flat,
    spot_market_buy_coin_with_usdt,
    update_bridge,
)


def coin_budget_preview(cfg: dict[str, Any], ex: Any) -> dict[str, Any]:
    from lib.exchange.okx_options_lib import fetch_options_balances

    bal = cfg.get("fetch_options_balances")(ex, force=True) if callable(cfg.get("fetch_options_balances")) else fetch_options_balances(ex, force=True)
    trading = bal.get("trading_usdt_avail")
    if trading is None:
        trading = bal.get("trading_usdt")
    try:
        trading_f = float(trading or 0)
    except (TypeError, ValueError):
        trading_f = 0.0
    buf = float(cfg.get("budget_buffer") or 0.95)
    return compute_coin_budget_usdt(trading_f, buffer=buf)


def open_coin_option_buy_full(
    cfg: dict[str, Any],
    ex: Any,
    *,
    inst_id: str,
    signal_note: str = "",
    target_index: float | None = None,
    profit_exit_enabled: bool = False,
    profit_exit_mult: float = 1.0,
    target_sheets: int | None = None,
) -> dict[str, Any]:
    """先按最大可开张数估权利金×现货缓冲买币,再开对应张数(不全额兑换预算)."""
    from lib.options.options_db import init_options_tables
    from lib.options.options_position_limit_lib import (
        compound_full_single_position_block_msg,
        option_position_limit_block_msg,
    )

    if not is_coin_margin_mode():
        return {"ok": False, "msg": "当前非币本位模式"}
    if margin_mode_from_inst_id(inst_id) != "coin":
        return {"ok": False, "msg": "合约不是币本位期权(请确认未选中 USD_UM 合约)"}

    # 解析标的
    parts = inst_id.split("-")
    underlying = (parts[0] if parts else "ETH").upper()

    conn = cfg["get_db"]()
    try:
        init_options_tables(conn)
        block = bridge_blocks_new_open_msg(conn)
        if block:
            return {"ok": False, "msg": block, "can_open": False}

        compound_block = compound_full_single_position_block_msg(
            ex, fetch_positions=cfg.get("fetch_option_positions")
        )
        if compound_block:
            return {"ok": False, "msg": compound_block, "can_open": False}
        pos_limit_msg = option_position_limit_block_msg(
            ex,
            opening_inst_id=inst_id,
            fetch_positions=cfg.get("fetch_option_positions"),
        )
        if pos_limit_msg:
            return {"ok": False, "msg": pos_limit_msg, "can_open": False}

        budget_info = coin_budget_preview(cfg, ex)
        if not budget_info.get("ok"):
            return {"ok": False, "msg": budget_info.get("msg") or "USDT 预算无效", "budget": budget_info}
        budget_usdt = float(budget_info["budget_usdt"])

        q = cfg["quote_option_contract"](ex, inst_id)
        if not q.get("ok"):
            return q
        ask = q.get("ask")
        ask_sz = q.get("ask_sz")
        can_open, block_msg = option_buy_liquidity_ok(ask, ask_sz)
        if not can_open:
            return {
                "ok": False,
                "msg": block_msg or "暂无卖一深度,无法买入",
                "can_open": False,
            }

        ct_mult = float(q.get("ct_mult") or 0.01)
        min_sz = int(q.get("min_sz") or 1)
        idx = None
        try:
            idx = float(q.get("index_px") or q.get("idxPx") or 0)
        except (TypeError, ValueError):
            idx = 0.0
        if idx <= 0:
            try:
                from lib.exchange.okx_options_lib import fetch_index_price

                idx = float(fetch_index_price(ex, f"{underlying}-USD") or 0)
            except Exception:
                idx = 0.0

        plan = plan_coin_open_by_budget(
            quote_per_unit=float(ask),
            ct_mult=ct_mult,
            min_sz=min_sz,
            budget_usdt=budget_usdt,
            index_px=float(idx),
            ask_sz=ask_sz,
            target_sheets=target_sheets,
        )
        if not plan.get("ok"):
            return {
                "ok": False,
                "msg": plan.get("msg") or "无法规划买币张数",
                "plan": plan,
                "budget": budget_info,
                "can_open": False,
            }
        buy_usdt = float(plan["buy_usdt"])
        sheets = int(plan["sheets"])

        # 1) 仅买「权利金×现货缓冲」所需 USDT,不全额兑换预算
        coin_before = fetch_trading_coin_available(ex, underlying) or 0.0
        buy = spot_market_buy_coin_with_usdt(ex, underlying=underlying, usdt_amount=buy_usdt)
        if not buy.get("ok"):
            return {
                "ok": False,
                "msg": f"现货买入 {underlying} 失败: {buy.get('msg')}",
                "budget": budget_info,
                "plan": plan,
            }
        bridge_id = insert_bridge(
            conn,
            underlying=underlying,
            status=BRIDGE_BOUGHT,
            budget_usdt=buy_usdt,
            buy_ord_id=str(buy.get("ord_id") or ""),
            inst_id=inst_id,
            message="已买币,待开期权",
        )
        # 等余额落账
        time.sleep(1.5)
        try:
            from lib.exchange.okx_options_lib import invalidate_options_balance_cache

            invalidate_options_balance_cache()
        except Exception:
            pass
        coin_after = fetch_trading_coin_available(ex, underlying)
        if coin_after is None:
            rb = rollback_bought_coin_to_usdt(
                conn, ex, bridge_id=bridge_id, underlying=underlying, reason="买币后读不到可用余额"
            )
            return {
                "ok": False,
                "msg": "买币后读不到可用余额,已尝试卖回 USDT",
                "rollback": rb,
                "budget": budget_info,
                "plan": plan,
            }
        coin_bought = max(0.0, float(coin_after) - float(coin_before or 0))
        if coin_bought <= 0:
            # 落账延迟时退化为用当前可用,但仍写入上限提示
            coin_bought = float(coin_after)
        if coin_bought <= 0:
            rb = rollback_bought_coin_to_usdt(
                conn, ex, bridge_id=bridge_id, underlying=underlying, reason="买入量无效"
            )
            return {"ok": False, "msg": "买币后可用增量无效", "rollback": rb, "budget": budget_info}
        update_bridge(conn, bridge_id, coin_bought=float(coin_bought))

        sizing = calc_sheets_from_coin_balance(
            quote_per_unit=float(ask),
            ct_mult=ct_mult,
            min_sz=min_sz,
            coin_available=float(coin_bought),
        )
        if not sizing.get("ok"):
            rb = rollback_bought_coin_to_usdt(
                conn,
                ex,
                bridge_id=bridge_id,
                underlying=underlying,
                reason=sizing.get("msg") or "张数不足",
                coin_amount=float(coin_bought),
            )
            return {"ok": False, "msg": sizing.get("msg"), "sizing": sizing, "rollback": rb, "budget": budget_info, "plan": plan}

        # 实盘以买到的币为准,但不超过规划张数
        sheets = min(int(sizing["sheets"]), int(plan["sheets"]))
        capped, cap_msg = cap_option_buy_sheets_to_ask_depth(sheets, ask_sz, min_sz=min_sz)
        if capped is None:
            rb = rollback_bought_coin_to_usdt(
                conn, ex, bridge_id=bridge_id, underlying=underlying, reason=cap_msg or "深度不足"
            )
            return {"ok": False, "msg": cap_msg or "卖一深度不足", "rollback": rb}
        if capped < sheets:
            sheets = int(capped)
            sizing = {
                "ok": True,
                "sheets": sheets,
                "eth_amount": round(sheets * ct_mult, 8),
                "coin_premium": round(sheets * float(ask) * ct_mult, 8),
                "ask_depth_capped": True,
            }
        else:
            sizing = {
                "ok": True,
                "sheets": sheets,
                "eth_amount": round(sheets * ct_mult, 8),
                "coin_premium": round(sheets * float(ask) * ct_mult, 8),
            }

        tick_sz = q.get("tick_sz")
        order = cfg["place_option_limit_order"](
            ex,
            inst_id=inst_id,
            side="buy",
            sheets=sheets,
            price=float(ask),
            td_mode=td_mode_for_option_buy(cfg.get("td_mode")),
            tick_sz=tick_sz,
            ord_type="ioc",
        )
        # 51008 时自动减半张数再试一次(买币已到位,避免整笔回滚)
        if (not order.get("ok")) and sheets > 1:
            msg_l = str(order.get("msg") or "").lower()
            if "51008" in str(order.get("raw") or "").lower() or "不足" in str(order.get("msg") or ""):
                sheets2 = max(1, sheets // 2)
                if sheets2 < sheets:
                    order2 = cfg["place_option_limit_order"](
                        ex,
                        inst_id=inst_id,
                        side="buy",
                        sheets=sheets2,
                        price=float(ask),
                        td_mode=td_mode_for_option_buy(cfg.get("td_mode")),
                        tick_sz=tick_sz,
                        ord_type="ioc",
                    )
                    if order2.get("ok"):
                        order = order2
                        sheets = sheets2
                        sizing = {
                            "ok": True,
                            "sheets": sheets,
                            "eth_amount": round(sheets * ct_mult, 8),
                            "coin_premium": round(sheets * float(ask) * ct_mult, 8),
                            "retried_half": True,
                        }
        if not order.get("ok"):
            rb = rollback_bought_coin_to_usdt(
                conn, ex, bridge_id=bridge_id, underlying=underlying, reason=order.get("msg") or "下单失败"
            )
            return {"ok": False, "msg": order.get("msg") or "期权下单失败", "order": order, "rollback": rb}

        ord_id = str((order.get("data") or {}).get("ordId") or "").strip()
        if not ord_id:
            rb = rollback_bought_coin_to_usdt(
                conn, ex, bridge_id=bridge_id, underlying=underlying, reason="无订单号"
            )
            return {"ok": False, "msg": "下单成功但未返回订单号", "rollback": rb}

        try:
            fill_timeout = max(2.0, float(os.getenv("OKX_OPTIONS_OPEN_FILL_TIMEOUT_SEC") or "12"))
        except (TypeError, ValueError):
            fill_timeout = 12.0
        fill = wait_option_order_full_fill(
            ex,
            inst_id=inst_id,
            ord_id=ord_id,
            need_sheets=int(sheets),
            timeout_sec=fill_timeout,
            cancel_on_timeout=True,
        )
        if not fill.get("ok"):
            filled_n = int(fill.get("filled_sheets") or 0)
            if filled_n <= 0:
                rb = rollback_bought_coin_to_usdt(
                    conn,
                    ex,
                    bridge_id=bridge_id,
                    underlying=underlying,
                    reason=fill.get("msg") or "未成交",
                )
                return {"ok": False, "msg": fill.get("msg") or "未完全成交", "fill": fill, "rollback": rb}
            sheets = filled_n

        eth_amount = round(float(sheets) * ct_mult, 8)
        premium_paid = round(float(ask) * eth_amount, 8)
        premium_ccy = premium_ccy_for_mode("coin", underlying)

        update_bridge(
            conn,
            bridge_id,
            status=BRIDGE_HOLDING,
            inst_id=inst_id,
            message="期权持仓中",
        )

        trade_id = _insert_coin_trade(
            conn,
            inst_id=inst_id,
            underlying=underlying,
            opt_type=str(q.get("opt_type") or ""),
            strike=q.get("strike"),
            exp_time=q.get("exp_time"),
            sheets=int(sheets),
            eth_amount=eth_amount,
            open_quote=float(ask),
            premium_paid=premium_paid,
            signal_note=signal_note,
            exchange_ord_id=ord_id,
            bridge_id=bridge_id,
            budget_usdt=buy_usdt,
            premium_ccy=premium_ccy,
            profit_exit_enabled=profit_exit_enabled,
            profit_exit_mult=profit_exit_mult,
        )

        # 目标位 / 翻倍离场 — 复用现有逻辑若存在
        try:
            if target_index is not None:
                from lib.options.options_target_lib import upsert_target_monitor

                upsert_target_monitor(
                    conn,
                    inst_id=inst_id,
                    underlying=underlying,
                    opt_type=str(q.get("opt_type") or ""),
                    target_index=float(target_index),
                    trade_id=trade_id,
                    sheets=int(sheets),
                )
        except Exception:
            pass
        try:
            from lib.options.options_notify_lib import notify_options_open

            notify_options_open(
                cfg,
                conn,
                trade_id=trade_id,
                inst_id=inst_id,
                underlying=underlying,
                opt_type=str(q.get("opt_type") or ""),
                sheets=int(sheets),
                premium_paid=premium_paid,
                open_quote=float(ask),
                target_index=target_index,
                signal_note=signal_note,
                premium_ccy=premium_ccy,
                margin_mode="coin",
            )
        except Exception:
            pass

        return {
            "ok": True,
            "msg": f"币本位开仓成功 {sheets} 张",
            "margin_mode": "coin",
            "budget": budget_info,
            "sizing": sizing,
            "sheets": sheets,
            "eth_amount": eth_amount,
            "premium_paid": premium_paid,
            "premium_ccy": premium_ccy,
            "bridge_id": bridge_id,
            "trade_id": trade_id,
            "order": order,
            "fill": fill,
        }
    finally:
        try:
            conn.close()
        except Exception:
            pass


def _insert_coin_trade(conn: Any, **kwargs: Any) -> int:
    pe = 1 if kwargs.get("profit_exit_enabled") else 0
    pe_mult = float(kwargs.get("profit_exit_mult") or 1.0)
    pe_state = "active" if pe else "idle"
    cur = conn.execute(
        """
        INSERT INTO options_trades(
            inst_id, underlying, opt_type, strike, exp_time, sheets, eth_amount,
            open_quote, premium_paid, status, signal_note, exchange_ord_id,
            margin_mode, premium_ccy, bridge_id, budget_usdt,
            profit_exit_enabled, profit_exit_mult, profit_exit_state
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', ?, ?, 'coin', ?, ?, ?, ?, ?, ?)
        """,
        (
            kwargs["inst_id"],
            kwargs["underlying"],
            kwargs["opt_type"],
            kwargs.get("strike"),
            str(kwargs.get("exp_time") or ""),
            kwargs["sheets"],
            kwargs["eth_amount"],
            kwargs.get("open_quote"),
            kwargs.get("premium_paid"),
            kwargs.get("signal_note") or "",
            kwargs.get("exchange_ord_id"),
            kwargs.get("premium_ccy") or "ETH",
            kwargs.get("bridge_id"),
            kwargs.get("budget_usdt"),
            pe,
            pe_mult,
            pe_state,
        ),
    )
    conn.commit()
    return int(cur.lastrowid)


def maybe_sell_spot_after_close(
    cfg: dict[str, Any],
    ex: Any,
    *,
    inst_id: str,
    close_result: dict[str, Any] | None = None,
) -> dict[str, Any] | None:
    """期权平仓后若该合约为币本位且已空仓,卖回本桥残留币."""
    if margin_mode_from_inst_id(inst_id) != "coin":
        return None
    # 仍有仓则不卖
    try:
        rows = cfg["fetch_option_positions"](ex) or []
        for p in rows:
            if str(p.get("instId") or p.get("inst_id") or "") != inst_id:
                continue
            try:
                if abs(float(p.get("pos") or 0)) > 1e-12:
                    return {"ok": True, "skipped": True, "msg": "仍有持仓,暂不卖币"}
            except (TypeError, ValueError):
                pass
    except Exception:
        pass
    parts = inst_id.split("-")
    underlying = (parts[0] if parts else "ETH").upper()
    conn = cfg["get_db"]()
    try:
        from lib.options.options_db import init_options_tables

        init_options_tables(conn)
        return sell_residual_after_option_flat(conn, ex, underlying=underlying, inst_id=inst_id)
    finally:
        try:
            conn.close()
        except Exception:
            pass
