from __future__ import annotations

from collections import deque
from typing import Protocol


class MetricSample(Protocol):
    ts: float


class SlidingHistory:
    """Time-based sliding history for one monitored key."""

    def __init__(self, max_window_s: float) -> None:
        self.max_window_s = max_window_s
        self.samples: deque[MetricSample] = deque()

    def add(self, sample: MetricSample) -> None:
        """Append one sample and prune data outside the max window.

        Args:
            sample: New sample to append.
        """
        self.samples.append(sample)
        self._prune(sample.ts)

    def _prune(self, now: float) -> None:
        """Drop samples older than the configured max window.

        Args:
            now: Reference monotonic timestamp.
        """
        cutoff = now - self.max_window_s
        while self.samples and self.samples[0].ts < cutoff:
            self.samples.popleft()

    def latest(self) -> MetricSample | None:
        """Return the latest sample if available.

        Returns:
            The latest sample or None when history is empty.
        """
        if not self.samples:
            return None
        return self.samples[-1]

    def avg(self, metric: str, window_s: float, now: float) -> float:
        """Compute average of one metric within a time window.

        Args:
            metric: Sample attribute name to average.
            window_s: Time window in seconds.
            now: Reference monotonic timestamp.

        Returns:
            Window average value, or 0.0 when no value exists in window.
        """
        if not self.samples:
            return 0.0
        cutoff = now - window_s
        values = [
            value
            for s in self.samples
            if s.ts >= cutoff
            for value in [getattr(s, metric)]
            if isinstance(value, (int, float))
        ]
        if not values:
            return 0.0
        return float(sum(values) / len(values))
