"""实例数据看板:本户活跃监控 / 持仓只读聚合."""
from __future__ import annotations

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


def _row_dict(row: Any) -> dict[str, Any]:
    if row is None:
        return {}
    if isinstance(row, dict):
        return dict(row)
    try:
        return dict(row)
    except Exception:
        return {}


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


def _dir_label(direction: Any) -> str:
    d = str(direction or "").strip().lower()
    if d == "short":
        return "做空"
    if d == "long":
        return "做多"
    return str(direction or "-")


def _format_order_item(od: dict[str, Any]) -> dict[str, Any]:
    try:
        from lib.strategy.strategy_trade_labels import apply_order_monitor_source_labels

        od = apply_order_monitor_source_labels(od)
    except Exception:
        pass
    try:
        from lib.trade.entry_model_lib import enrich_entry_model_display

        enrich_entry_model_display(od)
    except Exception:
        pass
    sym = od.get("exchange_symbol") or od.get("symbol") or "-"
    direction = str(od.get("direction") or "long").lower()
    mt = od.get("monitor_type_display") or od.get("monitor_type") or ""
    kst = od.get("key_signal_type") or ""
    title = f"{sym} {_dir_label(direction)}"
    bits = [x for x in (mt, kst) if x]
    subtitle = " · ".join(bits) if bits else ""
    entry = _safe_float(od.get("trigger_price"))
    sl = _safe_float(od.get("stop_loss"))
    tp = _safe_float(od.get("take_profit"))
    return {
        "id": od.get("id"),
        "kind": "order",
        "tab": "trade",
        "title": title,
        "subtitle": subtitle,
        "symbol": sym,
        "price_symbol": od.get("symbol") or sym,
        "direction": direction,
        "direction_label": _dir_label(direction),
        "entry": entry,
        "mark_price": None,
        "contracts": _safe_float(od.get("order_amount")),
        "tp_profit": None,
        "float_pnl": None,
        "stop_loss": sl,
        "take_profit": tp,
        "status": od.get("status") or "active",
    }


OPTIONS_SOURCE_LABELS = {
    "option": "纯期权",
    "perp_options": "永期对冲",
    "options_options": "期期对冲",
}

HEDGE_ACTIVE_STATUSES = frozenset({"opening", "active", "partial"})


def _resolve_options_source(conn, inst_id: str) -> tuple[str, str, int | None]:
    """根据进行中对冲计划腿判定来源;默认纯期权. 返回 (source, label, plan_id)."""
    default = ("option", OPTIONS_SOURCE_LABELS["option"], None)
    if not inst_id or not _table_exists(conn, "hedge_plans") or not _table_exists(conn, "hedge_plan_legs"):
        return default
    try:
        row = conn.execute(
            """
            SELECT p.plan_type, p.id
            FROM hedge_plans p
            JOIN hedge_plan_legs l ON l.plan_id = p.id
            WHERE p.status IN ('opening', 'active', 'partial')
              AND l.status = 'open'
              AND l.inst_id = ?
            ORDER BY p.id DESC
            LIMIT 1
            """,
            (inst_id,),
        ).fetchone()
    except Exception:
        return default
    if not row:
        return default
    d = _row_dict(row)
    pt = str(d.get("plan_type") or "").strip()
    try:
        plan_id = int(d["id"]) if d.get("id") is not None else None
    except (TypeError, ValueError):
        plan_id = None
    if pt in OPTIONS_SOURCE_LABELS and pt != "option":
        return pt, OPTIONS_SOURCE_LABELS[pt], plan_id
    return default


def _format_profit_exit_mult(mult: Any) -> str:
    try:
        n = float(mult)
    except (TypeError, ValueError):
        return "1倍"
    if n <= 0:
        return "1倍"
    if abs(n - round(n)) < 1e-9:
        return f"{int(round(n))}倍"
    return f"{n:g}倍"


