<?php

declare(strict_types=1);

namespace App\Infrastructure\Http\Middleware;

use App\Domain\Auth\AuthenticatedPrincipal;
use App\Domain\Auth\Role;
use App\Domain\Auth\TokenKind;
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;

/**
 * Route-attached RBAC gate. Use as `RbacMiddleware::require($container, Role::Operator)`
 * — returns a middleware instance bound to that required role.
 *
 * Reporter and consumer tokens have no role and *must not* reach
 * admin/auth routes; for them this middleware returns 401 ("unauthorized")
 * — matching the SPEC rule that "wrong kind for the route" looks the same
 * as a missing or revoked token. 403 is reserved for "authenticated, role
 * is too low for this endpoint."
 */
final class RbacMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly Role $required,
        private readonly ResponseFactoryInterface $responseFactory,
    ) {
    }

    public static function require(ResponseFactoryInterface $factory, Role $required): self
    {
        return new self($required, $factory);
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $principal = $request->getAttribute(TokenAuthenticationMiddleware::ATTR_PRINCIPAL);
        if (!$principal instanceof AuthenticatedPrincipal) {
            return $this->jsonError(401, 'unauthorized');
        }

        // Reporter / consumer tokens never satisfy admin-route role checks.
        if ($principal->tokenKind === TokenKind::Reporter || $principal->tokenKind === TokenKind::Consumer) {
            return $this->jsonError(401, 'unauthorized');
        }

        if ($principal->role === null) {
            // Service token without impersonation should already have been
            // 400'd by ImpersonationMiddleware; defensive 403 here.
            return $this->jsonError(403, 'forbidden');
        }

        if (!$principal->role->satisfies($this->required)) {
            return $this->jsonError(403, 'forbidden');
        }

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

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

        return $response;
    }
}
