"""Strategy runner: wires stream + strategy together."""

from __future__ import annotations

import threading
from typing import Any, Callable, Optional

from sd_api.logging import get_logger
from sd_api.models.quote import Quote
from sd_api.strategy.base import BaseStrategy
from sd_api.strategy.context import StrategyContext

logger = get_logger("strategy.runner")


class StrategyRunner:
    """Wires an SDStream and a BaseStrategy together.

    Usage::

        from sd_api import SDClient, SDStream
        from sd_api.strategy import BaseStrategy, StrategyRunner

        class MyStrategy(BaseStrategy):
            def on_quote(self, quote, ctx):
                print(f"{quote.symbol}: {quote.bid}/{quote.ask}")

        client = SDClient("http://127.0.0.1:5000")
        stream = SDStream("http://127.0.0.1:5000")
        runner = StrategyRunner(client=client, stream=stream, strategy=MyStrategy())

        # Run for a single symbol
        runner.run_quote("XAUUSD")

        # Or run for full market snapshots
        runner.run_snapshot(symbols=None)
    """

    def __init__(
        self,
        client: Any,
        stream: Any,
        strategy: BaseStrategy,
        store: Any = None,
    ) -> None:
        self.client = client
        self.stream = stream
        self.strategy = strategy
        self.store = store
        self._stop_event = threading.Event()
        self._ctx = StrategyContext(
            client=client,
            store=store,
        )
        self.strategy.on_init(self._ctx)

    def stop(self) -> None:
        """Signal the runner to stop gracefully."""
        self._stop_event.set()
        self.stream.close()

    def _is_stopped(self) -> bool:
        return self._stop_event.is_set()

    def run_quote(
        self,
        symbol: str,
        *,
        max_iterations: int = 0,
    ) -> None:
        """Run the strategy on a single-symbol stream.

        Parameters
        ----------
        symbol : str
            Product symbol (e.g. "XAUUSD").
        max_iterations : int
            Stop after this many quotes. 0 means run forever (until stop()).
        """
        self._ctx.metadata["symbol"] = symbol.upper().strip()
        self._stop_event.clear()
        self.strategy.on_start(self._ctx)
        count = 0

        try:
            for quote in self.stream.quote(symbol):
                if self._is_stopped():
                    break
                try:
                    self.strategy.on_quote(quote, self._ctx)
                except Exception as exc:
                    logger.warning(f"Strategy.on_quote error: {exc}")
                count += 1
                if max_iterations > 0 and count >= max_iterations:
                    break
        finally:
            self.strategy.on_stop(self._ctx)

    def run_snapshot(
        self,
        symbols: Optional[str] = None,
        *,
        max_iterations: int = 0,
    ) -> None:
        """Run the strategy on full-market snapshots.

        Parameters
        ----------
        symbols : str, optional
            Comma-separated symbol filter.
        max_iterations : int
            Stop after this many snapshots. 0 means run forever.
        """
        self._stop_event.clear()
        self.strategy.on_start(self._ctx)
        count = 0

        try:
            for snapshot in self.stream.quotes(symbols=symbols):
                if self._is_stopped():
                    break
                try:
                    self.strategy.on_snapshot(snapshot, self._ctx)
                except Exception as exc:
                    logger.warning(f"Strategy.on_snapshot error: {exc}")
                count += 1
                if max_iterations > 0 and count >= max_iterations:
                    break
        finally:
            self.strategy.on_stop(self._ctx)
