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

use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\Common\Collections\Selectable;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
 * @internal
 *
 * @coversNothing
 */
final class DocGenObjectNormalizerTest extends KernelTestCase
{
    private NormalizerInterface $normalizer;

    protected function setUp(): void
    {
        self::bootKernel();

        $this->normalizer = self::getContainer()->get(NormalizerInterface::class);
    }

    public function testChangeContextOnAttribute()
    {
        $object = new TestableParentClass();
        $actual = $this->normalizer->normalize(
            $object,
            'docgen',
            ['groups' => 'docgen:read']
        );

        $this->assertIsArray($actual);
        $this->assertArrayHasKey('child', $actual);
        $this->assertIsArray($actual['child']);
        $this->assertArrayHasKey('foo', $actual['child']);
        $this->assertEquals('bar', $actual['child']['foo']);
        $this->assertArrayNotHasKey('baz', $actual['child']);

        // test with child = null
        $object->child = null;
        $actual = $this->normalizer->normalize(
            $object,
            'docgen',
            ['groups' => 'docgen:read']
        );
        $this->assertIsArray($actual);
        $this->assertArrayHasKey('child', $actual);
        $this->assertIsArray($actual['child']);
        $this->assertArrayHasKey('foo', $actual['child']);
        $this->assertEquals('', $actual['child']['foo']);
        $this->assertArrayNotHasKey('baz', $actual['child']);

        $actual = $this->normalizer->normalize(
            null,
            'docgen',
            ['groups' => 'docgen:read', 'docgen:expects' => TestableParentClass::class],
        );
        $this->assertIsArray($actual);
        $this->assertArrayHasKey('child', $actual);
        $this->assertIsArray($actual['child']);
        $this->assertArrayHasKey('foo', $actual['child']);
        $this->assertEquals('', $actual['child']['foo']);
        $this->assertArrayNotHasKey('baz', $actual['child']);
    }

    public function testNormalizableBooleanPropertyOrMethodOnNull()
    {
        $actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableClassWithBool::class]);

        $expected = [
            'foo' => null,
            'thing' => null,
            'isNull' => true,
        ];

        $this->assertEquals($expected, $actual);
    }

    public function testNormalizationBasic()
    {
        $scope = new Scope();
        $scope->setName(['fr' => 'scope']);

        $normalized = $this->normalizer->normalize($scope, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]);
        $expected = [
            'id' => null,
            'name' => 'scope',
            'type' => 'scope',
            'isNull' => false,
        ];

        $this->assertEquals($expected, $normalized, 'test normalization fo a scope');
    }

    public function testNormalizeBooleanPropertyOrMethod()
    {
        $testable = new TestableClassWithBool();
        $testable->foo = false;

        $actual = $this->normalizer->normalize($testable, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableClassWithBool::class]);

        $expected = [
            'foo' => false,
            'thing' => true,
            'isNull' => false,
        ];

        $this->assertEquals($expected, $actual);
    }

    public function testNormalizeNull()
    {
        $actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]);
        $expected = [
            'id' => '',
            'name' => '',
            'type' => 'scope',
            'isNull' => true,
        ];

        $this->assertEquals($expected, $actual, 'test normalization for a null scope');
    }

    public function testNormalizeNullObjectWithObjectEmbedded(): never
    {
        $this->markTestIncomplete('test to implement');
        $normalized = $this->normalizer->normalize(null, 'docgen', [
            AbstractNormalizer::GROUPS => ['docgen:read'],
            'docgen:expects' => User::class,
        ]);

        $expected = [
            'label' => '',
            'email' => '',
            'main_center' => [
                'name' => '',
            ],
        ];

        $this->assertEquals($expected, $normalized, 'test normalization for a null user');
    }

    public function testNormalizeWithNullValueEmbedded(): never
    {
        $this->markTestIncomplete('test to write');
        $user = new User();
        $user->setUsername('User Test');

        $normalized = $this->normalizer->normalize($user, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class]);
        $expected = [
            'label' => 'User Test',
            'email' => '',
            'mainCenter' => [
                'name' => '',
            ],
        ];

        $this->assertEquals($expected, $normalized, 'test normalization fo an user with null center');
    }

    public function testIntersectionTypeForReadableCollection(): void
    {
        $value = new TestableWithIntersectionReadableCollection();

        $normalized = $this->normalizer->normalize($value, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableWithIntersectionReadableCollection::class]);

        self::assertIsArray($normalized);
        self::assertIsArray($normalized['collection']);
        self::assertCount(2, $normalized['collection']);

        self::assertEquals(
            ['baz' => 'bloup', 'isNull' => false],
            $normalized['collection'][0]
        );
        self::assertEquals(
            ['baz' => 'bloup', 'isNull' => false],
            $normalized['collection'][1]
        );
    }

    public function testIntersectionTypeForReadableCollectionWithNullValue(): void
    {
        $normalized = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableWithIntersectionReadableCollection::class]);

        self::assertIsArray($normalized);
        self::assertIsArray($normalized['collection']);
        self::assertCount(0, $normalized['collection']);
    }

    public function testIntersectionTypeForCollection(): void
    {
        $value = new TestableWithCollection();

        $normalized = $this->normalizer->normalize($value, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableWithCollection::class]);

        self::assertIsArray($normalized);
        self::assertIsArray($normalized['collection']);
        self::assertCount(2, $normalized['collection']);

        self::assertEquals(
            ['baz' => 'bloup', 'isNull' => false],
            $normalized['collection'][0]
        );
        self::assertEquals(
            ['baz' => 'bloup', 'isNull' => false],
            $normalized['collection'][1]
        );
    }

    public function testIntersectionTypeForCollectionWithNullValue(): void
    {
        $normalized = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => TestableWithCollection::class]);

        self::assertIsArray($normalized);
        self::assertIsArray($normalized['collection']);
        self::assertCount(0, $normalized['collection']);
    }
}

class TestableParentClass
{
    #[Serializer\Groups('docgen:read')]
    #[Serializer\Context(normalizationContext: ['groups' => 'docgen:read:foo'], groups: ['docgen:read'])]
    public ?TestableChildClass $child;

    public function __construct()
    {
        $this->child = new TestableChildClass();
    }
}

class TestableChildClass
{
    #[Serializer\Groups('docgen:read')]
    public string $baz = 'bloup';

    #[Serializer\Groups('docgen:read:foo')]
    public string $foo = 'bar';
}

class TestableClassWithBool
{
    #[Serializer\Groups('docgen:read')]
    public bool $foo;

    #[Serializer\Groups('docgen:read')]
    public function getThing(): bool
    {
        return true;
    }
}

class TestableWithIntersectionReadableCollection
{
    #[Serializer\Groups('docgen:read')]
    public ReadableCollection&Selectable $collection;

    public function __construct()
    {
        $this->collection = new ArrayCollection([new TestableChildClass(), new TestableChildClass()]);
    }
}

class TestableWithCollection
{
    #[Serializer\Groups('docgen:read')]
    public Collection&Selectable $collection;

    public function __construct()
    {
        $this->collection = new ArrayCollection([new TestableChildClass(), new TestableChildClass()]);
    }
}
