"""Unified stream entry point — wraps SSE with polling fallback."""

from __future__ import annotations

from typing import Any, Callable, Generator, Optional

from sd_api.config import settings
from sd_api.logging import get_logger
from sd_api.models.quote import Quote, QuoteSnapshot
from sd_api.stream.sse import SSEClient
from sd_api.stream.poller import PollerClient
from sd_api.stream.dispatcher import Dispatcher

logger = get_logger("stream")


class SDStream:
    """Unified real-time streaming client.

    Automatically attempts SSE first; falls back to HTTP polling on failure.

    Usage::

        stream = SDStream(base_url="http://127.0.0.1:5000")

        # Single symbol
        for quote in stream.quote("XAUUSD"):
            print(quote.symbol, quote.bid, quote.ask)

        # Full market snapshot
        for snapshot in stream.quotes():
            print(snapshot.server_time_shanghai, snapshot.count)

        # Callback mode
        dispatcher = Dispatcher()
        @dispatcher.on_quote
        def handle(q):
            print(q.symbol, q.bid, q.ask)
        for _ in stream.quotes(callback=dispatcher):
            pass
    """

    def __init__(
        self,
        base_url: str,
        *,
        interval: float | None = None,
        timeout: float = 60.0,
        prefer_sse: bool = True,
    ) -> None:
        self.base_url = base_url.rstrip("/")
        self.interval = interval or settings.poll_interval
        self.timeout = timeout
        self.prefer_sse = prefer_sse
        self._sse: Optional[SSEClient] = None
        self._poller: Optional[PollerClient] = None

    def _sse_client(self) -> SSEClient:
        if self._sse is None:
            self._sse = SSEClient(
                f"{self.base_url}/api/stream/quote?symbol=XAUUSD",
                timeout=self.timeout,
            )
        return self._sse

    def _poller_client(self) -> PollerClient:
        if self._poller is None:
            self._poller = PollerClient(
                self.base_url,
                interval=self.interval,
                timeout=self.timeout,
            )
        return self._poller

    def quote(
        self,
        symbol: str,
        *,
        callback: Optional[Dispatcher] = None,
    ) -> Generator[Quote, None, None]:
        """Stream real-time quotes for a single symbol.

        Parameters
        ----------
        symbol : str
            Product symbol (e.g. "XAUUSD").
        callback : Dispatcher, optional
            If provided, each Quote is also dispatched to the registered handlers.

        Yields
        ------
        Quote
        """
        symbol = symbol.upper().strip()
        sse_url = f"{self.base_url}/api/stream/quote?symbol={symbol}"
        sse = SSEClient(sse_url, timeout=self.timeout)
        used_fallback = False
        try:
            for raw in sse.stream():
                if not raw:
                    continue
                import json
                try:
                    payload = json.loads(raw)
                    quote = Quote.from_api(payload)
                except Exception as e:
                    logger.warning(f"Failed to parse quote: {e}")
                    continue
                if callback:
                    callback.dispatch_quote(quote)
                yield quote
        except Exception as exc:
            logger.warning(f"SSE stream failed, falling back to polling: {exc}")
            used_fallback = True
            poller = self._poller_client()
            for quote in poller.poll_quote(symbol):
                if callback:
                    callback.dispatch_quote(quote)
                yield quote

    def quotes(
        self,
        symbols: Optional[str] = None,
        *,
        callback: Optional[Dispatcher] = None,
    ) -> Generator[QuoteSnapshot, None, None]:
        """Stream full-market snapshots continuously.

        Parameters
        ----------
        symbols : str, optional
            Comma-separated symbol filter.
        callback : Dispatcher, optional
            If provided, each QuoteSnapshot is also dispatched.

        Yields
        ------
        QuoteSnapshot
        """
        params = f"?symbols={symbols}" if symbols else ""
        sse_url = f"{self.base_url}/api/stream/quotes{params}"
        sse = SSEClient(sse_url, timeout=self.timeout)
        try:
            for raw in sse.stream():
                if not raw:
                    continue
                import json
                try:
                    payload = json.loads(raw)
                    snapshot = QuoteSnapshot.from_api(payload)
                except Exception as e:
                    logger.warning(f"Failed to parse snapshot: {e}")
                    continue
                if callback:
                    callback.dispatch_snapshot(snapshot)
                yield snapshot
        except Exception as exc:
            logger.warning(f"SSE snapshots failed, falling back to polling: {exc}")
            poller = self._poller_client()
            for snapshot in poller.poll_quotes(symbols=symbols):
                if callback:
                    callback.dispatch_snapshot(snapshot)
                yield snapshot

    def close(self) -> None:
        if self._sse is not None:
            self._sse.close()
            self._sse = None
        if self._poller is not None:
            self._poller.close()
            self._poller = None

    def __enter__(self) -> "SDStream":
        return self

    def __exit__(self, *_: Any) -> None:
        self.close()
