"""Single sliding window rate limiter with per-client locking."""

from __future__ import annotations

from collections import deque
from dataclasses import dataclass, field
from threading import Lock


@dataclass(slots=True)
class _ClientState:
    """A client's request log and the lock that guards it.

    Keeping the lock and deque together in one object means a client always gets
    exactly one lock for its own deque -- they can never drift apart.
    """

    lock: Lock = field(default_factory=Lock)
    timestamps: deque[float] = field(default_factory=deque)


class SlidingWindowRateLimiter:
    """Exact per-client sliding window rate limiter with per-client locks.

    Each client has its own lock guarding its own request log, so requests from
    different clients are processed in parallel. A separate, very short-lived
    ``registry_lock`` makes "find-or-create this client's state" atomic, which is
    what keeps the shared ``clients`` dict safe under concurrency.
    """

    def __init__(self, limit: int, window_seconds: float) -> None:
        """Initialise the limiter.

        Args:
            limit (int): Maximum allowed requests within the window.
            window_seconds (float): Length of the rolling window in seconds.
        """
        self.limit = limit
        self.window_seconds = window_seconds
        self.clients: dict[str, _ClientState] = {}
        # Held only while looking up / creating an entry in ``clients`` -- never
        # while doing the per-client work. This is what protects the dict itself.
        self.registry_lock = Lock()

    def _state_for(self, client_id: str) -> _ClientState:
        """Return the state for ``client_id``, creating it once if needed.

        Args:
            client_id (str): The client identifier.

        Returns:
            _ClientState: The single shared state for this client.
        """
        with self.registry_lock:
            state = self.clients.get(client_id)
            if state is None:
                state = _ClientState()
                self.clients[client_id] = state
            return state

    def is_allowed(self, client_id: str, now: float) -> bool:
        """Decide whether a request from ``client_id`` is within its quota.

        Args:
            client_id (str): Identifier for the calling client.
            now (float): Current time in seconds.

        Returns:
            bool: ``True`` if the request is allowed, ``False`` if it is throttled.
        """
        state = self._state_for(client_id)
        cutoff = now - self.window_seconds

        # Only this client's lock is held here, so other clients run in parallel.
        with state.lock:
            timestamps = state.timestamps
            while timestamps and timestamps[0] <= cutoff:
                timestamps.popleft()

            allowed = len(timestamps) < self.limit
            if allowed:
                timestamps.append(now)

        return allowed
