"""Request validation middleware to enforce basic HTTP hygiene."""

from __future__ import annotations

from typing import Iterable

from fastapi import Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp


class InputValidationMiddleware(BaseHTTPMiddleware):
    """Perform lightweight input validation before hitting route handlers."""

    SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}

    def __init__(
        self,
        app: ASGIApp,
        *,
        allowed_content_types: Iterable[str] | None = None,
        max_query_params: int = 50,
        max_query_length: int = 2048,
    ) -> None:
        super().__init__(app)
        self.allowed_content_types = {
            ct.lower() for ct in (allowed_content_types or {
                "application/json",
                "application/x-www-form-urlencoded",
                "multipart/form-data",
            })
        }
        self.max_query_params = max_query_params
        self.max_query_length = max_query_length

    async def dispatch(self, request: Request, call_next):
        if len(request.query_params) > self.max_query_params:
            return JSONResponse(
                status_code=400,
                content={
                    "detail": "Too many query parameters",
                    "max_allowed": self.max_query_params,
                },
            )

        if len(request.url.query) > self.max_query_length:
            return JSONResponse(
                status_code=414,
                content={
                    "detail": "Query string too long",
                    "max_allowed": self.max_query_length,
                },
            )

        if request.method not in self.SAFE_METHODS:
            content_type = request.headers.get("content-type", "")
            normalized = content_type.split(";")[0].strip().lower()
            if normalized and normalized not in self.allowed_content_types:
                return JSONResponse(
                    status_code=415,
                    content={
                        "detail": "Unsupported content type",
                        "allowed": sorted(self.allowed_content_types),
                    },
                )

        return await call_next(request)
