"""单独期权翻倍出场:盈利达权利金×倍数后按买一限价平仓.

1 倍 = 盈利金额等于初始权利金 ⇒ 买一可回收 ≥ 权利金 × (1 + 倍数).
与「目标位」并行;与仅微信提醒的 OKX_OPTIONS_PROFIT_ALERT_RATIO 独立.
"""
from __future__ import annotations

import sqlite3
from typing import Any, Callable

from lib.options.options_db import init_options_tables, sum_open_premium_paid


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 ensure_profit_exit_columns(conn: sqlite3.Connection) -> None:
    init_options_tables(conn)
    for ddl in (
        "ALTER TABLE options_trades ADD COLUMN profit_exit_enabled INTEGER DEFAULT 0",
        "ALTER TABLE options_trades ADD COLUMN profit_exit_mult REAL DEFAULT 1.0",
        "ALTER TABLE options_trades ADD COLUMN profit_exit_state TEXT DEFAULT 'idle'",
    ):
        try:
            conn.execute(ddl)
        except Exception:
            pass


def normalize_profit_exit_mult(raw: Any, *, default: float = 1.0) -> float:
    try:
        mult = float(raw)
    except (TypeError, ValueError):
        mult = float(default)
    if mult <= 0:
        mult = float(default)
    return round(mult, 4)


def profit_exit_hit(
    *,
    premium_paid: float,
    recycle_usdc: float,
    mult: float,
) -> bool:
    """1倍:盈利=权利金 ⇒ recycle ≥ premium×(1+mult)."""
    prem = float(premium_paid or 0)
    recv = float(recycle_usdc or 0)
    m = float(mult or 0)
    if prem <= 0 or m <= 0 or recv <= 0:
        return False
    return recv + 1e-9 >= prem * (1.0 + m)


def required_recycle_usdc(premium_paid: float, mult: float) -> float | None:
    prem = float(premium_paid or 0)
    m = float(mult or 0)
    if prem <= 0 or m <= 0:
        return None
    return round(prem * (1.0 + m), 4)


def set_profit_exit(
    conn: sqlite3.Connection,
    *,
    inst_id: str,
    enabled: bool,
    mult: float | None = None,
) -> dict[str, Any]:
    ensure_profit_exit_columns(conn)
    inst = (inst_id or "").strip()
    if not inst:
        return {"ok": False, "msg": "缺少 inst_id"}
    m = normalize_profit_exit_mult(mult if mult is not None else 1.0)
    rows = conn.execute(
        """
        SELECT id FROM options_trades
        WHERE inst_id = ? AND status = 'open'
        """,
        (inst,),
    ).fetchall()
    if not rows:
        return {"ok": False, "msg": "未找到该合约的本地开仓记录"}
    if enabled:
        conn.execute(
            """
            UPDATE options_trades
            SET profit_exit_enabled = 1,
                profit_exit_mult = ?,
                profit_exit_state = 'active'
            WHERE inst_id = ? AND status = 'open'
            """,
            (m, inst),
        )
    else:
        conn.execute(
            """
            UPDATE options_trades
            SET profit_exit_enabled = 0,
                profit_exit_state = 'idle'
            WHERE inst_id = ? AND status = 'open'
            """,
            (inst,),
        )
    return {
        "ok": True,
        "inst_id": inst,
        "profit_exit_enabled": bool(enabled),
        "profit_exit_mult": m if enabled else None,
        "updated": len(rows),
    }


def profit_exit_by_inst(conn: sqlite3.Connection) -> dict[str, dict[str, Any]]:
    """进行中(active/closing)的翻倍出场,按合约取最新一条规则."""
    ensure_profit_exit_columns(conn)
    rows = conn.execute(
        """
        SELECT inst_id, profit_exit_enabled, profit_exit_mult, profit_exit_state
        FROM options_trades
        WHERE status = 'open'
          AND (
            CAST(COALESCE(profit_exit_enabled, 0) AS INTEGER) = 1
            OR COALESCE(profit_exit_state, 'idle') IN ('active', 'closing')
          )
        ORDER BY id DESC
        """
    ).fetchall()
    out: dict[str, dict[str, Any]] = {}
    for r in rows:
        inst = str(r["inst_id"] or "").strip()
        if not inst or inst in out:
            continue
        enabled = int(r["profit_exit_enabled"] or 0) == 1
        state = str(r["profit_exit_state"] or "idle")
        if not enabled and state not in ("active", "closing"):
            continue
        mult = normalize_profit_exit_mult(r["profit_exit_mult"], default=1.0)
        out[inst] = {
            "inst_id": inst,
            "profit_exit_enabled": enabled or state in ("active", "closing"),
            "profit_exit_mult": mult,
            "profit_exit_state": state if state in ("active", "closing") else ("active" if enabled else "idle"),
            "required_recycle": None,
        }
    for inst, info in out.items():
        prem = sum_open_premium_paid(conn, inst)
        if prem is not None:
            info["premium_paid"] = prem
            info["required_recycle"] = required_recycle_usdc(prem, float(info["profit_exit_mult"]))
    return out


def _mark_state(conn: sqlite3.Connection, inst_id: str, state: str) -> None:
    conn.execute(
        """
        UPDATE options_trades
        SET profit_exit_state = ?
        WHERE inst_id = ? AND status = 'open'
        """,
        (state, inst_id),
    )


def _commit(conn: sqlite3.Connection) -> None:
    try:
        conn.commit()
    except Exception:
        pass


def _result_fully_done(result: dict[str, Any]) -> bool:
    if result.get("already_flat"):
        return True
    if result.get("fully_closed"):
        return True
    remaining = result.get("remaining_sheets")
    if remaining is not None and int(remaining) <= 0 and result.get("ok"):
        return True
    return False