def _format_options_target(p: dict[str, Any]) -> str:
    hedge = p.get("hedge_plan_target") if isinstance(p.get("hedge_plan_target"), dict) else None
    opt_type = str(p.get("opt_type") or p.get("optType") or "").upper()
    if hedge:
        rr = _safe_float(hedge.get("profit_rr"))
        pid = hedge.get("plan_id")
        if rr is not None and rr > 0:
            return f"对冲#{pid} 盈亏比 {rr:g}" if pid is not None else f"盈亏比 {rr:g}"
        ot = str(hedge.get("opt_type") or opt_type).upper()
        side = "Put ≤" if ot == "P" else "Call ≥"
        tgt = _safe_float(hedge.get("target_index"))
        if tgt is not None:
            return f"对冲#{pid} {side} {tgt:g}" if pid is not None else f"{side} {tgt:g}"
    parts: list[str] = []
    tgt = _safe_float(p.get("target_index"))
    if tgt is not None and tgt > 0:
        side = "Put ≤" if opt_type == "P" else "Call ≥"
        parts.append(f"{side} {tgt:g}")
    if p.get("profit_exit_enabled"):
        parts.append(_format_profit_exit_mult(p.get("profit_exit_mult")))
    if parts:
        return " · ".join(parts)
    return "—"


def _format_options_item(p: dict[str, Any], *, conn=None) -> dict[str, Any]:
    inst = str(p.get("inst_id") or p.get("instId") or "-").strip() or "-"
    opt_type = str(p.get("opt_type") or p.get("optType") or "").upper()
    label = "Call" if opt_type == "C" else "Put" if opt_type == "P" else (opt_type or "OPT")
    # 看板期权列:优先买一净盈亏,残档回退交易所 upl
    pnl = None
    try:
        from lib.options.options_positions_lib import display_pnl_from_option_row

        pnl = display_pnl_from_option_row(p)
    except Exception:
        pnl = None
    pos = _safe_float(p.get("pos"))
    exp_ms = p.get("exp_time_ms")
    if exp_ms is None:
        exp_ms = p.get("exp_time")
    try:
        exp_ms = int(float(exp_ms)) if exp_ms not in (None, "") else None
    except (TypeError, ValueError):
        exp_ms = None
    if conn is not None:
        source_key, source_label, source_plan_id = _resolve_options_source(conn, inst)
    else:
        source_key, source_label, source_plan_id = "option", OPTIONS_SOURCE_LABELS["option"], None
    return {
        "id": inst,
        "kind": "options",
        "tab": "options",
        "title": f"{inst} {label}",
        "subtitle": f"张数 {pos if pos is not None else '-'}",
        "inst_id": inst,
        "opt_type": opt_type,
        "opt_type_label": label,
        "source": source_key,
        "source_label": source_label,
        "source_plan_id": source_plan_id,
        "pos": pos,
        "exp_time_ms": exp_ms,
        "target_monitor": _format_options_target(p),
        "pnl": round(pnl, 4) if pnl is not None else None,
    }


def _format_hedge_item(plan: dict[str, Any]) -> dict[str, Any]:
    pid = plan.get("id")
    underlying = plan.get("underlying") or "-"
    plan_type = plan.get("plan_type") or ""
    status = str(plan.get("status") or "")
    summary = plan.get("contracts_summary") or ""
    plan_type_label = OPTIONS_SOURCE_LABELS.get(plan_type, plan_type)
    active = status in HEDGE_ACTIVE_STATUSES
    status_label = "进行中" if active else (status or "—")
    return {
        "id": pid,
        "kind": "hedge_plan",
        "tab": "hedge_plan",
        "title": f"对冲 #{pid} {underlying}",
        "subtitle": " · ".join(x for x in (plan_type_label, status_label, summary) if x),
        "underlying": underlying,
        "plan_type": plan_type,
        "plan_type_label": plan_type_label,
        "status": status,
        "status_label": status_label,
        "status_active": active,
        "contracts_summary": summary,
    }


