"""A tiny in-process sliding-window rate limiter.

Generic groundwork: keyed by an arbitrary string (e.g. a client IP), so it can throttle any
endpoint. Single-worker uvicorn → in-memory state is sufficient; it resets on restart, which
is fine for abuse throttling (not for anything that must survive a deploy). Not shared across
processes — if we ever run multiple workers, swap the backing store for Redis behind the same
``allow()`` interface.
"""
import threading
import time


class RateLimiter:
    def __init__(self, max_events: int, window_seconds: float):
        self.max_events = max_events
        self.window = window_seconds
        self._hits: dict[str, list[float]] = {}
        self._lock = threading.Lock()

    def allow(self, key: str) -> bool:
        """Record an attempt for ``key`` and return whether it is within the limit.

        True  -> under the cap (the attempt is counted).
        False -> the cap for the current window is already reached (attempt NOT counted, so a
                 blocked caller can't keep pushing the window forward)."""
        now = time.monotonic()
        cutoff = now - self.window
        with self._lock:
            hits = [t for t in self._hits.get(key, []) if t > cutoff]
            if len(hits) >= self.max_events:
                self._hits[key] = hits
                return False
            hits.append(now)
            self._hits[key] = hits
            # Opportunistic cleanup so abandoned keys don't accumulate unboundedly.
            if len(self._hits) > 4096:
                for k in list(self._hits):
                    fresh = [t for t in self._hits[k] if t > cutoff]
                    if fresh:
                        self._hits[k] = fresh
                    else:
                        del self._hits[k]
            return True