def close_option_by_bid_profit_exit(
    cfg: dict[str, Any],
    ex: Any,
    inst_id: str,
    *,
    sheets: int | None = None,
) -> dict[str, Any]:
    from lib.options.options_close_exec_lib import close_option_by_bid1

    return close_option_by_bid1(
        cfg,
        ex,
        inst_id,
        sheets=sheets,
        require_recycle_gate=False,
        signal_note="翻倍出场",
    )


def _estimate_recycle(
    cfg: dict[str, Any],
    ex: Any,
    pos: dict[str, Any],
    premium_paid: float | None,
) -> float | None:
    from lib.options.options_positions_lib import attach_close_preview

    row = dict(pos)
    attach_close_preview(cfg, ex, row, premium_paid=premium_paid)
    preview = row.get("close_preview") if isinstance(row.get("close_preview"), dict) else {}
    if preview.get("bid_invalid"):
        return None
    return _safe_float(preview.get("total_received"))


def _notify_profit_exit_close(
    cfg: dict[str, Any] | None,
    send_wechat: Callable[[str], None] | None,
    *,
    account_label: str,
    inst_id: str,
    mult: float,
    premium_paid: float | None,
    recycle: float | None,
    result: dict[str, Any],
    conn: Any = None,
) -> None:
    if result.get("fully_closed") or result.get("already_flat"):
        if cfg is not None:
            try:
                from lib.options.options_notify_lib import notify_options_close

                notify_options_close(
                    cfg,
                    conn,
                    inst_id=inst_id,
                    reason=f"翻倍出场({mult:g}倍)",
                    sheets=result.get("submitted_sheets"),
                    premium_received=result.get("premium_received"),
                    close_quote=result.get("locked_bid_px") or result.get("bid"),
                )
                return
            except Exception:
                pass
    if not send_wechat:
        return
    try:
        send_wechat(
            "\n".join(
                [
                    "【OKX期权·翻倍出场】",
                    f"账户:{account_label}",
                    f"合约:{inst_id}",
                    f"倍数:{mult:g}(1倍=盈利=权利金)",
                    f"权利金:{premium_paid if premium_paid is not None else '—'}",
                    f"可回收:{recycle if recycle is not None else '—'}",
                    f"提交张数:{result.get('submitted_sheets') or '—'}",
                    f"状态:{'已全平' if (result.get('fully_closed') or result.get('already_flat')) else '挂单中/部分'}",
                ]
            )
        )
    except Exception:
        pass


def run_options_profit_exits(
    conn: sqlite3.Connection,
    positions: list[dict[str, Any]],
    *,
    close_fn: Callable[[str], dict[str, Any]],
    recycle_fn: Callable[[dict[str, Any], float | None], float | None] | None = None,
    send_wechat: Callable[[str], None] | None = None,
    account_label: str = "OKX期权",
    cfg: dict[str, Any] | None = None,
    ex: Any = None,
) -> int:
    """扫描开启翻倍出场的 open 仓;买一可回收达标后限价平仓.返回本次新触发条数."""
    ensure_profit_exit_columns(conn)
    pos_by_inst = {str(p.get("inst_id") or p.get("instId") or ""): p for p in positions}
    hedge_managed: set[str] = set()
    try:
        from lib.hedge_plan.hedge_plan_db import active_hedge_option_inst_ids, init_hedge_plan_tables

        init_hedge_plan_tables(conn)
        hedge_managed = active_hedge_option_inst_ids(conn)
    except Exception:
        return 0

    rules = profit_exit_by_inst(conn)
    triggered = 0

    for inst_id, info in list(rules.items()):
        if not inst_id:
            continue
        if inst_id in hedge_managed:
            _mark_state(conn, inst_id, "idle")
            conn.execute(
                """
                UPDATE options_trades
                SET profit_exit_enabled = 0, profit_exit_state = 'idle'
                WHERE inst_id = ? AND status = 'open'
                """,
                (inst_id,),
            )
            _commit(conn)
            continue
        pos = pos_by_inst.get(inst_id)
        if not pos:
            # 持仓已平:收尾
            _mark_state(conn, inst_id, "done")
            _commit(conn)
            continue

        state = str(info.get("profit_exit_state") or "active")
        mult = normalize_profit_exit_mult(info.get("profit_exit_mult"), default=1.0)
        prem = sum_open_premium_paid(conn, inst_id)
        if prem is None or prem <= 0:
            continue

        if state == "closing":
            result = close_fn(inst_id)
            if result.get("already_flat") or _result_fully_done(result):
                _mark_state(conn, inst_id, "done")
                _commit(conn)
            else:
                _mark_state(conn, inst_id, "closing")
                _commit(conn)
            continue

        if not info.get("profit_exit_enabled"):
            continue

        if recycle_fn is not None:
            recycle = recycle_fn(pos, prem)
        elif cfg is not None and ex is not None:
            recycle = _estimate_recycle(cfg, ex, pos, prem)
        else:
            continue
        if recycle is None:
            continue
        if not profit_exit_hit(premium_paid=prem, recycle_usdc=recycle, mult=mult):
            continue

        result = close_fn(inst_id)
        if result.get("already_flat"):
            _mark_state(conn, inst_id, "done")
            _commit(conn)
            continue
        if not result.get("ok"):
            _mark_state(conn, inst_id, "active")
            _commit(conn)
            continue

        done = _result_fully_done(result)
        _mark_state(conn, inst_id, "done" if done else "closing")
        _commit(conn)
        triggered += 1
        _notify_profit_exit_close(
            cfg,
            send_wechat,
            account_label=account_label,
            inst_id=inst_id,
            mult=mult,
            premium_paid=prem,
            recycle=recycle,
            result=result,
            conn=conn,
        )
    return triggered
