<?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\Application\Service\CurrentSiteProviderInterface;
use App\Module\Sites\Domain\Entity\Site;
use App\Shared\Domain\Contract\SiteAwareInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * Decorator du processor de persistance Doctrine d'API Platform qui injecte
 * automatiquement le site courant de l'utilisateur sur les entites
 * implementant SiteAwareInterface, si le payload ne precise pas de site.
 *
 * S'applique a TOUTES les operations POST/PATCH qui deleguent au persist
 * processor natif. Les processors custom qui appellent
 * `$this->persistProcessor->process()` (pattern UserRbacProcessor) passent
 * aussi par ce decorator, transparent pour les entites non-SiteAware.
 *
 * Comportement :
 *  - $data pas SiteAware -> delegation directe (no-op).
 *  - $data SiteAware avec site deja positionne, appelant a `sites.bypass_scope`
 *    -> delegation directe (ex: admin qui cree une entite dans un autre site).
 *  - $data SiteAware avec site deja positionne, appelant SANS `sites.bypass_scope`
 *    -> validation que le site precise appartient aux sites autorises de l'user.
 *    Si non, leve AccessDeniedHttpException (cross-site write interdite).
 *  - $data SiteAware sans site, provider retourne un Site -> injection
 *    puis delegation.
 *  - $data SiteAware sans site, provider retourne null -> throw 400
 *    BadRequestHttpException avec message explicite.
 *
 * Volontairement HTTP-only : ne couvre pas les persistances hors API
 * Platform (fixtures, commandes CLI, imports). Ces contextes doivent
 * positionner le site explicitement — c'est assume dans la doc
 * d'adoption (`docs/modules/site-aware.md`).
 *
 * @implements ProcessorInterface<mixed|SiteAwareInterface, mixed>
 */
#[AsDecorator('api_platform.doctrine.orm.state.persist_processor')]
final class SiteAwareInjectionProcessor implements ProcessorInterface
{
    public function __construct(
        private readonly ProcessorInterface $inner,
        private readonly CurrentSiteProviderInterface $currentSiteProvider,
        private readonly Security $security,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if ($data instanceof SiteAwareInterface) {
            if (null !== $data->getSite()) {
                // Le payload precise un site explicite : on valide que le site
                // appartient aux sites autorises de l'utilisateur courant, sauf
                // si celui-ci dispose de la permission `sites.bypass_scope`
                // (ex: admin effectuant une operation cross-site).
                if (!$this->security->isGranted('sites.bypass_scope')) {
                    $user         = $this->security->getUser();
                    $explicitSite = $data->getSite();
                    // hasSite() attend un Site concret. Si l'agent entity fait
                    // evoluer la signature vers SiteInterface, le instanceof
                    // reste valide (Site implemente SiteInterface) et le cast
                    // disparaitra naturellement lors du prochain nettoyage.
                    if ($user instanceof User && $explicitSite instanceof Site && !$user->hasSite($explicitSite)) {
                        throw new AccessDeniedHttpException(
                            'Le site specifie n\'est pas dans les sites autorises pour cet utilisateur.'
                        );
                    }
                }
            } else {
                // Aucun site dans le payload : injection automatique depuis le
                // site courant de l'utilisateur.
                $currentSite = $this->currentSiteProvider->get();

                if (null === $currentSite) {
                    throw new BadRequestHttpException(
                        'Impossible de creer l\'enregistrement : aucun site selectionne.',
                    );
                }

                $data->setSite($currentSite);
            }
        }

        return $this->inner->process($data, $operation, $uriVariables, $context);
    }
}
