"""期权平仓执行:只锁买一限价卖出;永不市价."""
from __future__ import annotations

import time
from typing import Any

from lib.options.options_close_gate_lib import (
    clear_close_gate,
    is_close_gate_passed,
    mark_close_gate_passed,
    update_close_gate,
)
from lib.options.options_pricing_lib import (
    estimate_close_by_bids,
    fetch_option_mark_px,
    is_stub_bid_px,
    total_premium,
)


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 _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
    try:
        conn = cfg["get_db"]()
        try:
            from lib.options.options_db import init_options_tables, sum_open_premium_paid

            init_options_tables(conn)
            return sum_open_premium_paid(conn, inst_id)
        finally:
            conn.close()
    except Exception:
        pass
    return None


def _pos_close_refs(ex: Any, pos: dict[str, Any], quote: dict[str, Any] | None = None) -> tuple[float | None, float | None]:
    from lib.exchange.okx_options_lib import option_fields_from_inst_id
    from lib.options.options_pricing_lib import close_ref_prices

    inst_id = str(pos.get("instId") or pos.get("inst_id") or "")
    mark = _safe_float(pos.get("markPx")) or _safe_float((quote or {}).get("mark_px") or (quote or {}).get("mark"))
    if mark is None:
        mark = fetch_option_mark_px(ex, inst_id)
    opt_type = pos.get("optType") or (quote or {}).get("opt_type")
    strike = _safe_float(pos.get("stk")) or _safe_float((quote or {}).get("strike"))
    if not opt_type or strike is None:
        pt, ps = option_fields_from_inst_id(inst_id)
        opt_type = opt_type or pt
        if strike is None:
            strike = ps
    idx = _safe_float(pos.get("idxPx")) or _safe_float((quote or {}).get("index_px"))
    return close_ref_prices(mark_px=mark, opt_type=str(opt_type or ""), strike=strike, index_px=idx)


def _avail_sheets(pos: dict[str, Any]) -> int:
    avail = _safe_float(pos.get("availPos"))
    if avail is None or avail <= 0:
        avail = abs(_safe_float(pos.get("pos")) or 0)
    return max(0, int(avail or 0))


def _cancel_sell_pending(ex: Any, inst_id: str) -> None:
    try:
        pending = ex.private_get_trade_orders_pending({"instType": "OPTION", "instId": inst_id}) or {}
        for o in pending.get("data") or []:
            if str(o.get("side") or "").lower() != "sell":
                continue
            oid = o.get("ordId")
            if not oid:
                continue
            try:
                ex.private_post_trade_cancel_order({"instId": inst_id, "ordId": oid})
            except Exception:
                pass
    except Exception:
        pass


