"""StreamingWindow: mutable rolling window for real-time bars."""

from __future__ import annotations

from collections import deque
from typing import Any

import numpy as np
from numpy.typing import NDArray

from pyalbedo.core.types import OHLCV
from pyalbedo.core.window import MarketDataWindow


class StreamingWindow:
    """
    Mutable rolling window of OHLCV bars for real-time use.

    Fixed max size (e.g. required_lookback + buffer). Push bars with push(bar);
    expose .open, .high, .low, .close, .volume (and optional .timestamps) as numpy
    arrays over the current window so the same signal generators can run on it.
    """

    def __init__(
        self,
        symbol: str,
        max_size: int,
        *,
        store_timestamps: bool = False,
    ) -> None:
        self.symbol = symbol
        self.max_size = max_size
        self.store_timestamps = store_timestamps
        self._bars: deque[OHLCV] = deque(maxlen=max_size)
        self._timestamps: deque[Any] = deque(maxlen=max_size) if store_timestamps else deque()

    def push(self, bar: OHLCV, timestamp: Any = None) -> None:
        """Append one bar (and optional timestamp). Drops oldest if at max_size."""
        self._bars.append(bar)
        if self.store_timestamps:
            self._timestamps.append(timestamp if timestamp is not None else len(self._bars) - 1)

    def push_ohlcv(
        self,
        open_: float,
        high: float,
        low: float,
        close: float,
        volume: float,
        timestamp: Any = None,
    ) -> None:
        """Append one bar from components."""
        self.push(OHLCV(open=open_, high=high, low=low, close=close, volume=volume), timestamp=timestamp)

    def __len__(self) -> int:
        return len(self._bars)

    def _to_array(self, attr: str) -> NDArray[np.float64]:
        return np.array([getattr(b, attr) for b in self._bars], dtype=np.float64)

    @property
    def open(self) -> NDArray[np.float64]:
        return self._to_array("open")

    @property
    def high(self) -> NDArray[np.float64]:
        return self._to_array("high")

    @property
    def low(self) -> NDArray[np.float64]:
        return self._to_array("low")

    @property
    def close(self) -> NDArray[np.float64]:
        return self._to_array("close")

    @property
    def volume(self) -> NDArray[np.float64]:
        return self._to_array("volume")

    @property
    def timestamps(self) -> NDArray[Any] | None:
        if not self.store_timestamps or not self._timestamps:
            return None
        return np.array(list(self._timestamps))

    @property
    def last_timestamp(self) -> Any:
        if self.store_timestamps and self._timestamps:
            return self._timestamps[-1]
        return len(self._bars) - 1 if self._bars else None

    def to_market_data_window(self) -> MarketDataWindow:
        """View current window as a MarketDataWindow for generators."""
        return MarketDataWindow(
            symbol=self.symbol,
            open=self.open,
            high=self.high,
            low=self.low,
            close=self.close,
            volume=self.volume,
            timestamps=self.timestamps,
        )
