from __future__ import annotations

import threading
import time
from typing import Iterable

from .types import BookLevel, MarketSnapshot, OptionPair, Quote


class BookCache:
    """内存盘口缓存：永续 + Call/Put。线程安全。"""

    def __init__(self) -> None:
        self._lock = threading.RLock()
        self._quotes: dict[str, Quote] = {}
        self._index_px: float | None = None
        self._pair: OptionPair | None = None
        self._connected = False
        self._updated_at_ms: int | None = None

    def set_connected(self, ok: bool) -> None:
        with self._lock:
            self._connected = bool(ok)

    def set_pair(self, pair: OptionPair | None) -> None:
        with self._lock:
            self._pair = pair

    def set_index_px(self, px: float | None) -> None:
        with self._lock:
            if px is not None and px > 0:
                self._index_px = float(px)
                self._touch()

    def upsert_book(
        self,
        inst_id: str,
        *,
        bids: list[BookLevel],
        asks: list[BookLevel],
        ts_ms: int | None = None,
    ) -> None:
        with self._lock:
            q = self._quotes.get(inst_id) or Quote(inst_id=inst_id)
            q.bids = bids
            q.asks = asks
            q.bid = bids[0].px if bids else None
            q.ask = asks[0].px if asks else None
            q.bid_sz = bids[0].sz if bids else None
            q.ask_sz = asks[0].sz if asks else None
            if ts_ms is not None:
                q.ts_ms = ts_ms
            self._quotes[inst_id] = q
            self._touch(ts_ms)

    def upsert_top(
        self,
        inst_id: str,
        *,
        bid: float | None,
        ask: float | None,
        bid_sz: float | None = None,
        ask_sz: float | None = None,
        ts_ms: int | None = None,
    ) -> None:
        with self._lock:
            q = self._quotes.get(inst_id) or Quote(inst_id=inst_id)
            if bid is not None:
                q.bid = bid
            if ask is not None:
                q.ask = ask
            if bid_sz is not None:
                q.bid_sz = bid_sz
            if ask_sz is not None:
                q.ask_sz = ask_sz
            if ts_ms is not None:
                q.ts_ms = ts_ms
            # 同步一层盘口，便于 snapshot 展示
            if bid is not None and bid_sz is not None:
                q.bids = [BookLevel(px=bid, sz=bid_sz)] + q.bids[1:]
            if ask is not None and ask_sz is not None:
                q.asks = [BookLevel(px=ask, sz=ask_sz)] + q.asks[1:]
            self._quotes[inst_id] = q
            self._touch(ts_ms)

    def set_mark_px(self, inst_id: str, mark_px: float | None, ts_ms: int | None = None) -> None:
        with self._lock:
            if mark_px is None or mark_px <= 0:
                return
            q = self._quotes.get(inst_id) or Quote(inst_id=inst_id)
            q.mark_px = float(mark_px)
            if ts_ms is not None:
                q.ts_ms = ts_ms
            self._quotes[inst_id] = q
            self._touch(ts_ms)

    def get(self, inst_id: str) -> Quote | None:
        with self._lock:
            return self._quotes.get(inst_id)

    def drop_except(self, keep: Iterable[str]) -> None:
        keep_set = set(keep)
        with self._lock:
            for k in list(self._quotes):
                if k not in keep_set:
                    del self._quotes[k]

    def snapshot(self, perp_inst_id: str) -> MarketSnapshot:
        with self._lock:
            pair = self._pair
            call = self._quotes.get(pair.call_inst_id) if pair else None
            put = self._quotes.get(pair.put_inst_id) if pair else None
            return MarketSnapshot(
                perp=self._quotes.get(perp_inst_id),
                call=call,
                put=put,
                index_px=self._index_px,
                pair=pair,
                connected=self._connected,
                updated_at_ms=self._updated_at_ms,
            )

    def _touch(self, ts_ms: int | None = None) -> None:
        self._updated_at_ms = int(ts_ms) if ts_ms is not None else int(time.time() * 1000)
