<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\PersonBundle\Tests\PersonIdentifier\Validator;

use Chill\PersonBundle\Entity\Identifier\IdentifierPresenceEnum;
use Chill\PersonBundle\Entity\Identifier\PersonIdentifier;
use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition;
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface;
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierManagerInterface;
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker;
use Chill\PersonBundle\PersonIdentifier\Validator\RequiredIdentifierConstraint;
use Chill\PersonBundle\PersonIdentifier\Validator\RequiredIdentifierConstraintValidator;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Argument;

/**
 * @internal
 */
#[CoversClass(RequiredIdentifierConstraintValidator::class)]
final class RequiredIdentifierConstraintValidatorTest extends ConstraintValidatorTestCase
{
    use ProphecyTrait;

    private PersonIdentifierDefinition $requiredDefinition;

    protected function createValidator(): RequiredIdentifierConstraintValidator
    {
        $this->requiredDefinition = new PersonIdentifierDefinition(
            label: ['fr' => 'Identifiant requis'],
            engine: 'test.engine',
        );
        $this->requiredDefinition->setPresence(IdentifierPresenceEnum::REQUIRED);
        $reflection = new \ReflectionClass($this->requiredDefinition);
        $id = $reflection->getProperty('id');
        $id->setValue($this->requiredDefinition, 1);

        // Mock only the required methods of the engine used by the validator through the worker
        $engineProphecy = $this->prophesize(PersonIdentifierEngineInterface::class);
        $engineProphecy->isEmpty(Argument::type(PersonIdentifier::class))
            ->will(function (array $args): bool {
                /** @var PersonIdentifier $identifier */
                $identifier = $args[0];

                return '' === trim($identifier->getValue()['content'] ?? '');
            });
        $engineProphecy->renderAsString(Argument::any(), Argument::any())
            ->will(function (array $args): string {
                /** @var PersonIdentifier|null $identifier */
                $identifier = $args[0] ?? null;

                return $identifier?->getValue()['content'] ?? '';
            });

        $worker = new PersonIdentifierWorker($engineProphecy->reveal(), $this->requiredDefinition);

        // Mock only the required method used by the validator
        $managerProphecy = $this->prophesize(PersonIdentifierManagerInterface::class);
        $managerProphecy->getWorkers()->willReturn([$worker]);

        return new RequiredIdentifierConstraintValidator($managerProphecy->reveal());
    }

    public function testThrowsOnNonCollectionValue(): void
    {
        $this->expectException(UnexpectedValueException::class);
        $this->validator->validate(new \stdClass(), new RequiredIdentifierConstraint());
    }

    public function testThrowsOnInvalidConstraintType(): void
    {
        $this->expectException(UnexpectedTypeException::class);
        // Provide a valid Collection value so the type check reaches the constraint check
        $this->validator->validate(new ArrayCollection(), new NotBlank());
    }

    public function testNoViolationWhenRequiredIdentifierPresentAndNotEmpty(): void
    {
        $identifier = new PersonIdentifier($this->requiredDefinition);
        $identifier->setValue(['content' => 'ABC']);

        $collection = new ArrayCollection([$identifier]);

        $this->validator->validate($collection, new RequiredIdentifierConstraint());

        $this->assertNoViolation();
    }

    public function testViolationWhenRequiredIdentifierMissing(): void
    {
        $collection = new ArrayCollection();

        $this->validator->validate($collection, new RequiredIdentifierConstraint());

        $this->buildViolation('person_identifier.This identifier must be set')
            ->setParameter('{{ value }}', '')
            ->setParameter('definition_id', '1')
            ->setCode('c08b7b32-947f-11f0-8608-9b8560e9bf05')
            ->assertRaised();
    }

    public function testViolationWhenRequiredIdentifierIsEmpty(): void
    {
        $identifier = new PersonIdentifier($this->requiredDefinition);
        $identifier->setValue(['content' => '   ']);

        $collection = new ArrayCollection([$identifier]);

        $this->validator->validate($collection, new RequiredIdentifierConstraint());

        $this->buildViolation('person_identifier.This identifier must be set')
            ->setParameter('{{ value }}', '   ')
            ->setParameter('definition_id', '1')
            ->setCode('c08b7b32-947f-11f0-8608-9b8560e9bf05')
            ->assertRaised();
    }
}
