<?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 Service\DocGenerator;

use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Service\DocGenerator\AccompanyingPeriodContext;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class AccompanyingPeriodContextTest extends KernelTestCase
{
    private BaseContextData $baseContextData;

    private DocumentCategoryRepository $documentCategoryRepository;

    private EntityManagerInterface $em;

    private NormalizerInterface $normalizer;

    private PersonRenderInterface $personRender;

    private PersonRepository $personRepository;

    private TranslatableStringHelperInterface $translatableStringHelper;

    private TranslatorInterface $translator;

    private ThirdPartyRender $thirdPartyRender;

    private ThirdPartyRepository $thirdPartyRepository;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->baseContextData = self::getContainer()->get(BaseContextData::class);
        $this->documentCategoryRepository = self::getContainer()->get(DocumentCategoryRepository::class);
        $this->em = self::getContainer()->get(EntityManagerInterface::class);
        $this->normalizer = self::getContainer()->get(NormalizerInterface::class);
        $this->personRender = self::getContainer()->get(PersonRenderInterface::class);
        $this->personRepository = self::getContainer()->get(PersonRepository::class);
        $this->translatableStringHelper = self::getContainer()->get(TranslatableStringHelperInterface::class);
        $this->translator = self::getContainer()->get(TranslatorInterface::class);
        $this->thirdPartyRender = self::getContainer()->get(ThirdPartyRender::class);
        $this->thirdPartyRepository = self::getContainer()->get(ThirdPartyRepository::class);
    }

    private function buildContext(): AccompanyingPeriodContext
    {
        return new AccompanyingPeriodContext(
            $this->documentCategoryRepository,
            $this->normalizer,
            $this->translatableStringHelper,
            $this->em,
            $this->personRender,
            $this->personRepository,
            $this->translator,
            $this->baseContextData,
            $this->thirdPartyRender,
            $this->thirdPartyRepository,
        );
    }

    /**
     * This test run the methods executed when a document is generated:.
     *
     * - normalized data from the form in a way that they are stored in message queue;
     * - denormalize the data from the message queue,
     * - and get the data, as they will be transmitted to the GeneratorDriver
     *
     * @param array              $options          the options, as they are stored in the DocGeneratorTemplate (the admin form data)
     * @param AccompanyingPeriod $entity           The entity from which the data will be extracted
     * @param array              $data             The data, from the public form
     * @param callable           $assertionsOnData some test that will be executed on the normalized data
     *
     * @dataProvider provideNormalizedData
     */
    public function testContextGenerationDataNormalizeDenormalizeGetData(
        array $options,
        AccompanyingPeriod $entity,
        array $data,
        array $expectedNormalized,
        callable $assertionsOnData,
    ): void {
        $context = $this->buildContext();
        $template = new DocGeneratorTemplate();
        $template->setName(['fr' => 'test'])->setContext(AccompanyingPeriodContext::class)
            ->setDescription('description')->setActive(true)
            ->setOptions($options);

        $normalized = $context->contextGenerationDataNormalize($template, $entity, $data);

        self::assertEquals($expectedNormalized, $normalized);

        $denormalized = $context->contextGenerationDataDenormalize($template, $entity, $normalized);

        $data = $context->getData($template, $entity, $denormalized);

        call_user_func($assertionsOnData, $data);
    }

    public static function provideNormalizedData(): iterable
    {
        self::bootKernel();
        $em = self::getContainer()->get(EntityManagerInterface::class);

        $thirdParty = $em->createQuery('SELECT t FROM '.ThirdParty::class.' t')
            ->setMaxResults(1)
            ->getSingleResult();

        if (null === $thirdParty) {
            throw new \RuntimeException('No thirdparty in database');
        }

        $period = $em->createQuery('SELECT a FROM '.AccompanyingPeriod::class." a WHERE a.step = 'CONFIRMED'")
            ->setMaxResults(1)
            ->getSingleResult();

        if (null === $period) {
            throw new \RuntimeException('No confirmed period in database');
        }

        $person = $em->createQuery('SELECT p FROM '.Person::class.' p')
            ->setMaxResults(1)
            ->getSingleResult();

        if (null === $person) {
            throw new \RuntimeException('No confirmed period in database');
        }

        yield [
            // test with only thirdParty
            [
                'mainPerson' => false,
                'mainPersonLabel' => 'person',
                'person1' => false,
                'person1Label' => 'person2',
                'person2' => false,
                'person2Label' => 'person2',
                'thirdParty' => true,
                'thirdPartyLabel' => '3party',
            ],
            $period,
            [
                'thirdParty' => $thirdParty,
            ],
            [
                'thirdParty' => $thirdParty->getId(),
                'mainPerson' => null,
                'person1' => null,
                'person2' => null,
            ],
            function (array $data) use ($thirdParty, $period) {
                self::assertArrayHasKey('thirdParty', $data);
                self::assertEquals($thirdParty->getId(), $data['thirdParty']['id']);

                self::assertArrayHasKey('course', $data);
                self::assertEquals($period->getId(), $data['course']['id']);
            },
        ];

        yield [
            // test with only mainPerson
            [
                'mainPerson' => true,
                'mainPersonLabel' => 'person',
                'person1' => false,
                'person1Label' => 'person2',
                'person2' => false,
                'person2Label' => 'person2',
                'thirdParty' => false,
                'thirdPartyLabel' => '3party',
            ],
            $period,
            [
                'mainPerson' => $person,
            ],
            [
                'thirdParty' => null,
                'mainPerson' => $person->getId(),
                'person1' => null,
                'person2' => null,
            ],
            function (array $data) use ($person, $period) {
                self::assertArrayHasKey('mainPerson', $data);
                self::assertEquals($person->getId(), $data['mainPerson']['id']);

                self::assertArrayHasKey('course', $data);
                self::assertEquals($period->getId(), $data['course']['id']);
            },
        ];

        yield [
            // test with every options activated
            [
                'mainPerson' => true,
                'mainPersonLabel' => 'person',
                'person1' => true,
                'person1Label' => 'person2',
                'person2' => true,
                'person2Label' => 'person2',
                'thirdParty' => true,
                'thirdPartyLabel' => '3party',
            ],
            $period,
            [
                'mainPerson' => $person,
                'person1' => $person,
                'person2' => $person,
                'thirdParty' => $thirdParty,
            ],
            [
                'thirdParty' => $thirdParty->getId(),
                'mainPerson' => $person->getId(),
                'person1' => $person->getId(),
                'person2' => $person->getId(),
            ],
            function (array $data) use ($person, $thirdParty, $period) {
                self::assertArrayHasKey('mainPerson', $data);
                self::assertEquals($person->getId(), $data['mainPerson']['id']);

                self::assertArrayHasKey('person1', $data);
                self::assertEquals($person->getId(), $data['person1']['id']);

                self::assertArrayHasKey('person2', $data);
                self::assertEquals($person->getId(), $data['person2']['id']);

                self::assertArrayHasKey('thirdParty', $data);
                self::assertEquals($thirdParty->getId(), $data['thirdParty']['id']);

                self::assertArrayHasKey('course', $data);
                self::assertEquals($period->getId(), $data['course']['id']);
            },
        ];

        yield [
            // test with any option activated
            [
                'mainPerson' => false,
                'mainPersonLabel' => 'person',
                'person1' => false,
                'person1Label' => 'person2',
                'person2' => false,
                'person2Label' => 'person2',
                'thirdParty' => false,
                'thirdPartyLabel' => '3party',
            ],
            $period,
            [
                'mainPerson' => null,
                'person1' => null,
                'person2' => null,
                'thirdParty' => null,
            ],
            [
                'thirdParty' => null,
                'mainPerson' => null,
                'person1' => null,
                'person2' => null,
            ],
            function (array $data) use ($period) {
                self::assertArrayHasKey('course', $data);
                self::assertEquals($period->getId(), $data['course']['id']);
            },
        ];
    }
}
