<?php

declare(strict_types=1);

namespace App\Module\Technique\Infrastructure\Security;

use App\Module\Sites\Application\Service\CurrentSiteProviderInterface;
use App\Module\Technique\Domain\Entity\Provider;
use App\Shared\Domain\Contract\SiteInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Decision centralisee du cloisonnement par site des prestataires (§ 2.13 /
 * RG-3.17). Source UNIQUE partagee par le ProviderProvider (liste + detail), le
 * provider decore des sous-ressources (ProviderSubResourceItemProvider) et les
 * processors d'ecriture des sous-ressources — afin d'eviter tout drift entre ces
 * points d'application.
 *
 * Regle : un user SANS `sites.bypass_scope` ET avec un site courant ne voit /
 * n'opere que sur les prestataires rattaches (relation directe provider.sites) a
 * son site courant. `bypass_scope` (Admin inclus via isAdmin) ou absence de site
 * courant (module Sites off / user sans currentSite) -> aucun cloisonnement
 * (no-op, aligne site-aware.md § 5).
 */
final class ProviderSiteScopeChecker
{
    public function __construct(
        private readonly Security $security,
        private readonly CurrentSiteProviderInterface $currentSiteProvider,
    ) {}

    /**
     * Site de cloisonnement a appliquer, ou null si aucun cloisonnement
     * (`bypass_scope`, ou pas de site courant resolu).
     */
    public function siteScopeOrNull(): ?SiteInterface
    {
        if ($this->security->isGranted('sites.bypass_scope')) {
            return null;
        }

        return $this->currentSiteProvider->get();
    }

    /**
     * Vrai si le prestataire est dans le perimetre site de l'user courant — ou si
     * aucun cloisonnement ne s'applique.
     */
    public function isInScope(Provider $provider): bool
    {
        $scopeSite = $this->siteScopeOrNull();
        if (null === $scopeSite) {
            return true;
        }

        return $this->providerHasSite($provider, (int) $scopeSite->getId());
    }

    /**
     * Leve un 404 si le prestataire est hors perimetre (anti-enumeration : ne pas
     * reveler l'existence d'une ligne hors site). No-op si dans le perimetre.
     */
    public function assertInScope(Provider $provider): void
    {
        if (!$this->isInScope($provider)) {
            throw new NotFoundHttpException('Prestataire introuvable.');
        }
    }

    /**
     * Vrai si le prestataire est rattache (relation directe provider.sites) au site
     * d'id donne. Comparaison en memoire sur l'entite deja chargee.
     */
    private function providerHasSite(Provider $provider, int $siteId): bool
    {
        foreach ($provider->getSites() as $site) {
            if ($site instanceof SiteInterface && $site->getId() === $siteId) {
                return true;
            }
        }

        return false;
    }
}
