<?php

declare(strict_types=1);

namespace App\Module\Sites\Infrastructure\ApiPlatform\State\Processor;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Core\Domain\Entity\User;
use App\Module\Sites\Domain\Exception\SiteNotAuthorizedException;
use App\Module\Sites\Infrastructure\ApiPlatform\Resource\CurrentSiteResource;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\OptimisticLockException;
use LogicException;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * Processor de l'operation `PATCH /api/me/current-site`.
 *
 * Flux :
 *  1. Recupere l'user authentifie via Security.
 *  2. Extrait le site cible depuis la ressource denormalisee.
 *  3. Refresh de l'user depuis la BDD pour eliminer la race condition TOCTOU :
 *     si un autre thread a revoque le site entre le chargement de session et
 *     ce PATCH, le refresh garantit que hasSite() reflete l'etat reel en base.
 *  4. Valide que le site fait partie des `sites` de l'user — sinon leve
 *     SiteNotAuthorizedException convertie immediatement en 403.
 *  5. Positionne `currentSite`, flush, retourne l'user pour normalisation
 *     par API Platform via les groupes `me:read` (payload identique a /api/me).
 *
 * Les etapes 3-5 sont executees dans une meme transaction pour garantir
 * un rollback propre en cas d'erreur entre le refresh et le flush.
 *
 * @implements ProcessorInterface<CurrentSiteResource, User>
 */
final class CurrentSiteProcessor implements ProcessorInterface
{
    public function __construct(
        private readonly Security $security,
        private readonly EntityManagerInterface $entityManager,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof CurrentSiteResource) {
            throw new LogicException(sprintf(
                'CurrentSiteProcessor attend une instance de %s, %s recu.',
                CurrentSiteResource::class,
                get_debug_type($data),
            ));
        }

        $user = $this->security->getUser();
        if (!$user instanceof User) {
            // security: "is_granted('ROLE_USER')" sur l'operation doit deja
            // bloquer ce cas — garde defensive si la config change.
            throw new AccessDeniedHttpException('Authentification requise pour changer de site courant.');
        }

        $targetSite = $data->site;
        if (null === $targetSite) {
            throw new BadRequestHttpException('Le champ "site" est requis.');
        }

        // Refresh + switchCurrentSite + flush dans une transaction atomique.
        // Le refresh elimine la race condition TOCTOU : si un PATCH /rbac concurrent
        // a revoque le site de l'user entre le chargement de session et ici, le
        // refresh force un re-fetch de l'user et de sa collection de sites depuis
        // la BDD, garantissant que hasSite() reflete l'etat reel persisté.
        try {
            $this->entityManager->wrapInTransaction(function () use ($user, $targetSite): void {
                // Re-fetch de l'user + ses collections depuis la BDD (elimination TOCTOU).
                $this->entityManager->refresh($user);

                try {
                    $user->switchCurrentSite($targetSite);
                } catch (SiteNotAuthorizedException $e) {
                    // Traduction HTTP immediate (pas de listener kernel necessaire) :
                    // aligne sur le pattern RoleProcessor → SystemRoleDeletionException.
                    throw new AccessDeniedHttpException($e->getMessage(), $e);
                }

                $this->entityManager->flush();
            });
        } catch (OptimisticLockException $e) {
            // Protection future : si un champ @Version est ajoute sur User,
            // le conflit de version sera intercepte ici plutot que de remonter
            // comme une erreur generique.
            throw new BadRequestHttpException(
                'Conflit de version detecte lors du changement de site courant. Veuillez reessayer.',
                $e,
            );
        }

        return $user;
    }
}
