<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CmsBundle\Security;

use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Security\Exception\BlockedEmailException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\UriSigner;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;

class JsonLoginFailureHandler implements AuthenticationFailureHandlerInterface
{
    public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly UriSigner $uriSigner)
    {
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
    {
        $exception = $exception->getPrevious() ?? $exception;

        // SECURITY_REVIEW FP-023: BlockedEmailException is only raised in
        // UserChecker::checkPostAuth(), i.e. *after* the credentials have been
        // verified, so surfacing it is not a pre-auth enumeration oracle and the
        // dedicated UX (unsubscribe page) must be preserved.
        if ($exception instanceof BlockedEmailException && ($user = $exception->getUser()) && $user instanceof User) {
            return new JsonResponse([
                'error' => 'blocked_email',
                'redirect_to' => $this->uriSigner->sign($this->urlGenerator->generate('cms_blocked_email', [
                    'uuid' => $user->getUuid(),
                    'email' => $user->getEmail(),
                    'unsubscribed' => ($user->getBlockedContact()?->getCreatedAt() ?? new \DateTimeImmutable())->format('c'),
                    'valid' => new \DateTimeImmutable('+5 min')->format('c'),
                ], UrlGeneratorInterface::ABSOLUTE_URL)),
            ], JsonResponse::HTTP_UNAUTHORIZED);
        }

        // SECURITY_REVIEW FP-023: A DisabledException is thrown pre-auth (in
        // UserChecker::checkPreAuth(), before the password is verified), so
        // returning a distinct "account_disabled" response leaked whether an
        // account exists for the submitted address — an enumeration oracle.
        // Collapse all credential/disabled failures into one generic response
        // so a caller cannot distinguish "exists-but-disabled" from
        // "wrong credentials".
        return new JsonResponse(['error' => 'invalid_credentials'], JsonResponse::HTTP_UNAUTHORIZED);
    }
}