def _format_key_item(kd: dict[str, Any]) -> dict[str, Any]:
    sym = kd.get("exchange_symbol") or kd.get("symbol") or "-"
    direction = str(kd.get("direction") or "long").lower()
    signal = kd.get("signal_type") or kd.get("key_signal_type") or kd.get("monitor_type") or ""
    upper = _safe_float(kd.get("upper"))
    lower = _safe_float(kd.get("lower"))
    subtitle_parts = []
    if signal:
        subtitle_parts.append(str(signal))
    if upper is not None or lower is not None:
        subtitle_parts.append(
            f"上{upper if upper is not None else '-'} / 下{lower if lower is not None else '-'}"
        )
    return {
        "id": kd.get("id"),
        "kind": "key",
        "tab": "key_monitor",
        "title": f"{sym} {_dir_label(direction)}",
        "subtitle": " · ".join(subtitle_parts),
        "symbol": sym,
        "direction": direction,
        "direction_label": _dir_label(direction),
        "upper": upper,
        "lower": lower,
        "status": kd.get("status") or "active",
    }


def _format_trend_item(td: dict[str, Any]) -> dict[str, Any]:
    sym = td.get("exchange_symbol") or td.get("symbol") or "-"
    direction = str(td.get("direction") or "long").lower()
    status = td.get("status") or "active"
    entry = _safe_float(td.get("entry_price") or td.get("trigger_price"))
    return {
        "id": td.get("id"),
        "kind": "trend",
        "tab": "strategy",
        "title": f"趋势回调 {sym} {_dir_label(direction)}",
        "subtitle": f"状态 {status}",
        "symbol": sym,
        "direction": direction,
        "direction_label": _dir_label(direction),
        "entry": entry,
        "status": status,
    }


def _format_roll_item(rd: dict[str, Any]) -> dict[str, Any]:
    sym = rd.get("exchange_symbol") or rd.get("symbol") or "-"
    direction = str(rd.get("direction") or "long").lower()
    status = rd.get("status") or "active"
    return {
        "id": rd.get("id"),
        "kind": "roll",
        "tab": "strategy",
        "title": f"顺势加仓 {sym} {_dir_label(direction)}",
        "subtitle": f"状态 {status}",
        "symbol": sym,
        "direction": direction,
        "direction_label": _dir_label(direction),
        "status": status,
    }


def _table_exists(conn, name: str) -> bool:
    try:
        row = conn.execute(
            "SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1",
            (name,),
        ).fetchone()
        return bool(row)
    except Exception:
        return False


def collect_orders(conn) -> list[dict[str, Any]]:
    if not _table_exists(conn, "order_monitors"):
        return []
    rows = conn.execute(
        "SELECT * FROM order_monitors WHERE status='active' ORDER BY id DESC"
    ).fetchall()
    return [_format_order_item(_row_dict(r)) for r in rows]


def collect_keys(conn) -> list[dict[str, Any]]:
    if not _table_exists(conn, "key_monitors"):
        return []
    rows = conn.execute("SELECT * FROM key_monitors ORDER BY id DESC").fetchall()
    return [_format_key_item(_row_dict(r)) for r in rows]


def collect_trends(conn) -> list[dict[str, Any]]:
    if not _table_exists(conn, "trend_pullback_plans"):
        return []
    try:
        rows = conn.execute(
            "SELECT * FROM trend_pullback_plans WHERE status='active' ORDER BY id DESC"
        ).fetchall()
    except Exception:
        return []
    return [_format_trend_item(_row_dict(r)) for r in rows]


def collect_rolls(conn) -> list[dict[str, Any]]:
    if not _table_exists(conn, "roll_groups") or not _table_exists(conn, "order_monitors"):
        return []
    try:
        rows = conn.execute(
            """SELECT g.* FROM roll_groups g
               INNER JOIN order_monitors m ON m.id = g.order_monitor_id AND m.status='active'
               WHERE g.status='active' ORDER BY g.id DESC"""
        ).fetchall()
    except Exception:
        return []
    return [_format_roll_item(_row_dict(r)) for r in rows]


