"""对冲计划监控:永期 TP/SL、期期目标价、到期结算与微信收口推送."""
from __future__ import annotations

import os
from datetime import datetime, timezone
from typing import Any, Optional

from lib.hedge_plan.hedge_plan_db import get_plan, get_plan_legs, list_plans, update_plan
from lib.hedge_plan.hedge_plan_notify_lib import notify_hedge, notify_plan_end, build_hedge_alert_message
from lib.hedge_plan.hedge_plan_orders_lib import _sell_option
from lib.hedge_plan.hedge_plan_settle_lib import leg_is_expired, settle_option_leg_at_spot


def _now() -> str:
    return datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M:%S")


def _env_bool(key: str, default: bool = False) -> bool:
    raw = (os.getenv(key) or "").strip().lower()
    if not raw:
        return default
    return raw in ("1", "true", "yes", "on")


def _sf(v: Any) -> Optional[float]:
    try:
        if v is None or v == "":
            return None
        return float(v)
    except (TypeError, ValueError):
        return None


def _perp_live_contracts(cfg: dict[str, Any], symbol: str, direction: str) -> Optional[float]:
    fn = cfg.get("get_live_position_contracts")
    if not callable(fn):
        return None
    try:
        return fn(symbol, direction)
    except Exception:
        return None


def _index_px(cfg: dict[str, Any], underlying: str) -> Optional[float]:
    ex = cfg.get("exchange_options")
    fn = cfg.get("fetch_index_price")
    if callable(fn) and ex is not None:
        try:
            return fn(ex, underlying)
        except Exception:
            return None
    # 无期权账户时回退永续 ticker
    ex_perp = cfg.get("exchange")
    if ex_perp is not None:
        try:
            base = (underlying or "ETH").upper()
            sym = f"{base}/USDT:USDT"
            t = ex_perp.fetch_ticker(sym)
            return _sf((t.get("info") or {}).get("markPx")) or _sf(t.get("last"))
        except Exception:
            return None
    return None


def tick_active_plans(cfg: dict[str, Any]) -> dict[str, Any]:
    """扫描 active 计划 + 止盈后遗留期权到期收口.返回处理摘要."""
    get_db = cfg.get("get_db")
    if not callable(get_db):
        return {"ok": False, "msg": "get_db missing"}
    conn = get_db()
    acted: list[dict[str, Any]] = []
    try:
        from lib.hedge_plan.hedge_plan_db import init_hedge_plan_tables

        init_hedge_plan_tables(conn)
        plans = list_plans(conn, status="active", limit=40)
        for plan in plans:
            r = _tick_one(cfg, conn, plan)
            if r:
                acted.append(r)
        orphaned = _settle_orphaned_after_tp(cfg, conn)
        acted.extend(orphaned)
        conn.commit()
    finally:
        conn.close()
    return {"ok": True, "acted": acted}


def _notify_end_reload(cfg: dict[str, Any], conn: Any, plan_id: int) -> None:
    plan = get_plan(conn, int(plan_id))
    if plan:
        notify_plan_end(cfg, conn, plan)


def _tick_one(cfg: dict[str, Any], conn: Any, plan: dict[str, Any]) -> Optional[dict[str, Any]]:
    pt = plan.get("plan_type")
    legs = get_plan_legs(conn, int(plan["id"]))
    if pt == "perp_options":
        # 先判断期权是否已过期且永续仍在(罕见);主路径仍是永续平仓侦测
        r = _tick_po(cfg, conn, plan, legs)
        return r
    if pt == "options_options":
        r = _tick_oo_expiry(cfg, conn, plan, legs)
        if r:
            return r
        return _tick_oo_target(cfg, conn, plan, legs)
    return None


