<?php

declare(strict_types=1);

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

use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Validator\Exception\ValidationException;
use App\Module\Transport\Domain\Entity\Carrier;
use App\Module\Transport\Domain\Entity\CarrierAddress;
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 Adresse d'un transporteur (M4,
 * spec-back § 4.5). Jumeau du SupplierAddressProcessor (M2) / ProviderAddressProcessor
 * (M3), recentre sur le perimetre ERP-159, AVEC une garde propre au M4 : RG-4.05
 * (adresse obligatoire si le transporteur est affrete).
 *
 * Sequence :
 *  - POST / PATCH : rattachement au transporteur parent (linkParent) puis garde
 *    RG-4.05 (guardCharteredAddress). RG-4.06 (code postal, Assert\Regex) est portee
 *    par l'entite et jouee par API Platform AVANT ce processor.
 *  - DELETE : aucune regle metier specifique (suppression physique directe).
 *
 * RG-4.05 vit ICI (et non en Assert\Callback sur l'entite) car elle depend du
 * transporteur PARENT, indisponible a la validation Symfony sur un POST
 * sous-ressource en read:false (le parent n'est rattache qu'au stade processor).
 * La violation est construite a la main avec le meme rendu Hydra que les
 * contraintes Symfony, donc consommable inline par champ (convention ERP-101).
 *
 * RG-4.07 (masquage du bouton « Valider » si QUALIMAT) est purement front : le
 * back accepte le PATCH normalement, aucune garde ici.
 *
 * La security d'operation (transport.carriers.manage) est appliquee par API
 * Platform en amont, de meme que la validation Symfony des contraintes d'attribut.
 *
 * @implements ProcessorInterface<CarrierAddress, null|CarrierAddress>
 */
final class CarrierAddressProcessor 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,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof CarrierAddress) {
            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->guardCharteredAddress($data);

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

    /**
     * Rattache l'adresse au transporteur parent de la sous-ressource POST
     * (/carriers/{carrierId}/addresses) : la relation n'est pas peuplee
     * automatiquement par le Link sur une ecriture. Sur PATCH, no-op.
     */
    private function linkParent(CarrierAddress $address, array $uriVariables): void
    {
        if (null !== $address->getCarrier()) {
            return;
        }

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

        $carrier = $carrierId instanceof Carrier
            ? $carrierId
            : $this->em->getRepository(Carrier::class)->find($carrierId);

        // 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 carrier_id NOT NULL).
        if (!$carrier instanceof Carrier) {
            throw new NotFoundHttpException('Transporteur introuvable.');
        }

        $address->setCarrier($carrier);
    }

    /**
     * RG-4.05 : si le transporteur parent est affrete (isChartered), l'adresse doit
     * porter Pays / Code postal / Ville / Adresse. Chaque champ manquant -> une
     * violation 422 sur son propre propertyPath (mapping inline ERP-101). La
     * validation porte sur l'ETAT RESULTANT de l'adresse (apres application du
     * payload), donc identique sur POST et sur PATCH partiel. Sans affretement,
     * l'adresse reste partielle (champs nullable, RG-4.06 inchangee).
     */
    private function guardCharteredAddress(CarrierAddress $address): void
    {
        $carrier = $address->getCarrier();
        if (!$carrier instanceof Carrier || !$carrier->isChartered()) {
            return;
        }

        $required = [
            'country'    => [$address->getCountry(), 'Le pays est obligatoire pour un transporteur affrété.'],
            'postalCode' => [$address->getPostalCode(), 'Le code postal est obligatoire pour un transporteur affrété.'],
            'city'       => [$address->getCity(), 'La ville est obligatoire pour un transporteur affrété.'],
            'street'     => [$address->getStreet(), 'L\'adresse est obligatoire pour un transporteur affrété.'],
        ];

        $violations = new ConstraintViolationList();
        foreach ($required as $path => [$value, $message]) {
            if (null === $value || '' === trim($value)) {
                $violations->add(new ConstraintViolation($message, null, [], $address, $path, $value));
            }
        }

        if (0 < $violations->count()) {
            throw new ValidationException($violations);
        }
    }
}