def collect_hedge_plans(conn) -> list[dict[str, Any]]:
    if not _table_exists(conn, "hedge_plans"):
        return []
    try:
        from lib.hedge_plan.hedge_plan_db import attach_legs_to_plans, list_plans

        rows: list[dict[str, Any]] = []
        for status in ("opening", "active", "partial"):
            rows.extend(list_plans(conn, status=status, limit=80))
        rows.sort(key=lambda row: int(row.get("id") or 0), reverse=True)
        plans = attach_legs_to_plans(conn, rows)
        return [_format_hedge_item(p) for p in plans]
    except Exception:
        return []


def collect_options_items(
    fetch_options_positions: Optional[Callable[[], list[dict[str, Any]]]] = None,
    *,
    conn=None,
) -> list[dict[str, Any]]:
    if not callable(fetch_options_positions):
        return []
    try:
        raw = fetch_options_positions() or []
    except Exception:
        return []
    pe_map: dict[str, dict[str, Any]] = {}
    tgt_map: dict[str, dict[str, Any]] = {}
    hedge_map: dict[str, dict[str, Any]] = {}
    if conn is not None:
        try:
            from lib.options.options_profit_exit_lib import profit_exit_by_inst
            from lib.options.options_target_lib import targets_by_inst
            from lib.hedge_plan.hedge_plan_db import active_options_targets_by_inst

            pe_map = profit_exit_by_inst(conn)
            tgt_map = targets_by_inst(conn)
            hedge_map = active_options_targets_by_inst(conn)
        except Exception:
            pe_map, tgt_map, hedge_map = {}, {}, {}
    out: list[dict[str, Any]] = []
    for p in raw:
        if not isinstance(p, dict):
            continue
        row = dict(p)
        inst = str(row.get("inst_id") or row.get("instId") or "").strip()
        mon = tgt_map.get(inst)
        if mon:
            row["target_index"] = mon.get("target_index")
        pe = pe_map.get(inst)
        if pe:
            row["profit_exit_enabled"] = pe.get("profit_exit_enabled")
            row["profit_exit_mult"] = pe.get("profit_exit_mult")
        hedge = hedge_map.get(inst)
        if hedge:
            row["hedge_plan_target"] = hedge
            if not mon:
                row["target_index"] = hedge.get("target_index")
        out.append(_format_options_item(row, conn=conn))
    return out


def _swap_symbol_candidates(row: dict[str, Any]) -> list[str]:
    """优先永续 symbol(含 settle),避免用现货 BTC/USDT 查到 contractSize=1."""
    raw: list[str] = []
    for key in ("symbol", "exchange_symbol", "price_symbol"):
        s = str(row.get(key) or "").strip()
        if s and s not in raw:
            raw.append(s)
    swapish: list[str] = []
    others: list[str] = []
    for s in raw:
        if ":" in s:
            swapish.append(s)
            continue
        others.append(s)
        if "/" in s:
            base, quote = s.split("/", 1)
            q = quote.split(":")[0].strip()
            if base and q:
                swapish.append(f"{base}/{q}:{q}")
    out: list[str] = []
    for s in swapish + others:
        if s and s not in out:
            out.append(s)
    return out


def _resolve_contract_size(
    row_or_sym: Any,
    *,
    get_contract_size: Optional[Callable[[str], Any]] = None,
) -> float:
    if not callable(get_contract_size):
        return 1.0
    if isinstance(row_or_sym, dict):
        candidates = _swap_symbol_candidates(row_or_sym)
    else:
        sym = str(row_or_sym or "").strip()
        candidates = _swap_symbol_candidates({"symbol": sym}) if sym else []
    for sym in candidates:
        try:
            cs = float(get_contract_size(sym) or 0)
            if cs > 0:
                return cs
        except Exception:
            continue
    return 1.0


