<?php

namespace apexl\Io\middleware\session;

use apexl\Io\includes\HookManager;
use apexl\Io\modules\user\entities\sessionEntity;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\services\currentUser;
use apexl\Io\services\GlobalData;
use apexl\Io\services\Output;
use apexl\Utils\Urls\Host;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Exception\HttpBadRequestException;
use Slim\Exception\HttpUnauthorizedException;

final readonly class SessionMiddleware implements MiddlewareInterface
{
    public final const string HOOK__POST_AUTH = 'postAuth';

    public function __construct(
        private HookManager $hookManager,
        private GlobalData $globalData,
        private Output $output,
    ) {}

    /**
     * @throws Exception
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // each client should remember their session id for EXACTLY the given time, or 1 hour.
        if (config('app.requires_ssl') && !Host::isSecure()) {
            throw new Exception(
                <<<MESSAGE
                You must either set app.requireSSL to FALSE in config (And use a browser other than Chrome),
                or run this under SSL. NOTE for SSL is required even when tested for chrome as new versions
                of chrome will block non-SSL cookies on cross Site Requests.
                MESSAGE
            );
        }

        $authenticated = false;
        $userId = 0;
        // Check token validity
        $config = config('auth.jwt');
        if (!isset($config)) {
            $this->output->addSystemError("No JWT object defined in config.");

            throw new HttpBadRequestException($request, 'JWT config object required');
        }

        [$token, $error] = currentUser::authenticateJWT(
            $request,
            config('auth.jwt.secret_key'),
            config('auth.jwt.algorithm'),
        );
        if ($token) {
            $session = new sessionEntity();
            $session->load($token->sessionId, true);
            if (($session->sessionId ?? null) !== null && $session->active && $session->uid == $token->userId) {
                $authenticated = true;
                $userId = $session->uid;
            }
        }

        //not logged in? we need to check if we need to be authenticated or not.
        if (config('app.requires_auth') === true && !$authenticated) {
            //kill the request
            $this->output->addSystemError($error);

            throw new HttpUnauthorizedException($request, 'Authentication Required',);
        }

        $request = $request->withAttribute('user', $userId);
        $request = $request->withAttribute('authenticated', $authenticated);
        $this->addUserToGlobalData($userId);
        $this->globalData->addData('session', ['authenticated' => $authenticated]);

        $this->hookManager->processHook(self::HOOK__POST_AUTH, $userId);

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

    /**
     * Add the loaded user data to the global data object.
     * @param $uid
     * @throws Exception
     */
    protected function addUserToGlobalData($uid): void
    {
        $user = new userEntity();
        //Add the object and id to the array to allow a load to be performed just before the data is returned to ensure its up-to-date.
        $this->globalData->addData('entities', ['user' => ['entity' => $user, 'id' => $uid]], true, true);
    }
}
