<?php

declare(strict_types=1);

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

use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\Technique\Domain\Entity\Provider;
use App\Module\Technique\Domain\Entity\ProviderRib;
use App\Module\Technique\Infrastructure\Security\ProviderSiteScopeChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Processor d'ecriture de la sous-ressource RIB d'un prestataire (M3, spec-back
 * § 4.5). Jumeau du SupplierRibProcessor (M2), recentre sur le perimetre ERP-135.
 *
 * Sequence :
 *  - POST / PATCH : rattachement au prestataire parent. Aucune normalisation
 *    specifique ; la validite de l'IBAN et du BIC est garantie par Assert\Iban /
 *    Assert\Bic sur l'entite (jouees en amont par API Platform). Aucun
 *    #[AuditIgnore] sur iban/bic : la tracabilite comptable est volontaire
 *    (decision M1/M2 reportee, spec § 2.7).
 *  - DELETE : RG-3.08 — si le prestataire est en reglement LCR, la suppression de
 *    son DERNIER RIB est refusee (409), car LCR exige au moins un RIB.
 *
 * La security de l'operation (technique.providers.accounting.manage) est appliquee
 * par API Platform en amont : un utilisateur sans cette permission recoit 403 sur
 * POST/PATCH/DELETE avant d'atteindre ce processor — c'est le niveau de gating
 * renforce des donnees bancaires (distinct de manage, spec § 4.5).
 *
 * @implements ProcessorInterface<ProviderRib, null|ProviderRib>
 */
final class ProviderRibProcessor implements ProcessorInterface
{
    public function __construct(
        #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
        private readonly ProcessorInterface $persistProcessor,
        #[Autowire(service: 'api_platform.doctrine.orm.state.remove_processor')]
        private readonly ProcessorInterface $removeProcessor,
        private readonly EntityManagerInterface $em,
        private readonly ProviderSiteScopeChecker $scopeChecker,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof ProviderRib) {
            return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
        }

        if ($operation instanceof DeleteOperationInterface) {
            $this->guardLastRibDeletionUnderLcr($data);

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

        $this->linkParent($data, $uriVariables);

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

    /**
     * Rattache le RIB au prestataire parent de la sous-ressource POST
     * (/providers/{providerId}/ribs) : la relation n'est pas peuplee
     * automatiquement par le Link sur une ecriture. Sur PATCH, no-op.
     */
    private function linkParent(ProviderRib $rib, array $uriVariables): void
    {
        if (null !== $rib->getProvider()) {
            return;
        }

        $providerId = $uriVariables['providerId'] ?? null;
        if (null === $providerId) {
            return;
        }

        $provider = $providerId instanceof Provider
            ? $providerId
            : $this->em->getRepository(Provider::class)->find($providerId);

        // read:false sur le POST : sans stade lecture, un parent introuvable n'est
        // plus intercepte en amont -> 404 explicite (sinon 500 au persist sur la
        // contrainte provider_id NOT NULL).
        if (!$provider instanceof Provider) {
            throw new NotFoundHttpException('Prestataire introuvable.');
        }

        // Cloisonnement par site (§ 2.13 / RG-3.17) : interdiction de creer une
        // sous-ressource sur un prestataire hors du perimetre de l'user -> 404
        // (anti-enumeration, coherent avec le detail Provider garde en 404).
        $this->scopeChecker->assertInScope($provider);

        $rib->setProvider($provider);
    }

    /**
     * RG-3.08 : un prestataire dont le type de reglement est LCR doit conserver au
     * moins un RIB. La collection inclut le RIB en cours de suppression : un
     * effectif <= 1 signifie qu'il ne resterait aucun RIB -> 409. Pour tout autre
     * type de reglement, les RIBs sont optionnels (suppression libre).
     */
    private function guardLastRibDeletionUnderLcr(ProviderRib $rib): void
    {
        $provider = $rib->getProvider();
        if (null === $provider) {
            return;
        }

        if ('LCR' === $provider->getPaymentType()?->getCode() && $provider->getRibs()->count() <= 1) {
            throw new ConflictHttpException(
                'Impossible de supprimer le dernier RIB : le type de règlement LCR exige au moins un RIB.',
            );
        }
    }
}
