<?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 ApiPlatform\Validator\Exception\ValidationException;
use App\Module\Technique\Application\Service\ProviderFieldNormalizer;
use App\Module\Technique\Domain\Entity\Provider;
use App\Module\Technique\Domain\Entity\ProviderContact;
use App\Module\Technique\Infrastructure\Security\ProviderSiteScopeChecker;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;

/**
 * Processor d'ecriture de la sous-ressource Contact d'un prestataire (M3,
 * spec-back § 4.5). Jumeau du SupplierContactProcessor (M2), recentre sur le
 * perimetre ERP-135.
 *
 * Sequence :
 *  - POST / PATCH : rattachement au prestataire parent, normalisation serveur
 *    (RG-3.11 : prenom/nom Title Case, telephones reduits aux chiffres, email
 *    lowercase) via le ProviderFieldNormalizer partage, puis validation RG-3.04
 *    (au moins un champ parmi prenom / nom / telephone principal / email) avant
 *    persistance.
 *  - DELETE : aucune garde « dernier contact » au M3 — la collection peut rester
 *    vide cote back (RG-3.12 front-driven, spec § 4.5). Suppression physique directe.
 *
 * La security de l'operation (technique.providers.manage) est appliquee par API
 * Platform en amont, de meme que la validation Symfony des contraintes d'attribut
 * (Assert\Email, Assert\Length...).
 *
 * @implements ProcessorInterface<ProviderContact, null|ProviderContact>
 */
final class ProviderContactProcessor 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 ProviderFieldNormalizer $normalizer,
        private readonly EntityManagerInterface $em,
        private readonly ProviderSiteScopeChecker $scopeChecker,
    ) {}

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

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

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

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

    /**
     * Rattache le contact au prestataire parent de la sous-ressource POST
     * (/providers/{providerId}/contacts). La relation n'est pas peuplee
     * automatiquement par le Link sur une operation d'ecriture : on resout le
     * parent depuis l'uri variable. Sur PATCH (entite existante), le prestataire
     * est deja present -> no-op.
     */
    private function linkParent(ProviderContact $contact, array $uriVariables): void
    {
        if (null !== $contact->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);

        $contact->setProvider($provider);
    }

    /**
     * Normalisation serveur (RG-3.11). Toutes les methodes du normalizer sont
     * null-safe : une chaine vide apres trim devient null.
     */
    private function normalize(ProviderContact $contact): void
    {
        $contact->setFirstName($this->normalizer->normalizePersonName($contact->getFirstName()));
        $contact->setLastName($this->normalizer->normalizePersonName($contact->getLastName()));
        $contact->setJobTitle($this->normalizer->normalizeText($contact->getJobTitle()));
        $contact->setPhonePrimary($this->normalizer->normalizePhone($contact->getPhonePrimary()));
        $contact->setPhoneSecondary($this->normalizer->normalizePhone($contact->getPhoneSecondary()));
        $contact->setEmail($this->normalizer->normalizeEmail($contact->getEmail()));
    }

    /**
     * RG-3.04 : un bloc Contact exige au moins le prenom OU le nom (aligne sur le
     * M1/M2 — un contact se materialise par son nom ; fonction / telephone / email
     * seuls ne suffisent pas). Double garde avec le CHECK BDD chk_provider_contact_name
     * — leve une 422 propre rattachee au champ `firstName` plutot qu'une 500 SQL.
     * Joue apres normalisation (les chaines vides sont deja ramenees a null).
     */
    private function validateName(ProviderContact $contact): void
    {
        if (null === $contact->getFirstName() && null === $contact->getLastName()) {
            $violations = new ConstraintViolationList();
            $violations->add(new ConstraintViolation(
                'Le prénom ou le nom du contact est obligatoire.',
                null,
                [],
                $contact,
                'firstName',
                null,
            ));

            throw new ValidationException($violations);
        }
    }
}
