"""中控期权档案:同步 OKX options_review_trades 到 hub_symbol_archive.db."""

from __future__ import annotations

import json
import time
from pathlib import Path
from typing import Any

from lib.hub.hub_symbol_archive_lib import (
    TRADING_DAY_RESET_HOUR,
    _connect,
    default_db_path,
    init_db as init_perp_archive_db,
    ms_to_trading_day,
    parse_wall_clock_ms,
    resolve_period_bounds,
    trading_day_bounds_ms,
)


def _now_ms() -> int:
    return int(time.time() * 1000)


def init_options_archive_db(db_path: Path | None = None) -> None:
    """确保期权缓存表存在(与永续共用同一 SQLite)."""
    init_perp_archive_db(db_path)
    conn = _connect(db_path)
    try:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS archive_options_trade_cache (
                exchange_key TEXT NOT NULL,
                history_key TEXT NOT NULL,
                source_type TEXT,
                underlying TEXT,
                opened_at TEXT,
                closed_at TEXT,
                opened_at_ms INTEGER,
                closed_at_ms INTEGER,
                hold_seconds INTEGER,
                realized_pnl_total REAL,
                status_raw TEXT,
                pos_id TEXT,
                inst_id TEXT,
                opt_type TEXT,
                strike REAL,
                exp_time TEXT,
                sheets INTEGER,
                open_avg REAL,
                close_avg REAL,
                premium_paid REAL,
                realized_pnl REAL,
                hedge_plan_id INTEGER,
                plan_close_reason TEXT,
                realized_pnl_perp REAL,
                realized_pnl_options REAL,
                premium_total REAL,
                direction TEXT,
                tp REAL,
                sl REAL,
                target_price REAL,
                target_price_up REAL,
                target_price_down REAL,
                legs_json TEXT,
                linked_hedge_plan_id INTEGER,
                excluded_as_hedge_leg INTEGER DEFAULT 0,
                strategy_tag TEXT,
                result_tag TEXT,
                reviewed INTEGER DEFAULT 0,
                source_label TEXT,
                payload_json TEXT NOT NULL,
                synced_at INTEGER NOT NULL,
                PRIMARY KEY (exchange_key, history_key)
            )
            """
        )
        conn.execute(
            """
            CREATE INDEX IF NOT EXISTS idx_archive_options_closed
            ON archive_options_trade_cache (exchange_key, closed_at_ms)
            """
        )
    finally:
        conn.close()


def purge_stale_options_trades_cache(
    exchange_key: str,
    active_history_keys: list[str],
    *,
    db_path: Path | None = None,
) -> int:
    init_options_archive_db(db_path)
    ex_k = (exchange_key or "").strip().lower()
    if not ex_k:
        return 0
    active = {str(k).strip() for k in (active_history_keys or []) if str(k).strip()}
    conn = _connect(db_path)
    try:
        rows = conn.execute(
            "SELECT history_key FROM archive_options_trade_cache WHERE exchange_key=?",
            (ex_k,),
        ).fetchall()
        stale = [r["history_key"] for r in rows if r["history_key"] not in active]
        removed = 0
        for hk in stale:
            cur = conn.execute(
                "DELETE FROM archive_options_trade_cache WHERE exchange_key=? AND history_key=?",
                (ex_k, hk),
            )
            removed += int(cur.rowcount or 0)
        return removed
    finally:
        conn.close()


def _optional_float(raw: Any) -> float | None:
    if raw in (None, ""):
        return None
    try:
        return float(raw)
    except (TypeError, ValueError):
        return None


def _optional_int(raw: Any) -> int | None:
    if raw in (None, ""):
        return None
    try:
        return int(raw)
    except (TypeError, ValueError):
        return None


def upsert_options_trades_cache(
    exchange_key: str,
    trades: list[dict[str, Any]],
    *,
    db_path: Path | None = None,
    prune_missing: bool = True,
) -> dict[str, int]:
    init_options_archive_db(db_path)
    ex_k = (exchange_key or "").strip().lower()
    if not ex_k:
        return {"upserted": 0, "removed": 0}
    now = _now_ms()
    n = 0
    active_keys: list[str] = []
    conn = _connect(db_path)
    try:
        for t in trades or []:
            if not isinstance(t, dict):
                continue
            hk = str(t.get("history_key") or "").strip()
            if not hk:
                continue
            if int(t.get("excluded_as_hedge_leg") or 0):
                continue
            active_keys.append(hk)
            opened_at = t.get("opened_at")
            closed_at = t.get("closed_at")
            opened_ms = t.get("opened_at_ms") or parse_wall_clock_ms(opened_at)
            closed_ms = t.get("closed_at_ms") or parse_wall_clock_ms(closed_at)
            entry = t.get("entry") if isinstance(t.get("entry"), dict) else {}
            strategy_tag = t.get("strategy_tag") or (entry or {}).get("strategy_tag")
            result_tag = t.get("result_tag") or (entry or {}).get("result_tag")
            reviewed = 1 if t.get("reviewed") or entry else 0
            row = dict(t)
            row["exchange_key"] = ex_k
            payload = json.dumps(row, ensure_ascii=False, default=str)
            conn.execute(
                """
                INSERT INTO archive_options_trade_cache (
                    exchange_key, history_key, source_type, underlying,
                    opened_at, closed_at, opened_at_ms, closed_at_ms, hold_seconds,
                    realized_pnl_total, status_raw,
                    pos_id, inst_id, opt_type, strike, exp_time, sheets,
                    open_avg, close_avg, premium_paid, realized_pnl,
                    hedge_plan_id, plan_close_reason, realized_pnl_perp, realized_pnl_options,
                    premium_total, direction, tp, sl, target_price, target_price_up, target_price_down,
                    legs_json, linked_hedge_plan_id, excluded_as_hedge_leg,
                    strategy_tag, result_tag, reviewed, source_label,
                    payload_json, synced_at
                ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
                ON CONFLICT(exchange_key, history_key) DO UPDATE SET
                    source_type=excluded.source_type,
                    underlying=excluded.underlying,
                    opened_at=excluded.opened_at,
                    closed_at=excluded.closed_at,
                    opened_at_ms=excluded.opened_at_ms,
                    closed_at_ms=excluded.closed_at_ms,
                    hold_seconds=excluded.hold_seconds,
                    realized_pnl_total=excluded.realized_pnl_total,
                    status_raw=excluded.status_raw,
                    pos_id=excluded.pos_id,
                    inst_id=excluded.inst_id,
                    opt_type=excluded.opt_type,
                    strike=excluded.strike,
                    exp_time=excluded.exp_time,
                    sheets=excluded.sheets,
                    open_avg=excluded.open_avg,
                    close_avg=excluded.close_avg,
                    premium_paid=excluded.premium_paid,
                    realized_pnl=excluded.realized_pnl,
                    hedge_plan_id=excluded.hedge_plan_id,
                    plan_close_reason=excluded.plan_close_reason,
                    realized_pnl_perp=excluded.realized_pnl_perp,
                    realized_pnl_options=excluded.realized_pnl_options,
                    premium_total=excluded.premium_total,
                    direction=excluded.direction,
                    tp=excluded.tp,
                    sl=excluded.sl,
                    target_price=excluded.target_price,
                    target_price_up=excluded.target_price_up,
                    target_price_down=excluded.target_price_down,
                    legs_json=excluded.legs_json,
                    linked_hedge_plan_id=excluded.linked_hedge_plan_id,
                    excluded_as_hedge_leg=excluded.excluded_as_hedge_leg,
                    strategy_tag=excluded.strategy_tag,
                    result_tag=excluded.result_tag,
                    reviewed=excluded.reviewed,
                    source_label=excluded.source_label,
                    payload_json=excluded.payload_json,
                    synced_at=excluded.synced_at
                """,
                (
                    ex_k,
                    hk,
                    t.get("source_type"),
                    t.get("underlying"),
                    opened_at,
                    closed_at,
                    int(opened_ms) if opened_ms else None,
                    int(closed_ms) if closed_ms else None,
                    _optional_int(t.get("hold_seconds")),
                    float(t.get("realized_pnl_total") or t.get("realized_pnl") or 0),
                    t.get("status_raw"),
                    t.get("pos_id"),
                    t.get("inst_id"),
                    t.get("opt_type"),
                    _optional_float(t.get("strike")),
                    t.get("exp_time"),
                    _optional_int(t.get("sheets")),
                    _optional_float(t.get("open_avg")),
                    _optional_float(t.get("close_avg")),
                    _optional_float(t.get("premium_paid")),
                    _optional_float(t.get("realized_pnl")),
                    _optional_int(t.get("hedge_plan_id")),
                    t.get("plan_close_reason"),
                    _optional_float(t.get("realized_pnl_perp")),
                    _optional_float(t.get("realized_pnl_options")),
                    _optional_float(t.get("premium_total")),
                    t.get("direction"),
                    _optional_float(t.get("tp")),
                    _optional_float(t.get("sl")),
                    _optional_float(t.get("target_price")),
                    _optional_float(t.get("target_price_up")),
                    _optional_float(t.get("target_price_down")),
                    t.get("legs_json")
                    if isinstance(t.get("legs_json"), str)
                    else (json.dumps(t.get("legs"), ensure_ascii=False) if t.get("legs") else None),
                    _optional_int(t.get("linked_hedge_plan_id")),
                    int(t.get("excluded_as_hedge_leg") or 0),
                    strategy_tag,
                    result_tag,
                    reviewed,
                    t.get("source_label"),
                    payload,
                    now,
                ),
            )
            n += 1
    finally:
        conn.close()
    removed = 0
    if prune_missing:
        removed = purge_stale_options_trades_cache(ex_k, active_keys, db_path=db_path)
    return {"upserted": n, "removed": removed}


def _options_row_to_dict(row: Any) -> dict[str, Any]:
    out: dict[str, Any] = dict(row)
    payload = {}
    raw = out.get("payload_json")
    if raw:
        try:
            payload = json.loads(raw) if isinstance(raw, str) else {}
        except (TypeError, ValueError, json.JSONDecodeError):
            payload = {}
    if isinstance(payload, dict):
        for k, v in payload.items():
            if k not in out or out.get(k) in (None, ""):
                out[k] = v
    pnl = float(out.get("realized_pnl_total") or out.get("realized_pnl") or 0)
    out["realized_pnl_total"] = pnl
    out["pnl_amount"] = pnl  # 复用永续统计/日历字段名
    hold_sec = out.get("hold_seconds")
    if hold_sec is not None:
        try:
            out["hold_minutes"] = round(float(hold_sec) / 60.0, 2)
        except (TypeError, ValueError):
            pass
    if not out.get("opened_at_ms") and out.get("opened_at"):
        ms = parse_wall_clock_ms(out.get("opened_at"))
        if ms:
            out["opened_at_ms"] = int(ms)
    if not out.get("closed_at_ms") and out.get("closed_at"):
        ms = parse_wall_clock_ms(out.get("closed_at"))
        if ms:
            out["closed_at_ms"] = int(ms)
    out["trade_id"] = out.get("history_key")
    out["id"] = out.get("history_key")
    out["symbol"] = out.get("inst_id") or out.get("underlying") or ""
    return out


def _empty_options_stats() -> dict[str, Any]:
    return {
        "open_count": 0,
        "sick_count": 0,
        "sick_pct": 0.0,
        "pnl_total": 0.0,
        "pnl_ex_sick": 0.0,
        "win_count": 0,
        "loss_count": 0,
        "avg_win": 0.0,
        "avg_loss": 0.0,
        "max_win": 0.0,
        "max_loss": 0.0,
        "win_rate": 0.0,
        "profit_loss_ratio": 0.0,
        "turnover_total": 0.0,
        "commission_total": 0.0,
        "premium_total": 0.0,
        "by_exchange": {},
        "by_source_type": {},
    }


def _compute_options_period_stats(trade_rows: list[dict[str, Any]]) -> dict[str, Any]:
    st = _empty_options_stats()
    wins: list[float] = []
    losses: list[float] = []
    by_ex: dict[str, dict[str, Any]] = {}
    by_src: dict[str, dict[str, Any]] = {}

    def bucket() -> dict[str, Any]:
        return {
            "open_count": 0,
            "pnl_total": 0.0,
            "win_count": 0,
            "loss_count": 0,
            "premium_total": 0.0,
        }

    for td in trade_rows:
        pnl = float(td.get("pnl_amount") or td.get("realized_pnl_total") or 0)
        ex = str(td.get("exchange_key") or "okx")
        src = str(td.get("source_type") or td.get("source_label") or "?")
        prem = float(td.get("premium_total") or td.get("premium_paid") or 0)
        st["open_count"] += 1
        st["pnl_total"] += pnl
        st["premium_total"] += prem
        if pnl > 0.0001:
            st["win_count"] += 1
            wins.append(pnl)
        elif pnl < -0.0001:
            st["loss_count"] += 1
            losses.append(pnl)
        if ex not in by_ex:
            by_ex[ex] = bucket()
        by_ex[ex]["open_count"] += 1
        by_ex[ex]["pnl_total"] += pnl
        by_ex[ex]["premium_total"] += prem
        if pnl > 0.0001:
            by_ex[ex]["win_count"] += 1
        elif pnl < -0.0001:
            by_ex[ex]["loss_count"] += 1
        if src not in by_src:
            by_src[src] = bucket()
        by_src[src]["open_count"] += 1
        by_src[src]["pnl_total"] += pnl

    total = int(st["open_count"] or 0)
    st["pnl_ex_sick"] = round(float(st["pnl_total"]), 4)
    st["pnl_total"] = round(float(st["pnl_total"]), 4)
    st["premium_total"] = round(float(st["premium_total"]), 4)
    st["avg_win"] = round(sum(wins) / len(wins), 4) if wins else 0.0
    st["avg_loss"] = round(sum(losses) / len(losses), 4) if losses else 0.0
    st["max_win"] = round(max(wins), 4) if wins else 0.0
    st["max_loss"] = round(min(losses), 4) if losses else 0.0
    st["win_rate"] = round(st["win_count"] / total * 100, 1) if total else 0.0
    if wins and losses and abs(st["avg_loss"]) > 1e-9:
        st["profit_loss_ratio"] = round(abs(st["avg_win"] / st["avg_loss"]), 2)
    for ex, b in by_ex.items():
        b["pnl_total"] = round(float(b["pnl_total"]), 4)
        b["premium_total"] = round(float(b["premium_total"]), 4)
        b["sick_count"] = 0
        b["sick_pct"] = 0.0
        b["pnl_ex_sick"] = b["pnl_total"]
        b["avg_win"] = 0.0
        b["avg_loss"] = 0.0
        b["max_win"] = 0.0
        b["max_loss"] = 0.0
        b["win_rate"] = (
            round(b["win_count"] / b["open_count"] * 100, 1) if b["open_count"] else 0.0
        )
        b["profit_loss_ratio"] = 0.0
        b["turnover_total"] = 0.0
        b["commission_total"] = 0.0
    for src, b in by_src.items():
        b["pnl_total"] = round(float(b["pnl_total"]), 4)
    st["by_exchange"] = by_ex
    st["by_source_type"] = by_src
    return st


def list_daily_options_trades(
    trading_day: str = "",
    *,
    period: str = "",
    date_from: str = "",
    date_to: str = "",
    exchange_key: str = "",
    filter_profit: bool = False,
    filter_loss: bool = False,
    search: str = "",
    source_type: str = "",
    db_path: Path | None = None,
) -> dict[str, Any]:
    init_options_archive_db(db_path)
    p = (period or "today").strip().lower() or "today"
    start_ms, end_ms, df, dt, period_label = resolve_period_bounds(
        period=p,
        trading_day=trading_day,
        date_from=date_from,
        date_to=date_to,
    )
    ex_filter = (exchange_key or "").strip().lower()
    src_filter = (source_type or "").strip().lower()
    conn = _connect(db_path)
    try:
        params: list[Any] = [start_ms, end_ms]
        where = "closed_at_ms IS NOT NULL AND closed_at_ms >= ? AND closed_at_ms < ?"
        where += " AND COALESCE(excluded_as_hedge_leg,0)=0"
        if ex_filter:
            where += " AND exchange_key=?"
            params.append(ex_filter)
        if src_filter:
            where += " AND LOWER(COALESCE(source_type,''))=?"
            params.append(src_filter)
        rows = conn.execute(
            f"""
            SELECT * FROM archive_options_trade_cache
            WHERE {where}
            ORDER BY closed_at_ms DESC, history_key DESC
            """,
            params,
        ).fetchall()
        trades: list[dict[str, Any]] = []
        q = (search or "").strip().lower()
        for r in rows:
            td = _options_row_to_dict(r)
            pnl = float(td.get("pnl_amount") or 0)
            if filter_profit and pnl <= 0.0001:
                continue
            if filter_loss and pnl >= -0.0001:
                continue
            if q:
                blob = " ".join(
                    str(td.get(k) or "")
                    for k in (
                        "underlying",
                        "inst_id",
                        "exchange_key",
                        "source_type",
                        "source_label",
                        "opt_type",
                        "strategy_tag",
                        "result_tag",
                        "direction",
                    )
                ).lower()
                if q not in blob:
                    continue
            trades.append(td)
        return {
            "period": p,
            "period_label": period_label,
            "trading_day": dt,
            "date_from": df,
            "date_to": dt,
            "product": "options",
            "trades": trades,
            "stats": _compute_options_period_stats(trades),
        }
    finally:
        conn.close()


def list_archive_options_calendar(
    year: int,
    month: int,
    *,
    exchange_key: str = "",
    db_path: Path | None = None,
    reset_hour: int = TRADING_DAY_RESET_HOUR,
) -> dict[str, Any]:
    init_options_archive_db(db_path)
    y = int(year)
    m = int(month)
    if m < 1 or m > 12:
        raise ValueError("month 无效")
    from datetime import datetime, timedelta

    first = f"{y:04d}-{m:02d}-01"
    if m == 12:
        next_first = datetime(y + 1, 1, 1)
    else:
        next_first = datetime(y, m + 1, 1)
    last = (next_first - timedelta(days=1)).strftime("%Y-%m-%d")
    start_ms, _ = trading_day_bounds_ms(first, reset_hour=reset_hour)
    _, end_ms = trading_day_bounds_ms(last, reset_hour=reset_hour)
    ex_filter = (exchange_key or "").strip().lower()
    conn = _connect(db_path)
    try:
        params: list[Any] = [start_ms, end_ms]
        where = (
            "closed_at_ms IS NOT NULL AND closed_at_ms >= ? AND closed_at_ms < ?"
            " AND COALESCE(excluded_as_hedge_leg,0)=0"
        )
        if ex_filter:
            where += " AND exchange_key=?"
            params.append(ex_filter)
        rows = conn.execute(
            f"SELECT * FROM archive_options_trade_cache WHERE {where}",
            params,
        ).fetchall()
        days: dict[str, dict[str, Any]] = {}
        for r in rows:
            td = _options_row_to_dict(r)
            closed_ms = td.get("closed_at_ms") or parse_wall_clock_ms(td.get("closed_at"))
            if not closed_ms:
                continue
            day = ms_to_trading_day(int(closed_ms), reset_hour=reset_hour)
            if not day or day < first or day > last:
                continue
            bucket = days.setdefault(
                day,
                {
                    "trading_day": day,
                    "open_count": 0,
                    "sick_count": 0,
                    "pnl_total": 0.0,
                    "turnover_total": 0.0,
                    "commission_total": 0.0,
                    "has_sick": False,
                },
            )
            bucket["open_count"] += 1
            bucket["pnl_total"] += float(td.get("pnl_amount") or 0)
        for d in days.values():
            d["pnl_total"] = round(float(d["pnl_total"]), 4)
        month_pnl = sum(float(d["pnl_total"]) for d in days.values())
        month_count = sum(int(d["open_count"]) for d in days.values())
        return {
            "year": y,
            "month": m,
            "date_from": first,
            "date_to": last,
            "product": "options",
            "days": days,
            "month_pnl_total": round(month_pnl, 4),
            "month_open_count": month_count,
        }
    finally:
        conn.close()


def sync_options_exchange_archive(
    exchange_key: str,
    trades: list[dict[str, Any]],
    *,
    db_path: Path | None = None,
) -> dict[str, Any]:
    """仅缓存期权交易,不做 K 线."""
    r = upsert_options_trades_cache(
        exchange_key, trades, db_path=db_path, prune_missing=True
    )
    return {
        "ok": True,
        "exchange_key": (exchange_key or "").strip().lower(),
        "product": "options",
        "trades_upserted": r.get("upserted", 0),
        "trades_removed": r.get("removed", 0),
        "trade_count": len(trades or []),
    }
