<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\services;

use apexl\Io\exceptions\RecordNotFoundException;
use apexl\Io\modules\user\entities\refreshTokenEntity;
use apexl\Io\modules\user\entities\sessionEntity;
use apexl\Io\modules\user\exceptions\ClientCredentialsValidationException;
use apexl\Io\modules\user\exceptions\RefreshTokenUserMismatchException;
use Psr\Http\Message\ServerRequestInterface;

final class RefreshTokenSessionIdFinder
{
    private ServerRequestInterface $request;
    private refreshTokenEntity $refreshTokenEntity;

    public function __construct(
        private readonly ClientCredentialValidator $clientCredentialValidator
    ) {
    }

    /**
     * @throws RefreshTokenUserMismatchException
     * @throws ClientCredentialsValidationException
     * @throws RecordNotFoundException
     */
    public function find(ServerRequestInterface $request, refreshTokenEntity $refreshTokenEntity): ?string
    {
        $this->request = $request;
        $this->refreshTokenEntity = $refreshTokenEntity;
        if (currentUser::requestHasBearerAuthHeader($request)) {
            return $this->findAgainstAccessToken();
        } elseif (currentUser::requestHasBasicAuthHeader($request, $matches)) {
            return $this->findAgainstRefreshToken($matches[1]);
        }

        return null;
    }

    /**
     * @throws RefreshTokenUserMismatchException
     */
    private function findAgainstAccessToken(): string
    {
        $claims = currentUser::getClaimsFromJWT($this->request);

        if (isset($claims->userId) && $this->refreshTokenEntity->user_id === $claims->userId) {
            return $claims->sessionId;
        }

        throw new RefreshTokenUserMismatchException();
    }

    /**
     * @throws ClientCredentialsValidationException
     * @throws RecordNotFoundException
     */
    private function findAgainstRefreshToken(string $encodedBasicAuth): string
    {
        [$clientId, $clientSecret] = explode(':', base64_decode($encodedBasicAuth));
        $this->clientCredentialValidator->assertValid($clientId, $clientSecret);
        $sessionEntity = new sessionEntity();
        $sessionEntity->loadByUserId($this->refreshTokenEntity->user_id);

        return $sessionEntity->sessionId;
    }
}
