"""
State management — LatestState + RollingWindow.

All structures are asyncio-safe (no locks needed — single-threaded event loop).
"""
from __future__ import annotations

import time
from collections import deque
from dataclasses import dataclass
from datetime import datetime
from typing import Optional

from senpai.md_consumer.models import QuoteEvent, TradeEvent


@dataclass
class LatestTrade:
    symbol: str
    price: float
    size: float
    side: Optional[str]
    provider: str
    ts_recv: datetime
    ts_exchange: Optional[datetime] = None


@dataclass
class LatestQuote:
    symbol: str
    bid: float
    ask: float
    bid_size: float
    ask_size: float
    provider: str
    ts_recv: datetime
    ts_exchange: Optional[datetime] = None


@dataclass
class TradeRecord:
    """Compact trade record for rolling window."""

    price: float
    size: float
    ts: float  # monotonic seconds
    ts_exchange: Optional[datetime] = None
    ts_recv: Optional[datetime] = None


@dataclass
class QuoteRecord:
    """Compact quote record for rolling window."""

    bid: float
    ask: float
    bid_size: float
    ask_size: float
    ts: float  # monotonic seconds
    ts_exchange: Optional[datetime] = None
    ts_recv: Optional[datetime] = None


class RollingWindow:
    """
    Fixed-duration rolling window using deque.

    Efficient: O(1) append, amortised O(1) eviction.
    No pandas dependency.
    """

    def __init__(self, window_seconds: float = 60.0) -> None:
        self._window = window_seconds
        self._trades: deque[TradeRecord] = deque()
        self._quotes: deque[QuoteRecord] = deque()

    def add_trade(self, trade: TradeRecord) -> None:
        self._trades.append(trade)
        self._evict_trades()

    def add_quote(self, quote: QuoteRecord) -> None:
        self._quotes.append(quote)
        self._evict_quotes()

    def _evict_trades(self) -> None:
        cutoff = time.monotonic() - self._window
        while self._trades and self._trades[0].ts < cutoff:
            self._trades.popleft()

    def _evict_quotes(self) -> None:
        cutoff = time.monotonic() - self._window
        while self._quotes and self._quotes[0].ts < cutoff:
            self._quotes.popleft()

    @property
    def trades(self) -> deque[TradeRecord]:
        self._evict_trades()
        return self._trades

    @property
    def quotes(self) -> deque[QuoteRecord]:
        self._evict_quotes()
        return self._quotes

    def trades_since(self, seconds_ago: float) -> list[TradeRecord]:
        """Return trades within the last N seconds."""
        cutoff = time.monotonic() - seconds_ago
        return [t for t in self._trades if t.ts >= cutoff]

    def quotes_since(self, seconds_ago: float) -> list[QuoteRecord]:
        """Return quotes within the last N seconds."""
        cutoff = time.monotonic() - seconds_ago
        return [q for q in self._quotes if q.ts >= cutoff]


class LatestState:
    """
    Maintains latest trade/quote per symbol + rolling windows.
    """

    def __init__(self, window_seconds: float = 60.0) -> None:
        self._window_seconds = window_seconds
        self._latest_trade: dict[str, LatestTrade] = {}
        self._latest_quote: dict[str, LatestQuote] = {}
        self._windows: dict[str, RollingWindow] = {}
        self._event_count = 0

    def _get_window(self, symbol: str) -> RollingWindow:
        if symbol not in self._windows:
            self._windows[symbol] = RollingWindow(self._window_seconds)
        return self._windows[symbol]

    def update_trade(self, event: TradeEvent) -> None:
        """Update latest trade and rolling window."""
        sym = event.symbol.upper()

        self._latest_trade[sym] = LatestTrade(
            symbol=sym,
            price=event.price,
            size=event.size,
            side=event.side,
            provider=event.provider,
            ts_recv=event.ts_recv,
            ts_exchange=event.ts_exchange,
        )

        self._get_window(sym).add_trade(
            TradeRecord(
                price=event.price,
                size=event.size,
                ts=time.monotonic(),
                ts_exchange=event.ts_exchange,
                ts_recv=event.ts_recv,
            )
        )
        self._event_count += 1

    def update_quote(self, event: QuoteEvent) -> None:
        """Update latest quote and rolling window."""
        sym = event.symbol.upper()

        self._latest_quote[sym] = LatestQuote(
            symbol=sym,
            bid=event.bid,
            ask=event.ask,
            bid_size=event.bid_size,
            ask_size=event.ask_size,
            provider=event.provider,
            ts_recv=event.ts_recv,
            ts_exchange=event.ts_exchange,
        )

        self._get_window(sym).add_quote(
            QuoteRecord(
                bid=event.bid,
                ask=event.ask,
                bid_size=event.bid_size,
                ask_size=event.ask_size,
                ts=time.monotonic(),
                ts_exchange=event.ts_exchange,
                ts_recv=event.ts_recv,
            )
        )
        self._event_count += 1

    def get_latest_trade(self, symbol: str) -> LatestTrade | None:
        return self._latest_trade.get(symbol.upper())

    def get_latest_quote(self, symbol: str) -> LatestQuote | None:
        return self._latest_quote.get(symbol.upper())

    def get_window(self, symbol: str) -> RollingWindow | None:
        return self._windows.get(symbol.upper())

    @property
    def symbols(self) -> list[str]:
        return sorted(
            set(list(self._latest_trade.keys()) + list(self._latest_quote.keys()))
        )

    @property
    def event_count(self) -> int:
        return self._event_count

    def to_dict(self, symbol: str) -> dict:
        """Serialise latest state for API."""
        sym = symbol.upper()
        result: dict = {"symbol": sym}

        trade = self._latest_trade.get(sym)
        if trade:
            result["latest_trade"] = {
                "price": trade.price,
                "size": trade.size,
                "side": trade.side,
                "provider": trade.provider,
                "ts_recv": trade.ts_recv.isoformat() if trade.ts_recv else None,
            }

        quote = self._latest_quote.get(sym)
        if quote:
            result["latest_quote"] = {
                "bid": quote.bid,
                "ask": quote.ask,
                "bid_size": quote.bid_size,
                "ask_size": quote.ask_size,
                "provider": quote.provider,
                "ts_recv": quote.ts_recv.isoformat() if quote.ts_recv else None,
            }

        window = self._windows.get(sym)
        if window:
            result["window"] = {
                "trades_count": len(window.trades),
                "quotes_count": len(window.quotes),
            }

        return result
