<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\controllers;

use apexl\Io\exceptions\RecordNotFoundException;
use apexl\Io\includes\Controller;
use apexl\Io\modules\user\Actions\RefreshAction;
use apexl\Io\modules\user\exceptions\JwtException;
use apexl\Io\modules\user\exceptions\NoSessionInRefreshTokenException;
use apexl\Io\modules\user\exceptions\RefreshTokenExpiredException;
use apexl\Io\modules\user\exceptions\RefreshTokenUserMismatchException;
use apexl\Io\modules\user\Request\RefreshRequest;
use apexl\Io\modules\user\services\RefreshTokenEntityFinder;
use apexl\Io\modules\user\services\RefreshTokenSessionIdFinder;
use Exception;
use JsonException;
use Psr\Http\Message\ResponseInterface;

final readonly class RefreshController extends Controller
{
    /**
     * @throws JsonException
     */
    public function __invoke(
        RefreshRequest $request,
        ResponseInterface $response,
        RefreshAction $refreshAction,
        RefreshTokenSessionIdFinder $sessionIdFinder,
        RefreshTokenEntityFinder $refreshTokenEntityFinder,
        RefreshTokenSessionIdFinder $refreshTokenSessionIdFinder,
    ): ResponseInterface {
        try {
            $refreshTokenEntity = $refreshTokenEntityFinder->fromToken($request->refreshToken());
            $sessionId = $refreshTokenSessionIdFinder->find($request, $refreshTokenEntity);

            // Rotation (opt-in): handle() may return a fresh refresh-token value
            // alongside the new access token. When rotation is off it returns
            // null and the response stays exactly as before (access_token only).
            [$newAuthToken, $newRefreshToken] = $refreshAction->handle($request->refreshToken(), $sessionId);

            $responseData = ['access_token' => $newAuthToken];
            if ($newRefreshToken !== null) {
                $responseData['refresh_token'] = $newRefreshToken;
            }

            $this->output->addResponse($request, $responseData);

            return $this->json($response);
        } catch (
            // Everything here means "this caller cannot prove who they are":
            // the refresh token has expired, belongs to someone else, or the
            // access token needed to recover the session id is missing /
            // unreadable / has no session in it. A missing Authorization header
            // surfaces as JwtException and used to fall through to the generic
            // handler below, answering a plain unauthenticated call with a 500.
        RefreshTokenUserMismatchException|
        RefreshTokenExpiredException|
        NoSessionInRefreshTokenException|
        JwtException
        $exception
        ) {
            return $this->json(
                $response,
                ['error' => $exception->getMessage()],
                401,
            );
        } catch (RecordNotFoundException $exception) {
            return $this->json(
                $response,
                ['error' => $exception->getMessage()],
                404,
            );
        } catch (Exception $exception) {
            return $this->json(
                $response,
                ['error' => $exception->getMessage()],
                500,
            );
        }
    }

}