def _fill_order_pnl_fields(row: dict[str, Any], *, mark: Optional[float], contract_size: float) -> None:
    """按线性 U 本位补看板「盈利金额 / 浮盈」."""
    direction = str(row.get("direction") or "long").lower()
    entry = _safe_float(row.get("entry"))
    contracts = _safe_float(row.get("contracts"))
    tp = _safe_float(row.get("take_profit"))
    if entry is None or contracts is None or contracts <= 0:
        return
    cs = float(contract_size) if contract_size and contract_size > 0 else 1.0
    if mark is not None:
        try:
            from lib.hub.hub_position_metrics import estimate_linear_swap_upnl_usdt

            upnl = estimate_linear_swap_upnl_usdt(direction, entry, mark, contracts, cs)
            if upnl is not None:
                row["float_pnl"] = upnl
        except Exception:
            pass
    if tp is not None and tp > 0:
        try:
            from lib.strategy.strategy_trend_lib import calc_tp_profit_usdt

            profit = calc_tp_profit_usdt(direction, entry, tp, contracts, cs)
            if profit is not None:
                row["tp_profit"] = round(float(profit), 2)
        except Exception:
            pass


def enrich_order_items_with_marks(
    items: list[dict[str, Any]],
    *,
    get_price: Optional[Callable[[str], Any]] = None,
    get_contract_size: Optional[Callable[[str], Any]] = None,
) -> list[dict[str, Any]]:
    """后台聚合时补标记价,并按张数×合约面值估算盈利金额/浮盈."""
    if not items:
        return items
    if not callable(get_price) and not callable(get_contract_size):
        return items
    out: list[dict[str, Any]] = []
    for it in items:
        row = dict(it)
        # 标记价:先试 price_symbol,再试永续候选
        mark = _safe_float(row.get("mark_price"))
        if callable(get_price):
            ordered: list[str] = []
            for s in [str(row.get("price_symbol") or "").strip()] + _swap_symbol_candidates(row):
                if s and s not in ordered:
                    ordered.append(s)
            for sym in ordered:
                try:
                    px = get_price(sym)
                except Exception:
                    px = None
                mark = _safe_float(px)
                if mark is not None:
                    row["mark_price"] = mark
                    break
        cs = _resolve_contract_size(row, get_contract_size=get_contract_size)
        _fill_order_pnl_fields(row, mark=mark, contract_size=cs)
        out.append(row)
    return out


def build_instance_dashboard_payload(
    conn,
    *,
    fetch_options_positions: Optional[Callable[[], list[dict[str, Any]]]] = None,
    hedge_enabled: bool = False,
) -> dict[str, Any]:
    orders = collect_orders(conn)
    keys = collect_keys(conn)
    trends = collect_trends(conn)
    rolls = collect_rolls(conn)
    strategy_items = trends + rolls
    options_items = collect_options_items(fetch_options_positions, conn=conn)
    hedge_items = collect_hedge_plans(conn)  # 始终展示进行中计划,与当前交易模式无关
    # hedge_enabled 仅影响「新建」入口,不隐藏已有仓
    now = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M:%S")
    return {
        "ok": True,
        "updated_at": now,
        "orders": {"title": "实盘下单", "count": len(orders), "items": orders, "tab": "trade"},
        "keys": {"title": "关键位监控", "count": len(keys), "items": keys, "tab": "key_monitor"},
        "strategy": {
            "title": "策略交易",
            "count": len(strategy_items),
            "items": strategy_items,
            "trends": trends,
            "rolls": rolls,
            "tab": "strategy",
        },
        "options": {
            "title": "期权持仓",
            "count": len(options_items),
            "items": options_items,
            "visible": len(options_items) > 0,
            "tab": "options",
        },
        "hedge_plan": {
            "title": "对冲计划",
            "count": len(hedge_items),
            "items": hedge_items,
            "visible": len(hedge_items) > 0,
            "tab": "hedge_plan",
        },
    }
