<?php

namespace apexl\Io\middleware\session;

use apexl\Config\Singleton;
use apexl\Io\includes\Hook;
use apexl\Io\includes\System;
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 apexl\Vault\Vault;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class sessionController implements MiddlewareInterface
{
    protected $globalData;
    protected $config;

    public function __construct(globalData $globalData, Singleton $config)
    {
        $this->globalData = $globalData;
        $this->config = $config;
    }

    public function process(Request $request, RequestHandler $handler): Response
    {
        // each client should remember their session id for EXACTLY the given time, or 1 hour.

        if (($requireSSL = ($this->config->app->requireSSL ?? true)) && !Host::isSecure()) {
            throw new \Exception("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.");
        }

        $authenticated = false;
        $userId = 0;
        // Check token validity
        if (!isset($this->config->app->jwt)) {
            Output::addSystemError("No JWT object defined in config.");
            $response = new \Slim\Psr7\Response();
            return System::asJson($response, ['message' => 'JWT config object required.'], 400);
        }
        list($token, $error) = currentUser::authenticateJWT($request, $this->config->app->jwt->secret_key, $this->config->app->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 (isset($this->config->app->needsAuth) && $this->config->app->needsAuth === true && !$authenticated) {
            //kill the request
            Output::addSystemError($error);
            $response = new \Slim\Psr7\Response();
            return System::asJson($response, ['message' => 'Authentication Required'], 403);
        }

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

        // Set userId in Vault for logging purposes
        $vault = Vault::getInstance();
        $vault->setLoggedInUserId($userId);

        Hook::processHook('postAuth', $userId);

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

    /**
     * Add the loaded user data to the global data object.
     * @param $uid
     * @throws \Exception
     */
    protected function addUserToGlobalData($uid)
    {
        $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);
    }
}
