<?php

namespace App\Security;

use App\Entity\User;
use App\Repository\UserRepository;
use App\Service\EmailDomainPolicy;
use App\Service\OtpManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\TooManyLoginAttemptsAuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;

/**
 * Authentificateur OTP : traite POST /api/auth/verify.
 * Le code est vérifié via CustomCredentials (phase CheckPassportEvent), ce qui laisse
 * le login_throttling du firewall protéger contre le brute-force du code à 6 chiffres.
 * L'utilisateur n'est persisté qu'en cas de succès (pas sur les tentatives échouées).
 * Port de app/api/auth/verify/route.ts.
 */
final class OtpAuthenticator extends AbstractAuthenticator
{
    /** @var list<string> */
    private array $adminEmails;

    public function __construct(
        private readonly OtpManager $otp,
        private readonly EmailDomainPolicy $domainPolicy,
        private readonly UserRepository $users,
        private readonly EntityManagerInterface $em,
        #[\Symfony\Component\DependencyInjection\Attribute\Autowire('%env(ADMIN_EMAILS)%')]
        string $adminEmails = '',
    ) {
        $this->adminEmails = array_values(array_filter(array_map(
            static fn (string $e) => strtolower(trim($e)),
            explode(',', $adminEmails)
        )));
    }

    public function supports(Request $request): bool
    {
        return $request->isMethod('POST') && '/api/auth/verify' === $request->getPathInfo();
    }

    public function authenticate(Request $request): Passport
    {
        $data = json_decode($request->getContent(), true);
        $email = strtolower(trim((string) (\is_array($data) ? ($data['email'] ?? '') : '')));
        $code = trim((string) (\is_array($data) ? ($data['code'] ?? '') : ''));

        if ('' === $email || '' === $code) {
            throw new CustomUserMessageAuthenticationException('Email et code requis');
        }

        if (!$this->domainPolicy->isAllowed($email)) {
            throw new CustomUserMessageAuthenticationException('Email non autorisé');
        }

        // L'utilisateur est résolu (ou instancié neuf) mais PAS persisté ici.
        $userBadge = new UserBadge($email, fn (string $id): User => $this->users->findOneByEmail($id) ?? new User($id));

        // Le code est vérifié pendant CheckPassportEvent → le throttling s'intercale avant.
        $credentials = new CustomCredentials(
            function (string $code, UserInterface $user): bool {
                if (!$this->otp->verify($user->getUserIdentifier(), $code)) {
                    throw new CustomUserMessageAuthenticationException('Code invalide ou expiré');
                }

                return true;
            },
            $code
        );

        return new Passport($userBadge, $credentials);
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): Response
    {
        // Crée l'utilisateur en base s'il n'existait pas encore (1re connexion réussie).
        $user = $token->getUser();
        if ($user instanceof User && null === $user->getId()) {
            $email = strtolower($user->getUserIdentifier());
            if (\in_array($email, $this->adminEmails, true)) {
                // Bootstrap : les emails de ADMIN_EMAILS sont admin actifs d'office (pas d'onboarding).
                $user->setGroup(User::ROLE_ADMIN)
                    ->setStatus(User::STATUS_ACTIVE)
                    ->setValidatedAt(new \DateTimeImmutable());
            } else {
                // Nouvel utilisateur : doit choisir son groupe + ses concessions (cf. /bienvenue).
                $user->setStatus(User::STATUS_ONBOARDING);
            }
            $this->em->persist($user);
            $this->em->flush();
        }

        return new JsonResponse(['success' => true, 'email' => $token->getUserIdentifier()]);
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
    {
        if ($exception instanceof TooManyLoginAttemptsAuthenticationException) {
            return new JsonResponse(
                ['error' => 'Trop de tentatives. Réessayez dans quelques minutes.'],
                Response::HTTP_TOO_MANY_REQUESTS
            );
        }

        return new JsonResponse(['error' => $exception->getMessageKey()], Response::HTTP_UNAUTHORIZED);
    }
}