def close_option_by_bid1(
    cfg: dict[str, Any],
    ex: Any,
    inst_id: str,
    *,
    sheets: int | None = None,
    require_recycle_gate: bool = False,
    signal_note: str | None = None,
) -> dict[str, Any]:
    """
    本轮只吃买一深度:
    - 本批张数 = min(请求张数, 持仓, 买一深度)
    - 限价 = 校验通过时锁定的买一价
    - 永不市价
    - 始终校验有效流动性(残档买一禁止)
    - require_recycle_gate=True 时:首次还需可回收≥2×权利金并持续 hold 秒;
      一旦通过后对同仓续批只验流动性
    """
    from lib.exchange.okx_options_lib import (
        _pos_side_from_position,
        invalidate_option_positions_cache,
    )

    inst_id = (inst_id or "").strip()
    if not inst_id:
        return {"ok": False, "msg": "缺少 inst_id"}

    q = cfg["quote_option_contract"](ex, inst_id)
    if not q.get("ok"):
        return {"ok": False, "msg": q.get("msg") or "报价失败"}
    tick_sz = q.get("tick_sz")
    ct_mult = float(q.get("ct_mult") or 0.01)

    raw_positions = cfg["fetch_option_positions"](ex)
    if raw_positions is None:
        return {"ok": False, "msg": "获取期权持仓失败"}
    pos = next((p for p in raw_positions if str(p.get("instId")) == inst_id), None)
    if not pos:
        clear_close_gate(inst_id)
        return {"ok": False, "msg": "未找到持仓", "already_flat": True}

    avail = _avail_sheets(pos)
    want = int(sheets) if sheets else avail
    want = min(want, avail)
    if want < 1:
        clear_close_gate(inst_id)
        return {"ok": False, "msg": "可平张数不足", "already_flat": True}

    td_mode = str(pos.get("mgnMode") or cfg.get("td_mode") or "isolated")
    pos_side = _pos_side_from_position(pos) or "net"
    mark_px, intrinsic_px = _pos_close_refs(ex, pos, q)
    premium_paid = _open_premium_paid(cfg, inst_id)
    if premium_paid is None:
        premium_paid = _safe_float(pos.get("premium_paid"))

    # 已有未成交卖平单:等成交,不撤不重挂
    try:
        pending = ex.private_get_trade_orders_pending({"instType": "OPTION", "instId": inst_id}) or {}
        sell_pending = [
            o
            for o in (pending.get("data") or [])
            if str(o.get("side") or "").lower() == "sell" and o.get("ordId")
        ]
        if sell_pending:
            time.sleep(0.5)
            invalidate_option_positions_cache()
            raw_positions = cfg["fetch_option_positions"](ex)
            if raw_positions is None:
                return {"ok": False, "msg": "获取期权持仓失败"}
            pos = next((p for p in raw_positions if str(p.get("instId")) == inst_id), None)
            if not pos or _avail_sheets(pos) < 1:
                clear_close_gate(inst_id)
                return {
                    "ok": True,
                    "already_flat": True,
                    "msg": "已有限价卖单成交",
                    "close_ord_id": ",".join(str(o.get("ordId")) for o in sell_pending),
                    "fully_closed": True,
                    "submitted_sheets": want,
                    "remaining_sheets": 0,
                    "mode": "bid1",
                }
            return {
                "ok": False,
                "msg": "等待已有买一限价卖单成交",
                "stopped_reason": "pending_close_order",
                "close_ord_id": ",".join(str(o.get("ordId")) for o in sell_pending),
            }
    except Exception:
        pass

    book = cfg["fetch_option_book_depth"](ex, inst_id, 1)
    preview = estimate_close_by_bids(
        book.get("bids") or [],
        want,
        ct_mult=ct_mult,
        premium_paid=premium_paid,
        mark_px=mark_px,
        intrinsic_px=intrinsic_px,
        max_levels=1,
    )
    if preview.get("bid_invalid") or preview.get("auto_close_blocked"):
        _cancel_sell_pending(ex, inst_id)
        update_close_gate(inst_id, recycle_usdc=None, premium_paid=premium_paid)
        return {
            "ok": False,
            "msg": preview.get("bid_invalid_reason") or "暂无有效买盘,禁止平仓",
            "stopped_reason": "stub_bid",
            "auto_close_blocked": True,
            "liquidity_blocked": True,
        }

    levels = preview.get("levels") or []
    if not levels:
        bid_px = _safe_float(q.get("bid"))
        stub, stub_reason = is_stub_bid_px(bid_px, mark_px=mark_px, intrinsic_px=intrinsic_px)
        if stub or bid_px is None or bid_px <= 0:
            update_close_gate(inst_id, recycle_usdc=None, premium_paid=premium_paid)
            return {
                "ok": False,
                "msg": stub_reason or "暂无买一,无法限价平仓",
                "stopped_reason": "stub_bid" if stub else "no_bid",
                "auto_close_blocked": True,
                "liquidity_blocked": True,
            }
        return {
            "ok": False,
            "msg": "暂无买一深度,无法平仓",
            "stopped_reason": "no_bid_depth",
            "liquidity_blocked": True,
        }

    level = levels[0]
    level_sheets = int(level.get("sheets") or 0)
    level_px = float(level.get("px") or 0)
    if level_sheets <= 0 or level_px <= 0:
        return {"ok": False, "msg": "买一深度无效", "stopped_reason": "invalid_bid_depth"}

    stub_lv, stub_lv_reason = is_stub_bid_px(level_px, mark_px=mark_px, intrinsic_px=intrinsic_px)
    if stub_lv:
        update_close_gate(inst_id, recycle_usdc=None, premium_paid=premium_paid)
        return {
            "ok": False,
            "msg": stub_lv_reason or "暂无有效买盘,禁止平仓",
            "stopped_reason": "stub_bid",
            "auto_close_blocked": True,
            "liquidity_blocked": True,
        }

    # 自动平仓:2×权利金门控(首次);通过后同仓续批只验流动性
    gate = update_close_gate(
        inst_id,
        recycle_usdc=_safe_float(preview.get("total_received")),
        premium_paid=premium_paid,
    )
    if require_recycle_gate and not is_close_gate_passed(inst_id) and not gate.get("ready"):
        return {
            "ok": False,
            "msg": gate.get("msg") or "平仓门控未就绪(需可回收≥2×权利金并持续一段时间)",
            "stopped_reason": "close_gate",
            "auto_close_blocked": True,
            "close_gate": gate,
        }
    if gate.get("ready"):
        mark_close_gate_passed(inst_id)

    locked_bid_px = level_px
    before_avail = avail
    order = cfg["place_option_limit_order"](
        ex,
        inst_id=inst_id,
        side="sell",
        sheets=level_sheets,
        price=locked_bid_px,
        td_mode=td_mode,
        tick_sz=tick_sz,
        reduce_only=True,
        pos_side=pos_side,
    )
    if not order.get("ok"):
        return {
            "ok": False,
            "msg": order.get("msg") or "买一限价平仓失败",
            "stopped_reason": "order_failed",
            "locked_bid_px": locked_bid_px,
            "batch_sheets": level_sheets,
        }

    px = float(order.get("px", locked_bid_px))
    oid = str((order.get("data") or {}).get("ordId") or "")
    prem_recv = round(total_premium(px, level_sheets * ct_mult), 4)
    time.sleep(0.6)
    invalidate_option_positions_cache()
    raw2 = cfg["fetch_option_positions"](ex)
    after_avail = 0
    if raw2 is not None:
        after_pos = next((p for p in raw2 if str(p.get("instId")) == inst_id), None)
        after_avail = _avail_sheets(after_pos) if after_pos else 0
    reduced = max(0, before_avail - after_avail) if raw2 is not None else 0
    remaining_pos = after_avail if raw2 is not None else max(0, before_avail - level_sheets)
    fully_closed = remaining_pos < 1

    if fully_closed:
        clear_close_gate(inst_id)
        conn = cfg["get_db"]()
        try:
            from lib.options.options_db import init_options_tables

            init_options_tables(conn)
            open_rows = conn.execute(
                """
                SELECT id, premium_paid FROM options_trades
                WHERE inst_id = ? AND status = 'open'
                ORDER BY id ASC
                """,
                (inst_id,),
            ).fetchall()
            total_paid = sum(float(r["premium_paid"] or 0) for r in open_rows)
            allocated = 0.0
            for i, row in enumerate(open_rows):
                paid = float(row["premium_paid"] or 0)
                if i == len(open_rows) - 1:
                    recv = round(prem_recv - allocated, 4)
                elif total_paid > 0:
                    recv = round(prem_recv * (paid / total_paid), 4)
                    allocated += recv
                else:
                    recv = round(prem_recv / len(open_rows), 4)
                    allocated += recv
                pnl = round(recv - paid, 4)
                note_sql = ""
                params: list[Any] = [px, recv, pnl, oid or None]
                if signal_note and i == len(open_rows) - 1:
                    note_sql = """,
                    signal_note = CASE
                        WHEN signal_note IS NULL OR TRIM(signal_note) = '' THEN ?
                        ELSE signal_note
                    END"""
                    params.append(signal_note)
                params.append(int(row["id"]))
                conn.execute(
                    f"""
                    UPDATE options_trades
                    SET status = 'closed', close_quote = ?, premium_received = ?,
                        realized_pnl = ?, close_ord_id = ?, closed_at = CURRENT_TIMESTAMP
                        {note_sql}
                    WHERE id = ?
                    """,
                    tuple(params),
                )
            conn.commit()
        finally:
            conn.close()
    elif require_recycle_gate:
        # 自动平已挂过单:同仓续批只验流动性
        mark_close_gate_passed(inst_id)

    return {
        "ok": True,
        "mode": "bid1",
        "orders": [{"order": order, "px": px, "sheets": level_sheets}],
        "bid": px,
        "locked_bid_px": locked_bid_px,
        "submitted_sheets": level_sheets,
        "filled_or_reduced_sheets": min(reduced, level_sheets) if reduced else 0,
        "remaining_sheets": remaining_pos,
        "premium_received": prem_recv,
        "stopped_reason": None if fully_closed else ("partial_bid1" if reduced > 0 else "order_not_filled"),
        "close_ord_id": oid or None,
        "fully_closed": fully_closed,
        "msg": (
            f"已按买一 {locked_bid_px:g} 提交 {level_sheets} 张"
            + ("" if fully_closed else f",剩余 {remaining_pos} 张待下次平仓")
        ),
    }


# 兼容旧名
def close_option_by_bid_depth(
    cfg: dict[str, Any],
    ex: Any,
    inst_id: str,
    *,
    sheets: int | None = None,
) -> dict[str, Any]:
    return close_option_by_bid1(
        cfg,
        ex,
        inst_id,
        sheets=sheets,
        require_recycle_gate=True,
        signal_note="目标位平仓",
    )
