<?php

declare(strict_types=1);

namespace App\Tests\Module\Catalog\Api;

use App\Module\Catalog\Domain\Entity\Category;

/**
 * Tests des regles de validation POST/PATCH sur Category :
 *  - RG-1.02 : `name` obligatoire (NotBlank) ;
 *  - RG-1.03 : `name` trim cote serveur via CategoryProcessor ;
 *  - RG-1.04 : `name` longueur 2..120 (Length) ;
 *  - RG-1.05 : `categoryType` obligatoire ;
 *  - RG-1.06 : `categoryType` doit pointer un type existant.
 *
 * @internal
 */
final class CategoryValidationTest extends AbstractCatalogApiTestCase
{
    // ============ RG-1.02 — name NotBlank ============

    public function testNameRequiredReturns422(): void
    {
        $type   = $this->createCategoryType();
        $client = $this->createAdminClient();
        $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'categoryType' => '/api/category_types/'.$type->getId(),
                // name absent
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    public function testNameEmptyStringReturns422(): void
    {
        $type   = $this->createCategoryType();
        $client = $this->createAdminClient();
        $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'         => '',
                'categoryType' => '/api/category_types/'.$type->getId(),
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    public function testNameWhitespaceOnlyReturns422(): void
    {
        // Le Processor trim avant la validation : "   " devient "" -> NotBlank
        // doit declencher 422 (RG-1.02 combinee a RG-1.03).
        $type   = $this->createCategoryType();
        $client = $this->createAdminClient();
        $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'         => '   ',
                'categoryType' => '/api/category_types/'.$type->getId(),
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    // ============ RG-1.03 — name trim cote serveur ============

    public function testNameIsTrimmedOnCreate(): void
    {
        $type        = $this->createCategoryType();
        $client      = $this->createAdminClient();
        $payloadName = '  '.self::TEST_CATEGORY_PREFIX.'trim  ';
        $expected    = trim($payloadName);

        $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'         => $payloadName,
                'categoryType' => '/api/category_types/'.$type->getId(),
            ],
        ]);

        self::assertResponseStatusCodeSame(201);

        // Verification cote base : la valeur stockee est trimee.
        $em = $this->getEm();
        $em->clear();
        $stored = $em->getRepository(Category::class)->findOneBy(['name' => $expected]);
        self::assertNotNull($stored, 'La categorie trimee doit etre persistee sous "'.$expected.'"');
        self::assertSame($expected, $stored->getName());
    }

    // ============ RG-1.04 — longueur 2..120 ============

    public function testNameTooShortReturns422(): void
    {
        $type   = $this->createCategoryType();
        $client = $this->createAdminClient();
        $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'         => 'A',
                'categoryType' => '/api/category_types/'.$type->getId(),
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    public function testNameTooLongReturns422(): void
    {
        $type   = $this->createCategoryType();
        $client = $this->createAdminClient();
        $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'         => str_repeat('a', 121),
                'categoryType' => '/api/category_types/'.$type->getId(),
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    public function testNameAtMaxLengthIs201(): void
    {
        // Borne haute : 120 caracteres doit passer (l'index est sur LOWER, name
        // est unique en collision avec d'autres tests donc on prefixe la marque
        // test_cat_ pour le cleanup et completons jusqu'a 120 caracteres).
        $prefix = self::TEST_CATEGORY_PREFIX;
        $name   = $prefix.str_repeat('z', 120 - strlen($prefix));
        self::assertSame(120, strlen($name));

        $type   = $this->createCategoryType();
        $client = $this->createAdminClient();
        $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'         => $name,
                'categoryType' => '/api/category_types/'.$type->getId(),
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
    }

    // ============ RG-1.05 — categoryType obligatoire ============

    public function testCategoryTypeRequiredReturns422(): void
    {
        $client = $this->createAdminClient();
        $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name' => self::TEST_CATEGORY_PREFIX.'no_type',
                // categoryType absent
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    public function testCategoryTypeNullIsRejected(): void
    {
        // `categoryType: null` echoue a la deserialization IRI (API Platform
        // renvoie 400) bien avant la validation Assert\NotNull. La spec § 4.3
        // accepte les deux : on assert le contrat fort "ne passe pas en BDD".
        $client   = $this->createAdminClient();
        $response = $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'         => self::TEST_CATEGORY_PREFIX.'null_type',
                'categoryType' => null,
            ],
        ]);

        self::assertContains(
            $response->getStatusCode(),
            [400, 422],
            'categoryType=null doit etre rejete (400 deserialization ou 422 validation).',
        );
    }

    // ============ RG-1.06 — categoryType doit exister ============

    public function testCategoryTypeMustExistReturns4xx(): void
    {
        // IRI vers un id qui n'existe pas. API Platform peut renvoyer 400
        // (resolution IRI echouee) ou 422 (validation NotNull declenchee).
        // La spec § 4.3 accepte les deux : on assert le contrat "ne passe pas".
        $client   = $this->createAdminClient();
        $response = $client->request('POST', '/api/categories', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'         => self::TEST_CATEGORY_PREFIX.'ghost_type',
                'categoryType' => '/api/category_types/9999999',
            ],
        ]);

        self::assertContains(
            $response->getStatusCode(),
            [400, 404, 422],
            'IRI categoryType inexistante doit etre rejetee (400/404/422 selon API Platform).',
        );
    }
}
