<?php

namespace App\Security;

use App\Repository\DeviceRepository;
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\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

final class DeviceAuthenticator extends AbstractAuthenticator
{
    public function __construct(
        private readonly DeviceRepository $devices,
        private readonly DeviceCredentialService $credentials,
        private readonly DeviceCookieFactory $cookies,
        private readonly EntityManagerInterface $entityManager,
    ) {
    }

    public function supports(Request $request): ?bool
    {
        return str_starts_with($request->getPathInfo(), '/api/v1/device/');
    }

    public function authenticate(Request $request): Passport
    {
        $rawToken = $request->cookies->get(DeviceCookieFactory::NAME);

        if (!is_string($rawToken) || '' === $rawToken) {
            throw new AuthenticationException('Terminal non reconnu.');
        }

        $now = new \DateTimeImmutable();
        $device = $this->devices->findUsableByCredentialDigest($this->credentials->credentialDigest($rawToken), $now);

        if (null === $device || !$device->isUsableAt($now)) {
            throw new AuthenticationException('Terminal révoqué ou expiré.');
        }

        $mustFlush = false;
        if (null === $device->getLastSeenAt() || $device->getLastSeenAt() < $now->modify('-1 day')) {
            $device->markSeen();
            $mustFlush = true;
        }

        if ($this->credentials->shouldRefresh($device, $now)) {
            $expiresAt = $this->credentials->nextCredentialExpiry();
            $device->refreshCredential($expiresAt);
            $request->attributes->set('_device_cookie_refresh', [$rawToken, $expiresAt]);
            $mustFlush = true;
        }

        if ($mustFlush) {
            $this->entityManager->flush();
        }

        return new SelfValidatingPassport(new UserBadge($device->getUserIdentifier(), static fn () => $device));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        $response = new JsonResponse(['error' => 'device_authentication_required'], Response::HTTP_UNAUTHORIZED);
        $response->headers->setCookie($this->cookies->expire());

        return $response;
    }
}
