<?php

declare(strict_types=1);

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

use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Validator\Exception\ValidationException;
use App\Module\Commercial\Application\Service\SupplierFieldNormalizer;
use App\Module\Commercial\Domain\Entity\Supplier;
use App\Module\Commercial\Domain\Entity\SupplierContact;
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 fournisseur (M2,
 * spec-back § 4.5). Jumeau du ClientContactProcessor (M1), recentre sur le
 * perimetre ERP-88.
 *
 * Sequence :
 *  - POST / PATCH : rattachement au fournisseur parent, normalisation serveur
 *    (RG-2.12 : prenom/nom Title Case, telephones reduits aux chiffres, email
 *    lowercase) via le SupplierFieldNormalizer partage, puis validation RG-2.04
 *    (au moins prenom OU nom) avant persistance.
 *  - DELETE : aucune garde « dernier contact » au M2 — contrairement au M1, la
 *    collection peut rester vide cote back (RG-2.13 front-driven, spec § 4.5).
 *    Suppression physique directe.
 *
 * La security de l'operation (commercial.suppliers.manage) est appliquee par API
 * Platform en amont, de meme que la validation Symfony des contraintes d'attribut
 * (Assert\Email, Assert\Length...).
 *
 * @implements ProcessorInterface<SupplierContact, null|SupplierContact>
 */
final class SupplierContactProcessor 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 SupplierFieldNormalizer $normalizer,
        private readonly EntityManagerInterface $em,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof SupplierContact) {
            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 fournisseur parent de la sous-ressource POST
     * (/suppliers/{supplierId}/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 fournisseur
     * est deja present -> no-op.
     */
    private function linkParent(SupplierContact $contact, array $uriVariables): void
    {
        if (null !== $contact->getSupplier()) {
            return;
        }

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

        $supplier = $supplierId instanceof Supplier
            ? $supplierId
            : $this->em->getRepository(Supplier::class)->find($supplierId);

        // 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 supplier_id NOT NULL).
        if (!$supplier instanceof Supplier) {
            throw new NotFoundHttpException('Fournisseur introuvable.');
        }

        $contact->setSupplier($supplier);
    }

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

    /**
     * RG-2.04 : au moins le prenom OU le nom est obligatoire (double garde avec le
     * CHECK BDD chk_supplier_contact_name — leve une 422 propre rattachee au champ
     * `firstName` plutot qu'une 500 SQL). Joue apres normalisation, donc les
     * chaines vides sont deja ramenees a null.
     */
    private function validateName(SupplierContact $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);
        }
    }
}
