<?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 Serializer\Normalizer;

use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonAltName;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\PersonBundle\Serializer\Normalizer\PersonJsonNormalizer;
use Chill\PersonBundle\PersonIdentifier\Rendering\PersonIdRenderingInterface;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
 * @internal
 *
 * @covers \Chill\PersonBundle\Serializer\Normalizer\PersonJsonNormalizer
 */
final class PersonJsonNormalizerTest extends TestCase
{
    use ProphecyTrait;

    public function testSupportsNormalization(): void
    {
        $normalizer = $this->createNormalizer();

        self::assertTrue($normalizer->supportsNormalization(new Person(), 'json'));
        self::assertFalse($normalizer->supportsNormalization(new \stdClass(), 'json'));
        self::assertFalse($normalizer->supportsNormalization(new Person(), 'xml'));
    }

    public function testNormalizeWithMinimalGroupReturnsOnlyBaseKeys(): void
    {
        $person = $this->createSamplePerson();

        $normalizer = $this->createNormalizer();
        $data = $normalizer->normalize($person, 'json', [AbstractNormalizer::GROUPS => 'minimal']);

        // Expected base keys
        $expectedKeys = [
            'type',
            'id',
            'text',
            'textAge',
            'firstName',
            'lastName',
            'current_household_address',
            'birthdate',
            'deathdate',
            'age',
            'phonenumber',
            'mobilenumber',
            'email',
            'gender',
            'civility',
            'personId',
        ];

        foreach ($expectedKeys as $key) {
            self::assertArrayHasKey($key, $data, sprintf('Key %s should be present', $key));
        }
        self::assertSame('PERSON-ID-RENDER', $data['personId']);

        // Ensure extended keys are not present in minimal mode
        foreach (['centers', 'altNames', 'current_household_id', 'current_residential_addresses'] as $key) {
            self::assertArrayNotHasKey($key, $data, sprintf('Key %s should NOT be present in minimal group', $key));
        }
    }

    public function testNormalizeWithoutGroupsIncludesExtendedKeys(): void
    {
        $person = $this->createSamplePerson(withAltNames: true);

        $center1 = (new Center())->setName('c1');
        $center2 = (new Center())->setName('c2');


        $normalizer = $this->createNormalizer(
            centers: [$center1, $center2],
            currentResidentialAddresses: [['addr' => 1]],
        );

        $data = $normalizer->normalize($person, 'json');

        // Base keys
        $baseKeys = [
            'type', 'id', 'text', 'textAge', 'firstName', 'lastName', 'current_household_address', 'birthdate', 'deathdate', 'age', 'phonenumber', 'mobilenumber', 'email', 'gender', 'civility', 'personId',
        ];
        foreach ($baseKeys as $key) {
            self::assertArrayHasKey($key, $data, sprintf('Key %s should be present', $key));
        }

        // Extended keys
        foreach (['centers', 'altNames', 'current_household_id', 'current_residential_addresses'] as $key) {
            self::assertArrayHasKey($key, $data, sprintf('Key %s should be present', $key));
        }

        self::assertSame(['c1', 'c2'], $data['centers']);
        self::assertIsArray($data['altNames']);
        self::assertSame([['key' => 'aka', 'label' => 'Johnny']], $data['altNames']);
        self::assertNull($data['current_household_id'], 'No household set so id should be null');
        self::assertSame([['addr' => 1]], $data['current_residential_addresses']);
    }

    private function createNormalizer(array $centers = [], array $currentResidentialAddresses = []): PersonJsonNormalizer
    {
        $render = $this->prophesize(ChillEntityRenderExtension::class);
        $render->renderString(Argument::type(Person::class), ['addAge' => false])->willReturn('John Doe');
        $render->renderString(Argument::type(Person::class), ['addAge' => true])->willReturn('John Doe (25)');

        $centerResolver = $this->prophesize(CenterResolverManagerInterface::class);
        $centerResolver->resolveCenters(Argument::type(Person::class))->willReturn($centers);

        $raRepo = $this->prophesize(ResidentialAddressRepository::class);
        $raRepo->findCurrentResidentialAddressByPerson(Argument::type(Person::class))->willReturn($currentResidentialAddresses);

        $phoneHelper = $this->prophesize(PhoneNumberHelperInterface::class);

        $personIdRendering = $this->prophesize(PersonIdRenderingInterface::class);
        $personIdRendering->renderPersonId(Argument::type(Person::class))->willReturn('PERSON-ID-RENDER');

        $normalizer = new PersonJsonNormalizer(
            $render->reveal(),
            $centerResolver->reveal(),
            $raRepo->reveal(),
            $phoneHelper->reveal(),
            $personIdRendering->reveal(),
        );

        // Inner normalizer that echoes values or simple conversions
        $inner = new class () implements NormalizerInterface {
            public function supportsNormalization($data, $format = null): bool
            {
                return true;
            }

            public function normalize($object, $format = null, array $context = [])
            {
                // For scalars and arrays, return as-is; for objects, return string or id when possible
                if (\is_scalar($object) || null === $object) {
                    return $object;
                }
                if ($object instanceof \DateTimeInterface) {
                    return $object->format('Y-m-d');
                }
                if ($object instanceof Center) {
                    return $object->getName();
                }
                if (is_array($object)) {
                    return array_map(fn ($o) => $this->normalize($o, $format, $context), $object);
                }

                // default stub
                return (string) (method_exists($object, 'getId') ? $object->getId() : 'normalized');
            }
        };

        $normalizer->setNormalizer($inner);

        return $normalizer;
    }

    private function createSamplePerson(bool $withAltNames = false): Person
    {
        $p = new Person();
        $p->setFirstName('John');
        $p->setLastName('Doe');
        $p->setBirthdate(new \DateTime('2000-01-01'));
        $p->setEmail('john@example.test');

        if ($withAltNames) {
            $alt = new PersonAltName();
            $alt->setKey('aka');
            $alt->setLabel('Johnny');
            $p->setAltNames(new ArrayCollection([$alt]));
        }

        return $p;
    }
}
