"""Simple in-memory rate limiter for API endpoints.

Provides a Starlette middleware that limits the number of requests per client
IP within a sliding time window.  WebSocket upgrade requests and static file
paths are exempt so that real-time streams and assets are not throttled.

Integration
-----------
Add the middleware to the FastAPI app in ``app.py``::

    from ieum.server.rate_limit import RateLimitMiddleware

    app.add_middleware(RateLimitMiddleware, max_requests=100, window_seconds=60)
"""

from __future__ import annotations

import time
from collections import defaultdict

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


class RateLimitMiddleware(BaseHTTPMiddleware):
    """Per-IP sliding-window rate limiter.

    Args:
        app: The ASGI application to wrap.
        max_requests: Maximum number of requests allowed within the window.
        window_seconds: Duration of the sliding window in seconds.
    """

    def __init__(
        self,
        app,
        max_requests: int = 100,
        window_seconds: int = 60,
    ) -> None:
        super().__init__(app)
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self._requests: dict[str, list[float]] = defaultdict(list)

    async def dispatch(self, request: Request, call_next):
        # Skip rate limiting for WebSocket upgrades and static files
        if request.url.path.startswith(("/ws/", "/static/")):
            return await call_next(request)

        client_ip = request.client.host if request.client else "unknown"
        now = time.monotonic()

        # Evict timestamps outside the current window
        timestamps = [
            t
            for t in self._requests[client_ip]
            if now - t < self.window_seconds
        ]

        # Clean up empty entries to prevent memory leak from departed IPs
        if not timestamps:
            self._requests.pop(client_ip, None)
        else:
            self._requests[client_ip] = timestamps

        if len(timestamps) >= self.max_requests:
            return JSONResponse(
                {"error": "Rate limit exceeded"},
                status_code=429,
                headers={"Retry-After": str(self.window_seconds)},
            )

        self._requests[client_ip].append(now)
        return await call_next(request)
