<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\controllers\twoFactorAuth;

use apexl\Io\includes\Controller;
use apexl\Io\modules\user\interfaces\SessionManagerInterface;
use apexl\Io\modules\user\Middleware\TwoFactorMiddleware;
use apexl\Io\modules\user\Request\TwoFactorCodeRequest;
use apexl\Io\modules\user\services\CurrentUserFactory;
use apexl\Io\modules\user\services\twoFactorAuth\TwoFactorAuthService;
use JsonException;
use Psr\Http\Message\ResponseInterface;
use Throwable;

/**
 * POST /api/v1/user/action/two-factor-auth/verify-recovery-code  { code }
 *
 * Lost-device path: burns a one-time recovery code and, on success, marks the
 * session 2FA-passed. The frontend should then prompt the user to re-enrol a
 * fresh method. Guarded by IS_LOGGED_IN.
 */
final readonly class VerifyRecoveryCodeController extends Controller
{
    /**
     * @throws JsonException
     */
    public function __invoke(
        TwoFactorCodeRequest $request,
        ResponseInterface $response,
        CurrentUserFactory $currentUserFactory,
        TwoFactorAuthService $twoFactorService,
        SessionManagerInterface $sessionManager,
    ): ResponseInterface {
        $user = $currentUserFactory->get();
        if ($user === null || !$user->id) {
            return $this->json($response, ['error' => 'Not authenticated'], 401);
        }

        if (!$twoFactorService->consumeRecoveryCode($user, $request->code())) {
            $this->output->addResponse($request, ['error' => 'Invalid or already-used recovery code'], false);

            return $this->json($response, [], 422);
        }

        $this->markSessionPassed($sessionManager);

        $this->output->addResponse($request, [
            'two_factor_passed' => true,
            // Hint for the frontend: a burned code should prompt re-enrolment.
            'should_reenrol' => true,
        ]);

        return $this->json($response);
    }

    private function markSessionPassed(SessionManagerInterface $sessionManager): void
    {
        try {
            $sessionManager->addDataToCurrentSession([TwoFactorMiddleware::SESSION_PASSED_KEY => true]);
        } catch (Throwable) {
            // Non-fatal; the next request re-challenges.
        }
    }
}
