<?php

declare(strict_types=1);

namespace App\Tests\Module\Sites\Domain\Entity;

use App\Module\Sites\Domain\Entity\Site;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
 * Tests de validation de l'entite Site : contraintes scalaires (regex couleur
 * hex, regex code postal FR, NotBlank, Length) ET unicite du nom. Utilise le
 * validator applicatif (via KernelTestCase) afin que la contrainte
 * UniqueEntity, adossee a Doctrine, puisse reellement interroger la base de
 * test.
 *
 * @internal
 */
final class SiteValidationTest extends KernelTestCase
{
    private ValidatorInterface $validator;
    private EntityManagerInterface $em;

    protected function setUp(): void
    {
        self::bootKernel();
        $container = self::getContainer();

        /** @var ValidatorInterface $validator */
        $validator       = $container->get(ValidatorInterface::class);
        $this->validator = $validator;

        /** @var EntityManagerInterface $em */
        $em       = $container->get(EntityManagerInterface::class);
        $this->em = $em;
    }

    protected function tearDown(): void
    {
        // Liberation explicite des handles pour eviter les fuites inter-tests
        // (pattern recommande par Symfony lorsque l'on capture le container).
        $this->em->clear();

        parent::tearDown();
    }

    public function testValidSitePassesValidation(): void
    {
        $site       = $this->makeSite();
        $violations = $this->validator->validate($site);

        self::assertCount(0, $violations, (string) $violations);
    }

    public function testValidSiteWithComplementPassesValidation(): void
    {
        $site       = $this->makeSite(complement: 'Batiment C');
        $violations = $this->validator->validate($site);

        self::assertCount(0, $violations, (string) $violations);
    }

    #[DataProvider('invalidColorProvider')]
    public function testColorMustBeHexRrggbb(string $color): void
    {
        $site       = $this->makeSite(color: $color);
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count(), sprintf('La couleur "%s" devrait etre rejetee.', $color));
    }

    /**
     * @return iterable<string, array{string}>
     */
    public static function invalidColorProvider(): iterable
    {
        yield 'nom CSS' => ['red'];

        yield 'hex court' => ['#FFF'];

        yield 'hex sans diese' => ['FFFFFF'];

        yield 'rgb()' => ['rgb(255, 0, 0)'];

        yield 'hex trop long' => ['#1234567'];

        yield 'caractere non hex' => ['#12345G'];

        yield 'vide' => [''];
    }

    #[DataProvider('validColorProvider')]
    public function testValidColorsAreAccepted(string $color): void
    {
        $site       = $this->makeSite(color: $color);
        $violations = $this->validator->validate($site);

        self::assertCount(0, $violations, sprintf('La couleur "%s" devrait etre acceptee.', $color));
    }

    /**
     * @return iterable<string, array{string}>
     */
    public static function validColorProvider(): iterable
    {
        yield 'majuscules' => ['#ABCDEF'];

        yield 'minuscules' => ['#abcdef'];

        yield 'mixte' => ['#0a1B2c'];

        yield 'noir' => ['#000000'];

        yield 'blanc' => ['#FFFFFF'];
    }

    #[DataProvider('invalidPostalCodeProvider')]
    public function testPostalCodeMustMatchFrFormat(string $postalCode): void
    {
        $site       = $this->makeSite(postalCode: $postalCode);
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count(), sprintf('Le CP "%s" devrait etre rejete.', $postalCode));
    }

    /**
     * @return iterable<string, array{string}>
     */
    public static function invalidPostalCodeProvider(): iterable
    {
        yield 'trop court' => ['1234'];

        yield 'trop long' => ['123456'];

        yield 'alphanumerique' => ['8610A'];

        yield 'avec tiret' => ['86-100'];

        yield 'vide' => [''];

        yield 'avec espace' => ['86 100'];
    }

    #[DataProvider('validPostalCodeProvider')]
    public function testValidPostalCodesAreAccepted(string $postalCode): void
    {
        $site       = $this->makeSite(postalCode: $postalCode);
        $violations = $this->validator->validate($site);

        self::assertCount(0, $violations, (string) $violations);
    }

    /**
     * @return iterable<string, array{string}>
     */
    public static function validPostalCodeProvider(): iterable
    {
        yield 'metropole' => ['86100'];

        yield 'paris' => ['75001'];

        yield 'dom' => ['97100'];

        yield 'corse' => ['20000'];
    }

    public function testBlankNameIsRejected(): void
    {
        $site       = $this->makeSite(name: '');
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count());
    }

    public function testBlankStreetIsRejected(): void
    {
        $site       = $this->makeSite(street: '');
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count());
    }

    public function testBlankCityIsRejected(): void
    {
        $site       = $this->makeSite(city: '');
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count());
    }

    public function testNameLongerThan100CharsIsRejected(): void
    {
        $site       = $this->makeSite(name: str_repeat('a', 101));
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count());
    }

    public function testCityLongerThan100CharsIsRejected(): void
    {
        $site       = $this->makeSite(city: str_repeat('a', 101));
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count());
    }

    public function testStreetLongerThan255CharsIsRejected(): void
    {
        $site       = $this->makeSite(street: str_repeat('a', 256));
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count());
    }

    public function testComplementLongerThan255CharsIsRejected(): void
    {
        $site       = $this->makeSite(complement: str_repeat('a', 256));
        $violations = $this->validator->validate($site);

        self::assertGreaterThan(0, $violations->count());
    }

    /**
     * Verifie que la contrainte UniqueEntity(name) est effectivement appliquee
     * par le validator Symfony (via le validateur Doctrine sous-jacent).
     *
     * Le test est auto-suffisant : il persiste lui-meme un site porteur d'un
     * nom unique, puis tente de valider un second Site avec le meme nom. Le
     * site cree est supprime en fin de test pour ne pas laisser de trace
     * inter-tests (pattern transactionnel non utilise ici car un seul test
     * persiste, un cleanup explicite suffit).
     */
    public function testDuplicateNameIsRejected(): void
    {
        $name     = 'Test-Duplicate-'.uniqid('', true);
        $original = $this->makeSite(name: $name);
        $this->em->persist($original);
        $this->em->flush();

        try {
            $duplicate  = $this->makeSite(name: $name, city: 'Autre');
            $violations = $this->validator->validate($duplicate);

            self::assertGreaterThan(0, $violations->count(), 'Un site homonyme doit lever au moins une violation.');

            // Assertion precise : on veut s'assurer que la violation levee
            // est bien UniqueEntity sur `name`, pas une autre contrainte
            // qui passerait par hasard (matching de message trop laxe).
            $found = false;
            foreach ($violations as $violation) {
                if (UniqueEntity::NOT_UNIQUE_ERROR === $violation->getCode()
                    && 'name' === $violation->getPropertyPath()) {
                    $found = true;

                    break;
                }
            }

            self::assertTrue($found, 'Violation UniqueEntity(name) attendue (code NOT_UNIQUE_ERROR sur property `name`).');
        } finally {
            $this->em->remove($original);
            $this->em->flush();
        }
    }

    /**
     * Helper : construit un Site valide avec un nom unique, sur lequel on
     * peut superposer un seul champ invalide pour tester une contrainte.
     */
    private function makeSite(
        ?string $name = null,
        string $street = '1 rue Test',
        ?string $complement = null,
        string $postalCode = '12345',
        string $city = 'Poitiers',
        string $color = '#000000',
    ): Site {
        return new Site(
            $name ?? 'Test-'.uniqid('', true),
            $street,
            $complement,
            $postalCode,
            $city,
            $color,
        );
    }
}
