"""Global request timeout middleware.

Wraps ``call_next()`` with ``asyncio.wait_for()`` to enforce a
server-level maximum request duration. Prevents slow retry+fallback
chains from hanging indefinitely.

Individual provider timeouts are shorter (e.g. 60s per call), but a
request that triggers retry → fallback → retry across multiple
providers could accumulate. This middleware caps total duration.
"""

from __future__ import annotations

import asyncio

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 the global timeout
_EXEMPT_PATHS: frozenset[str] = frozenset({"/health"})


class TimeoutMiddleware(BaseHTTPMiddleware):
    """Enforce a global request timeout.

    Reads ``ROUTER_REQUEST_TIMEOUT_SECONDS`` from Settings on each
    request.  A value of ``0`` disables the timeout entirely.
    """

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

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

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

        try:
            return await asyncio.wait_for(call_next(request), timeout=timeout)
        except asyncio.TimeoutError:
            logger.warning(
                "request_timeout",
                path=request.url.path,
                method=request.method,
                timeout_seconds=timeout,
            )
            return JSONResponse(
                status_code=504,
                content={
                    "error": "Gateway Timeout",
                    "detail": f"Request exceeded {timeout}s timeout",
                },
            )
