from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


@dataclass(slots=True)
class BookLevel:
    px: float
    sz: float  # OKX 张数 / 合约张数口径


@dataclass(slots=True)
class Quote:
    inst_id: str
    bid: float | None = None
    ask: float | None = None
    bid_sz: float | None = None
    ask_sz: float | None = None
    mark_px: float | None = None
    ts_ms: int | None = None
    bids: list[BookLevel] = field(default_factory=list)
    asks: list[BookLevel] = field(default_factory=list)

    def to_dict(self, *, depth: int = 5) -> dict[str, Any]:
        return {
            "inst_id": self.inst_id,
            "bid": self.bid,
            "ask": self.ask,
            "bid_sz": self.bid_sz,
            "ask_sz": self.ask_sz,
            "mark_px": self.mark_px,
            "ts_ms": self.ts_ms,
            "bids": [{"px": x.px, "sz": x.sz} for x in self.bids[:depth]],
            "asks": [{"px": x.px, "sz": x.sz} for x in self.asks[:depth]],
        }


@dataclass(slots=True)
class OptionPair:
    expiry_ymd: str  # YYMMDD
    expiry_ms: int
    strike: float
    call_inst_id: str
    put_inst_id: str

    def to_dict(self) -> dict[str, Any]:
        return {
            "expiry_ymd": self.expiry_ymd,
            "expiry_ms": self.expiry_ms,
            "strike": self.strike,
            "call_inst_id": self.call_inst_id,
            "put_inst_id": self.put_inst_id,
        }


@dataclass(slots=True)
class MarketSnapshot:
    perp: Quote | None
    call: Quote | None
    put: Quote | None
    index_px: float | None
    pair: OptionPair | None
    connected: bool
    updated_at_ms: int | None

    def to_dict(self) -> dict[str, Any]:
        return {
            "connected": self.connected,
            "updated_at_ms": self.updated_at_ms,
            "index_px": self.index_px,
            "pair": self.pair.to_dict() if self.pair else None,
            "perp": self.perp.to_dict() if self.perp else None,
            "call": self.call.to_dict() if self.call else None,
            "put": self.put.to_dict() if self.put else None,
            "ask_compare": {
                "call_ask": self.call.ask if self.call else None,
                "put_ask": self.put.ask if self.put else None,
                "bias": _open_bias(self.pair, self.index_px, self.perp, self.call, self.put),
            },
        }


def _open_bias(
    pair: OptionPair | None,
    index_px: float | None,
    perp: Quote | None,
    call: Quote | None,
    put: Quote | None,
) -> str:
    """与开仓 decide 一致；固定方向开启时显示 fixed_*。"""
    try:
        from ..config import get_settings
        from ..models.db import get_db

        s = get_settings()
        db = get_db()
        raw = db.get_setting(
            "fixed_direction_enabled", str(s.fixed_direction_enabled)
        )
        on = str(raw or "").strip().lower() in ("1", "true", "yes", "on")
        if on:
            side = str(
                db.get_setting("fixed_perp_side", s.fixed_perp_side)
                or s.fixed_perp_side
                or "long"
            ).strip().lower()
            return "fixed_long_put" if side == "long" else "fixed_short_call"
    except Exception:
        pass
    mark = None
    if index_px is not None and index_px > 0:
        mark = float(index_px)
    elif perp and perp.mark_px and perp.mark_px > 0:
        mark = float(perp.mark_px)
    elif perp and perp.bid and perp.ask:
        mark = (float(perp.bid) + float(perp.ask)) / 2
    if pair is not None and mark is not None:
        diff = float(pair.strike) - mark
        if diff < -1e-9:
            return "strike_below_spot"
        if diff > 1e-9:
            return "strike_above_spot"
    ca = call.ask if call else None
    pa = put.ask if put else None
    if ca is None or pa is None:
        return "unknown"
    if ca > pa:
        return "call_ask_gt_put"
    if ca < pa:
        return "put_ask_gt_call"
    return "equal"
