<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\services;

use apexl\Io\modules\user\entities\RefreshToken;
use apexl\Io\modules\user\exceptions\JwtException;
use apexl\Io\modules\user\exceptions\NoSessionInRefreshTokenException;
use apexl\Io\modules\user\exceptions\RefreshTokenUserMismatchException;
use apexl\Io\modules\user\Factories\JwtFromRequestServiceFactory;
use Firebase\JWT\ExpiredException;
use Psr\Http\Message\ServerRequestInterface;

final readonly class RefreshTokenSessionIdFinder
{
    public function __construct(
        private JwtFromRequestServiceFactory $jwtFromRequestServiceFactory,
    ) {}

    /**
     * @throws RefreshTokenUserMismatchException
     * @throws JwtException
     * @throws NoSessionInRefreshTokenException
     */
    public function find(ServerRequestInterface $request, RefreshToken $refreshTokenEntity): ?string
    {
        try {
            return $this->findAgainstAccessToken($request, $refreshTokenEntity);
        } catch (JwtException $jwtException) {
            $previous = $jwtException->getPrevious();
            if ($previous instanceof ExpiredException) {
                return $this->findInPayload($previous->getPayload(), $refreshTokenEntity);
            }

            throw $jwtException;
        }
    }

    /**
     * @throws RefreshTokenUserMismatchException
     * @throws NoSessionInRefreshTokenException
     * @throws JwtException
     */
    private function findAgainstAccessToken(ServerRequestInterface $request, RefreshToken $refreshTokenEntity): string
    {
        $token = $this->jwtFromRequestServiceFactory->make($request)->token();

        return $this->findInPayload($token->getPayload(), $refreshTokenEntity);
    }

    /**
     * @throws RefreshTokenUserMismatchException
     * @throws NoSessionInRefreshTokenException
     */
    private function findInPayload(object $payload, RefreshToken $refreshTokenEntity): string
    {
        if (isset($payload->userId) && $refreshTokenEntity->user_id === $payload->userId) {
            if (isset($payload->sessionId)) {
                return $payload->sessionId;
            }

            throw new NoSessionInRefreshTokenException();
        }

        throw new RefreshTokenUserMismatchException();
    }
}
