<?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\Controller;

use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
use Chill\MainBundle\Serializer\Normalizer\CollectionNormalizer;
use Chill\PersonBundle\Controller\PersonIdentifierListApiController;
use Chill\PersonBundle\Entity\Identifier\PersonIdentifier;
use Chill\PersonBundle\Entity\Identifier\PersonIdentifierDefinition;
use Chill\PersonBundle\PersonIdentifier\Normalizer\PersonIdentifierWorkerNormalizer;
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierEngineInterface;
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierManagerInterface;
use Chill\PersonBundle\PersonIdentifier\PersonIdentifierWorker;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Serializer;

/**
 * @internal
 *
 * @coversNothing
 */
class PersonIdentifierListApiControllerTest extends TestCase
{
    use ProphecyTrait;

    public function testListAccessDenied(): void
    {
        $security = $this->prophesize(Security::class);
        $security->isGranted('ROLE_USER')->willReturn(false)->shouldBeCalledOnce();

        $serializer = new Serializer([new PersonIdentifierWorkerNormalizer(), new CollectionNormalizer()], [new JsonEncoder()]);

        $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
        $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class);

        $controller = new PersonIdentifierListApiController(
            $security->reveal(),
            $serializer,
            $personIdentifierManager->reveal(),
            $paginatorFactory->reveal(),
        );

        $this->expectException(AccessDeniedHttpException::class);
        $controller->list();
    }

    public function testListSuccess(): void
    {
        // Build 3 workers
        $engine = new class () implements PersonIdentifierEngineInterface {
            public static function getName(): string
            {
                return 'dummy';
            }

            public function canonicalizeValue(array $value, PersonIdentifierDefinition $definition): ?string
            {
                return null;
            }

            public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, PersonIdentifierDefinition $personIdentifierDefinition): void {}

            public function renderAsString(?PersonIdentifier $identifier, PersonIdentifierDefinition $definition): string
            {
                return '';
            }

            public function isEmpty(PersonIdentifier $identifier): bool
            {
                return false;
            }

            public function validate(PersonIdentifier $identifier, PersonIdentifierDefinition $definition): array
            {
                return [];
            }

            public function getDefaultValue(PersonIdentifierDefinition $definition): array
            {
                return [];
            }
        };

        $definition1 = new PersonIdentifierDefinition(['en' => 'Label 1'], 'dummy');
        $definition2 = new PersonIdentifierDefinition(['en' => 'Label 2'], 'dummy');
        $definition3 = new PersonIdentifierDefinition(['en' => 'Label 3'], 'dummy');
        // simulate persisted ids
        $r = new \ReflectionProperty(PersonIdentifierDefinition::class, 'id');
        $r->setAccessible(true);
        $r->setValue($definition1, 1);
        $r->setValue($definition2, 2);
        $r->setValue($definition3, 3);

        $workers = [
            new PersonIdentifierWorker($engine, $definition1),
            new PersonIdentifierWorker($engine, $definition2),
            new PersonIdentifierWorker($engine, $definition3),
        ];

        $security = $this->prophesize(Security::class);
        $security->isGranted('ROLE_USER')->willReturn(true)->shouldBeCalledOnce();

        $personIdentifierManager = $this->prophesize(PersonIdentifierManagerInterface::class);
        $personIdentifierManager->getWorkers()->willReturn($workers)->shouldBeCalledOnce();

        $paginator = $this->prophesize(\Chill\MainBundle\Pagination\PaginatorInterface::class);
        $paginator->setItemsPerPage(3)->shouldBeCalledOnce();
        $paginator->getCurrentPageFirstItemNumber()->willReturn(0);
        $paginator->getItemsPerPage()->willReturn(count($workers));
        $paginator->getTotalItems()->willReturn(count($workers));
        $paginator->hasNextPage()->willReturn(false);
        $paginator->hasPreviousPage()->willReturn(false);

        $paginatorFactory = $this->prophesize(PaginatorFactoryInterface::class);
        $paginatorFactory->create(3)->willReturn($paginator->reveal())->shouldBeCalledOnce();

        $serializer = new Serializer([
            new PersonIdentifierWorkerNormalizer(),
            new CollectionNormalizer(),
        ], [new JsonEncoder()]);

        $controller = new PersonIdentifierListApiController(
            $security->reveal(),
            $serializer,
            $personIdentifierManager->reveal(),
            $paginatorFactory->reveal(),
        );

        $response = $controller->list();
        self::assertSame(200, $response->getStatusCode());
        $body = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
        self::assertIsArray($body);
        self::assertArrayHasKey('count', $body);
        self::assertArrayHasKey('pagination', $body);
        self::assertArrayHasKey('results', $body);
        self::assertSame(3, $body['count']);
        self::assertCount(3, $body['results']);
        // spot check one item
        self::assertSame('person_identifier_worker', $body['results'][0]['type']);
        self::assertSame(1, $body['results'][0]['id']);
        self::assertSame('dummy', $body['results'][0]['engine']);
        self::assertSame(['en' => 'Label 1'], $body['results'][0]['label']);
    }
}
