<?php

namespace apexl\Io\modules\user\Middleware;

use apexl\Io\includes\RouteManager;
use apexl\Io\modules\display\Controllers\DisplayController;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\enums\permissions\Common;
use apexl\Io\modules\user\services\CurrentUserFactory;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Psr7\Factory\StreamFactory;
use Slim\Routing\RouteContext;

final readonly class PermissionMiddleware implements MiddlewareInterface
{
    public function __construct(
        private StreamFactory $streamFactory,
        private RouteManager $routeManager,
        private CurrentUserFactory $currentUserFactory,
    ) {}

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): Response
    {
        //always allow OPTIONS
        $method = $request->getMethod();
        if ($method == 'OPTIONS') {
            return $handler->handle($request);
        }

        $user = $this->currentUserFactory->get() ?? new userEntity();
        // retrieve the configured permission value
        $routeContext = RouteContext::fromRequest($request);
        $route = $routeContext->getRoute();
        $permission = $route->getArgument('permission');
        $permission = $permission ? unserialize($permission) : Common::ALLOW_ALL;

        //check if the user accessing this route has permission to do so.
        if (!$user->isAllowed($permission)) {
            $response = new \Slim\Psr7\Response(403);
            $callable = $route->getCallable();
            if ($this->shouldRedirectToLogin($callable)) {
                return $response
                    ->withHeader('Content-Type', 'application/json')
                    ->withBody($this->streamFactory->createStream(json_encode([
                        'redirect' => $this->routeManager->getRoutePattern('user.display.login'),
                    ])));
            }

            return $response;
        }

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

    private function shouldRedirectToLogin(callable|array|string $callable): bool
    {
        return (is_string($callable) && is_subclass_of($callable, DisplayController::class))
            || (is_callable($callable) && $callable instanceof DisplayController);
    }
}
