"""Per-client rate limiting middleware using in-memory sliding window.

No external dependencies — uses stdlib collections.deque for O(1)
append/popleft. State resets on server restart, which is fine for a
local single-instance proxy.
"""

from __future__ import annotations

import time
from collections import deque

import structlog
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse

from robot_resources.config import get_settings

logger = structlog.get_logger()

# Paths exempt from rate limiting (load balancer probes, health checks)
_EXEMPT_PATHS: frozenset[str] = frozenset({"/health"})


class RateLimitMiddleware(BaseHTTPMiddleware):
    """Sliding-window rate limiter keyed by client IP.

    Reads ``ROUTER_RATE_LIMIT_PER_MINUTE`` from Settings on each request.
    A value of ``0`` disables rate limiting entirely.
    """

    # Class-level state — shared across requests, reset on restart.
    # Using class-level so tests can call ``RateLimitMiddleware.reset()``.
    _requests: dict[str, deque[float]] = {}

    async def dispatch(self, request: Request, call_next):  # noqa: ANN001
        settings = get_settings()
        limit = settings.router_rate_limit_per_minute

        # 0 or negative = disabled
        if limit <= 0:
            return await call_next(request)

        # Exempt paths
        if request.url.path in _EXEMPT_PATHS:
            return await call_next(request)

        # Client identifier: IP address
        client_id = request.client.host if request.client else "unknown"

        now = time.monotonic()
        window_start = now - 60.0

        if client_id not in self._requests:
            self._requests[client_id] = deque()

        q = self._requests[client_id]

        # Evict expired entries
        while q and q[0] < window_start:
            q.popleft()

        if len(q) >= limit:
            retry_after = max(1, int(q[0] - window_start) + 1)
            logger.warning(
                "rate_limit_exceeded",
                client=client_id,
                limit=limit,
                retry_after=retry_after,
            )
            return JSONResponse(
                status_code=429,
                content={
                    "error": "Rate limit exceeded",
                    "detail": f"Maximum {limit} requests per minute",
                    "retry_after": retry_after,
                },
                headers={"retry-after": str(retry_after)},
            )

        q.append(now)
        return await call_next(request)

    @classmethod
    def reset(cls) -> None:
        """Clear all rate limit state. Used in tests."""
        cls._requests.clear()