def _tick_po(cfg: dict[str, Any], conn: Any, plan: dict[str, Any], legs: list[dict[str, Any]]) -> Optional[dict[str, Any]]:
    perp = next((x for x in legs if x.get("leg_role") == "perp"), None)
    opt = next((x for x in legs if x.get("leg_role") == "option_hedge"), None)
    if not perp or perp.get("status") != "open":
        return None
    symbol = perp.get("symbol") or ""
    direction = (plan.get("direction") or "long").lower()
    live = _perp_live_contracts(cfg, symbol, direction)
    # 仍有仓 → 未触达交易所 TP/SL
    if live is not None and live > 0:
        return None
    # 仓已平:用标记/最新粗判 TP or SL
    entry = _sf(plan.get("entry_mark")) or _sf(perp.get("avg_open")) or 0
    tp = _sf(plan.get("tp"))
    sl = _sf(plan.get("sl"))
    mark = None
    ex = cfg.get("exchange")
    if ex is not None and symbol:
        try:
            t = ex.fetch_ticker(symbol)
            mark = _sf((t.get("info") or {}).get("markPx")) or _sf(t.get("last"))
        except Exception:
            mark = None
    reason = "perp_tp"
    if mark is not None and sl is not None and entry:
        if direction == "long" and mark <= sl:
            reason = "perp_sl"
        elif direction == "short" and mark >= sl:
            reason = "perp_sl"
        elif tp is not None:
            if direction == "long" and mark >= tp:
                reason = "perp_tp"
            elif direction == "short" and mark <= tp:
                reason = "perp_tp"
    premium = float(plan.get("premium_total") or 0)
    cs = float(cfg.get("default_contract_size") or 0.01)
    get_cs = cfg.get("get_contract_size")
    if callable(get_cs) and symbol:
        try:
            cs = float(get_cs(symbol) or cs)
        except Exception:
            pass
    size = float(perp.get("size") or 0)
    exit_px = mark or (tp if reason == "perp_tp" else sl) or entry
    coins = size * cs
    if direction == "short":
        perp_pnl = (entry - exit_px) * coins
    else:
        perp_pnl = (exit_px - entry) * coins

    opt_pnl = -premium
    if reason == "perp_sl" and opt and _env_bool("HEDGE_PLAN_ON_PERP_SL_CLOSE_OPTIONS", True):
        close_r = _sell_option(
            cfg,
            inst_id=str(opt.get("inst_id") or ""),
            sheets=float(opt.get("size") or 1),
        )
        if not close_r.get("ok"):
            notify_hedge(
                cfg,
                build_hedge_alert_message(
                    title="永续止损后期权强制平仓失败",
                    plan_id=plan.get("id"),
                    detail=str(close_r.get("msg") or close_r),
                ),
            )
        if close_r.get("ok"):
            bid = _sf(close_r.get("bid"))
            ask_open = _sf(opt.get("avg_open"))
            if bid is not None and ask_open is not None:
                ct = float(opt.get("ct_mult") or 0.01)
                opt_pnl = (bid - ask_open) * float(opt.get("size") or 1) * ct
            else:
                opt_pnl = -premium
        conn.execute(
            "UPDATE hedge_plan_legs SET status=?, close_reason=?, closed_at=?, realized_pnl=? WHERE id=?",
            ("closed", reason, _now(), opt_pnl, opt["id"]),
        )
    elif reason == "perp_tp" and opt:
        if _env_bool("HEDGE_PLAN_ON_PERP_TP_CLOSE_OPTIONS", False):
            close_r = _sell_option(cfg, inst_id=str(opt.get("inst_id") or ""), sheets=float(opt.get("size") or 1))
            if not close_r.get("ok"):
                notify_hedge(
                    cfg,
                    build_hedge_alert_message(
                        title="永续止盈后期权平仓失败",
                        plan_id=plan.get("id"),
                        detail=str(close_r.get("msg") or close_r),
                    ),
                )
            conn.execute(
                "UPDATE hedge_plan_legs SET status=?, close_reason=?, closed_at=? WHERE id=?",
                ("closed", reason, _now(), opt["id"]),
            )
        else:
            conn.execute(
                "UPDATE hedge_plan_legs SET status=?, close_reason=? WHERE id=?",
                ("hold_to_expiry", "orphaned_after_tp", opt["id"]),
            )
        opt_pnl = -premium

    if reason == "perp_tp":
        total = perp_pnl + opt_pnl
    else:
        total = opt_pnl + perp_pnl

    conn.execute(
        "UPDATE hedge_plan_legs SET status=?, close_reason=?, closed_at=?, realized_pnl=? WHERE id=?",
        ("closed", reason, _now(), perp_pnl, perp["id"]),
    )
    update_plan(
        conn,
        int(plan["id"]),
        status="closed",
        close_reason=reason,
        realized_pnl_perp=round(perp_pnl, 4),
        realized_pnl_options=round(opt_pnl, 4),
        realized_pnl_total=round(total, 4),
        stats_bucket="tp" if reason == "perp_tp" else "sl",
        closed_at=_now(),
    )
    _notify_end_reload(cfg, conn, int(plan["id"]))
    return {"plan_id": plan["id"], "close_reason": reason, "total": total}


