<?php

declare(strict_types=1);

namespace App\Infrastructure\Http\Middleware;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
 * SEC_REVIEW F69: cap inbound request body size BEFORE Slim's
 * `BodyParsingMiddleware` reads it into memory.
 *
 * Two layers of check:
 *  1. `Content-Length` header — if it claims a size over the cap, we
 *     reject with 413 without touching the stream. The vast majority
 *     of clients send a correct Content-Length so this catches the
 *     common case cheaply.
 *  2. Stream-length fallback — if the request lies about
 *     Content-Length, or omits it (chunked), we ask the body's
 *     `getSize()`. Slim/Guzzle PSR-7 streams typically know their
 *     length when the body has already been buffered; if they don't,
 *     we let the request through and rely on PHP's `memory_limit` /
 *     downstream bounds.
 *
 * The middleware doesn't try to wrap the stream in a hard byte
 * counter — Slim's `BodyParsingMiddleware` reads the entire stream in
 * one call, so any caps we'd add inside the read loop wouldn't kick
 * in before the memory hit. Caddy's `request_body { max_size … }` is
 * the durable outer bound; this middleware is the application-layer
 * defence in depth that fail-closes when something gets through.
 */
final class RequestBodySizeLimitMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly ResponseFactoryInterface $responseFactory,
        private readonly int $maxBytes,
    ) {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $declared = $request->getHeaderLine('Content-Length');
        if ($declared !== '' && ctype_digit($declared) && (int) $declared > $this->maxBytes) {
            return $this->tooLarge();
        }

        $size = $request->getBody()->getSize();
        if ($size !== null && $size > $this->maxBytes) {
            return $this->tooLarge();
        }

        return $handler->handle($request);
    }

    private function tooLarge(): ResponseInterface
    {
        $response = $this->responseFactory->createResponse(413)
            ->withHeader('Content-Type', 'application/json');
        $response->getBody()->write((string) json_encode(['error' => 'payload_too_large']));

        return $response;
    }
}
