"""Quote models: Quote, QuoteSnapshot, QuoteHistory."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

try:
    import pandas as pd

    _HAS_PANDAS = True
except ImportError:
    _HAS_PANDAS = False


@dataclass
class Quote:
    """A single price quote for one symbol.

    Attributes
    ----------
    symbol : str
        Normalized product symbol (e.g. "XAUUSD").
    bid : float
        Bid (buy) price.
    ask : float
        Ask (sell) price.
    spread : float | None
        Bid-ask spread in price units.
    received_at : str
        ISO-style timestamp (with milliseconds) when the server received the tick.
    quote_ts_ms : int | None
        Original quote timestamp in milliseconds (UTC epoch).
    quote_time_shanghai : str | None
        Quote timestamp expressed in Shanghai (UTC+8) time.
    server_ts_ms : int | None
        Server-side timestamp in milliseconds at time of response.
    server_time_shanghai : str | None
        Server time in Shanghai timezone.
    latency_ms : int | None
        Observed latency in milliseconds (server_ts_ms - quote_ts_ms).
    is_stale : bool | None
        True if the quote is considered stale (> 3 s old).
    """

    symbol: str
    bid: float
    ask: float
    spread: Optional[float] = None
    received_at: str = ""
    quote_ts_ms: Optional[int] = None
    quote_time_shanghai: Optional[str] = None
    server_ts_ms: Optional[int] = None
    server_time_shanghai: Optional[str] = None
    latency_ms: Optional[int] = None
    is_stale: Optional[bool] = None

    @property
    def mid(self) -> float:
        """Mid price = (bid + ask) / 2."""
        return (self.bid + self.ask) / 2.0

    def to_dict(self) -> Dict[str, Any]:
        return {
            "symbol": self.symbol,
            "bid": self.bid,
            "ask": self.ask,
            "mid": self.mid,
            "spread": self.spread,
            "received_at": self.received_at,
            "quote_ts_ms": self.quote_ts_ms,
            "quote_time_shanghai": self.quote_time_shanghai,
            "server_ts_ms": self.server_ts_ms,
            "server_time_shanghai": self.server_time_shanghai,
            "latency_ms": self.latency_ms,
            "is_stale": self.is_stale,
        }

    def to_series(self) -> "pd.Series":
        """Return a pandas Series representing this quote row."""
        if not _HAS_PANDAS:
            raise ImportError("pandas is required for to_series()")
        return pd.Series(self.to_dict())

    def to_pandas_row(self) -> "pd.DataFrame":
        """Return a single-row DataFrame."""
        if not _HAS_PANDAS:
            raise ImportError("pandas is required for to_pandas_row()")
        return pd.DataFrame([self.to_dict()])

    @classmethod
    def from_api(cls, data: Dict[str, Any]) -> "Quote":
        """Construct a Quote from a raw API response dict."""
        return cls(
            symbol=str(data.get("symbol", "")),
            bid=float(data["bid"]),
            ask=float(data["ask"]),
            spread=float(data["spread"]) if data.get("spread") is not None else None,
            received_at=str(data.get("received_at", "")),
            quote_ts_ms=int(data["quote_ts_ms"]) if data.get("quote_ts_ms") is not None else None,
            quote_time_shanghai=data.get("quote_time_shanghai"),
            server_ts_ms=int(data["server_ts_ms"]) if data.get("server_ts_ms") is not None else None,
            server_time_shanghai=data.get("server_time_shanghai"),
            latency_ms=int(data["latency_ms"]) if data.get("latency_ms") is not None else None,
            is_stale=bool(data["is_stale"]) if data.get("is_stale") is not None else None,
        )


@dataclass
class QuoteSnapshot:
    """A snapshot of the full market — one Quote per symbol.

    Attributes
    ----------
    count : int
        Number of quotes in the snapshot.
    data : List[Quote]
        List of Quote objects.
    server_time_shanghai : str | None
        Server timestamp (Shanghai timezone).
    """

    count: int
    data: List[Quote] = field(default_factory=list)
    server_time_shanghai: Optional[str] = None

    @classmethod
    def from_api(cls, payload: Dict[str, Any]) -> "QuoteSnapshot":
        quotes = [Quote.from_api(q) for q in payload.get("data", [])]
        return cls(
            count=payload.get("count", len(quotes)),
            data=quotes,
            server_time_shanghai=payload.get("server_time_shanghai"),
        )


@dataclass
class QuoteHistory:
    """Historical sequence of quotes for a symbol.

    Attributes
    ----------
    symbol : str
        Product symbol.
    data : List[Quote]
        Chronologically ordered list of quotes.
    from_ts : int | None
        Start of the queried range (ms epoch).
    to_ts : int | None
        End of the queried range (ms epoch).
    """

    symbol: str
    data: List[Quote] = field(default_factory=list)
    from_ts: Optional[int] = None
    to_ts: Optional[int] = None

    def to_pandas(self) -> "pd.DataFrame":
        """Convert the full history to a pandas DataFrame."""
        if not _HAS_PANDAS:
            raise ImportError("pandas is required for to_pandas()")
        rows = [q.to_dict() for q in self.data]
        return pd.DataFrame(rows)