def _tick_oo_target(
    cfg: dict[str, Any], conn: Any, plan: dict[str, Any], legs: list[dict[str, Any]]
) -> Optional[dict[str, Any]]:
    """期期:触及上破或下破目标价时平盈利腿."""
    idx = _index_px(cfg, str(plan.get("underlying") or "ETH"))
    if idx is None:
        return None
    up = _sf(plan.get("target_price_up"))
    down = _sf(plan.get("target_price_down"))
    # 旧计划仅有单目标:两边都用它
    legacy = _sf(plan.get("target_price"))
    if up is None and legacy is not None:
        up = legacy
    if down is None and legacy is not None:
        down = legacy
    if up is None and down is None:
        return None

    hit_side: Optional[str] = None
    # 上破:现价接近或超过上破目标
    if up is not None and idx >= up * 0.998:
        hit_side = "up"
    # 下破:现价接近或低于下破目标
    elif down is not None and idx <= down * 1.002:
        hit_side = "down"
    if not hit_side:
        return None
    if not _env_bool("HEDGE_PLAN_OO_CLOSE_WINNER_ONLY", True):
        return None
    open_legs = [x for x in legs if x.get("status") == "open" and str(x.get("leg_role") or "").startswith("option")]
    if len(open_legs) < 2:
        return None
    winners = []
    for leg in open_legs:
        strike = _sf(leg.get("strike")) or 0
        o = (leg.get("opt_type") or "").upper()
        intrinsic = max(0.0, idx - strike) if o == "C" else max(0.0, strike - idx)
        premium = float(leg.get("premium") or 0)
        pnl = intrinsic * float(leg.get("size") or 1) * float(leg.get("ct_mult") or 0.01) - premium
        winners.append((pnl, leg))
    winners.sort(key=lambda x: x[0], reverse=True)
    best_pnl, best = winners[0]
    if best_pnl <= 0:
        return None
    close_r = _sell_option(cfg, inst_id=str(best.get("inst_id") or ""), sheets=float(best.get("size") or 1))
    if not close_r.get("ok"):
        notify_hedge(
            cfg,
            build_hedge_alert_message(
                title="期期平盈利腿失败",
                plan_id=plan.get("id"),
                detail=str(close_r.get("msg") or close_r),
            ),
        )
        return {"plan_id": plan["id"], "msg": "平盈利腿失败", "close": close_r}
    reason = "target_up_win_leg" if hit_side == "up" else "target_down_win_leg"
    conn.execute(
        "UPDATE hedge_plan_legs SET status=?, close_reason=?, closed_at=?, realized_pnl=? WHERE id=?",
        ("closed", reason, _now(), best_pnl, best["id"]),
    )
    update_plan(conn, int(plan["id"]), close_reason=reason)
    mid = dict(plan)
    mid["close_reason"] = reason
    mid["status"] = "active"
    notify_plan_end(cfg, conn, mid)
    return {
        "plan_id": plan["id"],
        "close_reason": reason,
        "hit_side": hit_side,
        "closed_leg": best.get("id"),
        "index": idx,
    }


