<?php

declare(strict_types=1);

namespace App\Tests\Module\Transport\Api;

/**
 * Ecriture du formulaire principal transporteur (M4, WT4 — ERP-158). Couvre les
 * RG du CarrierProcessor + contraintes conditionnelles : RG-4.01 (QUALIMAT + cas
 * LIOT), RG-4.02 (AUTRE -> decharge), RG-4.03 (affrete -> indexation/benne/volume),
 * RG-4.12 (doublon de nom -> 409), RG-4.13 (normalisation), RG-4.14 (archivage +
 * mode strict). Jumeau des SupplierApiTest / SupplierPatchStrictTest (M2).
 *
 * @internal
 */
final class CarrierWriteApiTest extends AbstractCarrierApiTestCase
{
    /**
     * RG-4.01 : POST avec qualimatCarrier -> certificationType=QUALIMAT accepte et
     * FK persistee (verifiee au detail, qualimatCarrier embarque).
     */
    public function testPostQualimatPersistsCertificationAndForeignKey(): void
    {
        $client   = $this->createAdminClient();
        $qualimat = $this->seedQualimatCarrier('Transports Grelillier');

        $created = $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'name'              => 'Transports Grelillier',
                'qualimatCarrier'   => '/api/qualimat_carriers/'.$qualimat->getId(),
                'certificationType' => 'QUALIMAT',
                'isChartered'       => false,
            ],
        ])->toArray();

        self::assertResponseStatusCodeSame(201);
        self::assertSame('QUALIMAT', $created['certificationType']);

        $detail = $client->request('GET', $created['@id'], ['headers' => ['Accept' => self::LD]])->toArray();
        self::assertIsArray($detail['qualimatCarrier']);
        self::assertSame((int) $qualimat->getId(), (int) $detail['qualimatCarrier']['id']);
    }

    /**
     * RG-4.01 (cas LIOT) : nom = LIOT -> certificationType non requis (champ masque)
     * et liotPlates accepte (et normalise, RG-4.13).
     */
    public function testPostLiotAcceptsPlatesWithoutCertification(): void
    {
        $client = $this->createAdminClient();

        $created = $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'name'        => 'LIOT',
                'liotPlates'  => 'ab-123-cd ; ef-456-gh',
                'isChartered' => false,
            ],
        ])->toArray();

        self::assertResponseStatusCodeSame(201);
        self::assertNull($created['certificationType']);
        self::assertSame('AB-123-CD; EF-456-GH', $created['liotPlates']);
    }

    /**
     * RG-4.01 : hors cas LIOT, l'absence de certification est rejetee (422 cible
     * sur certificationType).
     */
    public function testPostWithoutCertificationOutsideLiotIsRejected(): void
    {
        $client = $this->createAdminClient();

        $response = $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['name' => 'Sans Certif', 'isChartered' => false],
        ]);

        self::assertResponseStatusCodeSame(422);
        self::assertViolationOnPath($response, 'certificationType');
    }

    /**
     * RG-4.02 : certificationType=AUTRE sans dischargeDocument -> 422 cible ; une
     * certification != AUTRE sans decharge passe (201).
     */
    public function testAutreCertificationRequiresDischarge(): void
    {
        $client = $this->createAdminClient();

        $response = $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['name' => 'Sans Decharge', 'certificationType' => 'AUTRE', 'isChartered' => false],
        ]);
        self::assertResponseStatusCodeSame(422);
        self::assertViolationOnPath($response, 'dischargeDocument');

        // Certification != AUTRE : pas de decharge requise.
        $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['name' => 'Avec GmpPlus', 'certificationType' => 'GMP_PLUS', 'isChartered' => false],
        ]);
        self::assertResponseStatusCodeSame(201);
    }

    /**
     * RG-4.03 : isChartered=true sans indexationRate / containerType / volumeM3 ->
     * 422 (violations ciblees) ; complet -> 201.
     */
    public function testCharteredRequiresIndexationContainerAndVolume(): void
    {
        $client = $this->createAdminClient();

        $response = $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['name' => 'Affrete Incomplet', 'certificationType' => 'GMP_PLUS', 'isChartered' => true],
        ]);
        self::assertResponseStatusCodeSame(422);
        self::assertViolationOnPath($response, 'indexationRate');
        self::assertViolationOnPath($response, 'containerType');
        self::assertViolationOnPath($response, 'volumeM3');

        $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'name'              => 'Affrete Complet',
                'certificationType' => 'GMP_PLUS',
                'isChartered'       => true,
                'indexationRate'    => '5.00',
                'containerType'     => 'BENNE',
                'volumeM3'          => '90.00',
            ],
        ]);
        self::assertResponseStatusCodeSame(201);
    }

    /**
     * RG-4.12 : nom deja pris (parmi actifs) -> 409. Le meme nom redevient
     * disponible apres archivage de l'ancien -> 201.
     */
    public function testDuplicateNameReturns409AndIsFreedAfterArchive(): void
    {
        $client   = $this->createAdminClient();
        $existing = $this->seedCarrier('Doublon Co');

        $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => $this->validMainPayload('Doublon Co'),
        ]);
        self::assertResponseStatusCodeSame(409);

        // Archivage de l'ancien -> le nom se libere (index partiel sur actifs).
        $client->request('PATCH', '/api/carriers/'.$existing->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['isArchived' => true],
        ]);
        self::assertResponseStatusCodeSame(200);

        $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => $this->validMainPayload('Doublon Co'),
        ]);
        self::assertResponseStatusCodeSame(201);
    }

    /**
     * RG-4.13 : le nom est persiste en MAJUSCULES (normalisation serveur).
     */
    public function testNameIsUpperCasedOnPersist(): void
    {
        $client = $this->createAdminClient();

        $created = $client->request('POST', '/api/carriers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => $this->validMainPayload('transports x'),
        ])->toArray();

        self::assertResponseStatusCodeSame(201);
        self::assertSame('TRANSPORTS X', $created['name']);
    }

    /**
     * RG-4.14 : PATCH isArchived=true par Admin -> 200 + archivedAt rempli ;
     * restauration -> archivedAt remis a null.
     */
    public function testAdminArchiveSetsArchivedAtAndRestoreClearsIt(): void
    {
        $client  = $this->createAdminClient();
        $carrier = $this->seedCarrier('A Archiver');

        $archived = $client->request('PATCH', '/api/carriers/'.$carrier->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['isArchived' => true],
        ])->toArray();
        self::assertResponseStatusCodeSame(200);
        self::assertTrue($archived['isArchived']);
        self::assertNotNull($archived['archivedAt']);

        $restored = $client->request('PATCH', '/api/carriers/'.$carrier->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['isArchived' => false],
        ])->toArray();
        self::assertResponseStatusCodeSame(200);
        self::assertFalse($restored['isArchived']);
        self::assertNull($restored['archivedAt']);
    }

    /**
     * RG-4.14 (mode strict) : une requete d'archivage ne peut modifier aucun autre
     * champ ecrivable -> 422.
     */
    public function testArchiveRequestMixingOtherFieldIsRejected(): void
    {
        $client  = $this->createAdminClient();
        $carrier = $this->seedCarrier('Strict Co');

        $client->request('PATCH', '/api/carriers/'.$carrier->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['isArchived' => true, 'name' => 'Renamed While Archiving'],
        ]);
        self::assertResponseStatusCodeSame(422);
    }
}