def _tick_oo_expiry(
    cfg: dict[str, Any], conn: Any, plan: dict[str, Any], legs: list[dict[str, Any]]
) -> Optional[dict[str, Any]]:
    """期期:剩余期权腿全部到期 → 结算合计并结束计划."""
    pending = [
        x
        for x in legs
        if str(x.get("leg_role") or "").startswith("option")
        and str(x.get("status") or "") in ("open", "hold_to_expiry")
    ]
    if not pending:
        # 若腿已全部 closed 但计划仍 active(异常残留)则用腿合计收口
        closed_opts = [
            x for x in legs if str(x.get("leg_role") or "").startswith("option") and x.get("status") == "closed"
        ]
        if len(closed_opts) < 1:
            return None
        total_opts = sum(float(x.get("realized_pnl") or 0) for x in closed_opts)
        reason = "oo_expiry_loss" if total_opts <= 0 else "oo_expiry_win"
        update_plan(
            conn,
            int(plan["id"]),
            status="closed",
            close_reason=reason,
            realized_pnl_options=round(total_opts, 4),
            realized_pnl_total=round(total_opts, 4),
            stats_bucket=reason if reason == "oo_expiry_loss" else "oo_target",
            closed_at=_now(),
        )
        _notify_end_reload(cfg, conn, int(plan["id"]))
        return {"plan_id": plan["id"], "close_reason": reason, "total": total_opts}

    if not all(leg_is_expired(x) for x in pending):
        return None

    spot = _index_px(cfg, str(plan.get("underlying") or "ETH"))
    if spot is None:
        return None

    settled_sum = 0.0
    for leg in pending:
        pnl = settle_option_leg_at_spot(leg, float(spot))
        settled_sum += pnl
        conn.execute(
            "UPDATE hedge_plan_legs SET status=?, close_reason=?, closed_at=?, realized_pnl=? WHERE id=?",
            ("closed", "expiry", _now(), round(pnl, 4), leg["id"]),
        )

    already = sum(
        float(x.get("realized_pnl") or 0)
        for x in legs
        if str(x.get("leg_role") or "").startswith("option") and x.get("status") == "closed"
    )
    total = already + settled_sum
    reason = "oo_expiry_loss" if total <= 0 else "oo_expiry_win"
    bucket = "oo_expiry_loss" if reason == "oo_expiry_loss" else "oo_target"
    update_plan(
        conn,
        int(plan["id"]),
        status="closed",
        close_reason=reason,
        realized_pnl_options=round(total, 4),
        realized_pnl_total=round(total, 4),
        stats_bucket=bucket,
        closed_at=_now(),
    )
    _notify_end_reload(cfg, conn, int(plan["id"]))
    return {"plan_id": plan["id"], "close_reason": reason, "total": total, "spot": spot}


def _settle_orphaned_after_tp(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any]]:
    """永期止盈后 hold_to_expiry 期权到期:只更新腿,不回写计划合计."""
    rows = conn.execute(
        """
        SELECT l.id AS leg_id, l.plan_id, l.inst_id, l.opt_type, l.strike, l.size, l.premium, l.status,
               p.underlying, p.status AS plan_status
        FROM hedge_plan_legs l
        JOIN hedge_plans p ON p.id = l.plan_id
        WHERE l.status = 'hold_to_expiry' AND l.close_reason = 'orphaned_after_tp'
        LIMIT 40
        """
    ).fetchall()
    acted: list[dict[str, Any]] = []
    for row in rows:
        leg = dict(row)
        if not leg_is_expired(leg):
            continue
        spot = _index_px(cfg, str(leg.get("underlying") or "ETH"))
        if spot is None:
            continue
        pnl = settle_option_leg_at_spot(leg, float(spot))
        conn.execute(
            "UPDATE hedge_plan_legs SET status=?, close_reason=?, closed_at=?, realized_pnl=? WHERE id=?",
            ("closed", "expiry", _now(), round(pnl, 4), leg["leg_id"]),
        )
        # 故意不 UPDATE hedge_plans.realized_pnl_*
        acted.append(
            {
                "plan_id": leg["plan_id"],
                "close_reason": "orphaned_option_expiry",
                "leg_id": leg["leg_id"],
                "leg_pnl": round(pnl, 4),
                "note": "不回写计划合计",
            }
        )
    return acted
