<?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\MainBundle\Tests\Export;

use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\DirectExportInterface;
use Chill\MainBundle\Export\ExportConfigNormalizer;
use Chill\MainBundle\Export\ExportConfigProcessor;
use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\ExportGenerator;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Export\FormattedExportGeneration;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
use Doctrine\ORM\NativeQuery;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

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

    private function buildParameter(bool $filterStat): ParameterBagInterface
    {
        return new ParameterBag(
            ['chill_main' => ['acl' => ['filter_stats_by_center' => $filterStat]]]
        );
    }

    public function testGenerateHappyScenario()
    {
        $initialData = ['initial' => 'test'];
        $fullConfig = [
            'export' => $formExportData = ['key' => 'form1'],
            'filters' => [
                'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']],
                'disabled_filter' => ['enabled' => false],
            ],
            'aggregators' => [
                'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']],
                'disabled_aggregator' => ['enabled' => false],
            ],
            'pick_formatter' => 'xlsx',
            'formatter' => $formatterData = ['key' => 'form4'],
            'centers' => ['centers' => [$centerA = new Center()], 'regroupments' => [(new Regroupment())->addCenter($centerB = new Center())]],
        ];
        $user = new User();

        $export = $this->prophesize(ExportInterface::class);
        $filter = $this->prophesize(FilterInterface::class);
        $filter->applyOn()->willReturn('tagada');
        $aggregator = $this->prophesize(AggregatorInterface::class);
        $aggregator->applyOn()->willReturn('tsointsoin');
        $formatter = $this->prophesize(FormatterInterface::class);

        $query = $this->prophesize(QueryBuilder::class);
        $query->getDQL()->willReturn('dummy');
        $dqlQuery = $this->prophesize(Query::class);
        $dqlQuery->getSQL()->willReturn('dummy');
        $query->getQuery()->willReturn($dqlQuery->reveal());

        // required methods
        $export->initiateQuery(
            ['tagada', 'tsointsoin'],
            Argument::that(function ($arg) use ($centerB, $centerA) {
                if (!is_array($arg)) {
                    return false;
                }
                if (2 !== count($arg)) {
                    return false;
                }

                foreach ($arg as $item) {
                    if ([] !== $item['circles']) {
                        return false;
                    }
                    if (!in_array($item['center'], [$centerA, $centerB], true)) {
                        return false;
                    }
                }

                return true;
            }),
            $formExportData,
            Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
        )->shouldBeCalled()->willReturn($query->reveal());
        $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
            ->shouldBeCalled()->willReturn([['result0' => '0']]);
        $export->requiredRole()->willReturn('dummy_role');

        $filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
            ->shouldBeCalled();
        $aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
            ->shouldBeCalled();

        $formatter->generate(
            [['result0' => '0']],
            $formatterData,
            'dummy',
            $formExportData,
            ['dummy_filter' => $formFilterData],
            ['dummy_aggregator' => $formAggregatorData],
            Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
        )
            ->shouldBeCalled()
            ->willReturn(new FormattedExportGeneration('export result', 'text/text'));

        $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
        $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);

        $exportManager = $this->prophesize(ExportManager::class);
        $exportManager->getExport('dummy')->willReturn($export->reveal());
        $exportManager->getFilter('dummy_filter')->willReturn($filter->reveal());
        $exportManager->hasFilter('dummy_filter')->willReturn(true);
        $exportManager->hasFilter('disabled_filter')->willReturn(true);
        $exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal());
        $exportManager->hasAggregator('dummy_aggregator')->willReturn(true);
        $exportManager->hasAggregator('disabled_aggregator')->willReturn(true);
        $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());

        $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
        $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);

        $centerRepository = $this->prophesize(CenterRepositoryInterface::class);
        $centerRepository->findActive()->shouldNotBeCalled();

        $generator = new ExportGenerator(
            $exportManager->reveal(),
            $exportConfigNormalizer->reveal(),
            new NullLogger(),
            $authorizationHelper->reveal(),
            new CenterRegroupementResolver(),
            new ExportConfigProcessor($exportManager->reveal()),
            $this->buildParameter(true),
            $centerRepository->reveal(),
        );

        $actual = $generator->generate('dummy', $initialData, $user);

        self::assertEquals('export result', $actual->content);
        self::assertEquals('text/text', $actual->contentType);
    }

    public function testGenerateNativeSqlHappyScenario()
    {
        $initialData = ['initial' => 'test'];
        $fullConfig = [
            'export' => $formExportData = ['key' => 'form1'],
            'filters' => [],
            'aggregators' => [],
            'pick_formatter' => 'xlsx',
            'formatter' => $formatterData = ['key' => 'form4'],
            'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []],
        ];
        $user = new User();

        $export = $this->prophesize(ExportInterface::class);
        $formatter = $this->prophesize(FormatterInterface::class);

        $query = $this->prophesize(NativeQuery::class);

        // required methods
        $export->initiateQuery(
            [],
            [['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]],
            ['key' => 'form1'],
            Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
        )->shouldBeCalled()->willReturn($query->reveal());
        $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
            ->shouldBeCalled()->willReturn([['result0' => '0']]);
        $export->supportsModifiers()->willReturn([]);
        $export->requiredRole()->willReturn('dummy_role');

        $formatter->generate(
            [['result0' => '0']],
            $formatterData,
            'dummy',
            $formExportData,
            [],
            [],
            Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
        )
            ->shouldBeCalled()
            ->willReturn(new FormattedExportGeneration('export result', 'text/text'));

        $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
        $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);

        $exportManager = $this->prophesize(ExportManager::class);
        $exportManager->getExport('dummy')->willReturn($export->reveal());
        $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());

        $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
        $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);

        $centerRepository = $this->prophesize(CenterRepositoryInterface::class);
        $centerRepository->findActive()->shouldNotBeCalled();

        $generator = new ExportGenerator(
            $exportManager->reveal(),
            $exportConfigNormalizer->reveal(),
            new NullLogger(),
            $authorizationHelper->reveal(),
            new CenterRegroupementResolver(),
            new ExportConfigProcessor($exportManager->reveal()),
            $this->buildParameter(true),
            $centerRepository->reveal(),
        );

        $actual = $generator->generate('dummy', $initialData, $user);

        self::assertInstanceOf(FormattedExportGeneration::class, $actual);
        self::assertEquals('export result', $actual->content);
        self::assertEquals('text/text', $actual->contentType);
    }

    public function testGenerateDirectExportHappyScenario()
    {
        $initialData = ['initial' => 'test'];
        $fullConfig = [
            'export' => $formExportData = ['key' => 'form1'],
            'filters' => [],
            'aggregators' => [],
            'pick_formatter' => 'xlsx',
            'formatter' => ['form' => $formatterData = ['key' => 'form4']],
            'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []],
        ];
        $user = new User();

        $export = $this->prophesize(DirectExportInterface::class);

        // required methods
        $export->generate(
            [['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]],
            ['key' => 'form1'],
            Argument::that(static fn (ExportGenerationContext $context) => $user === $context->byUser),
        )->shouldBeCalled()
            ->willReturn(new FormattedExportGeneration('export result', 'text/text'));
        $export->requiredRole()->willReturn('dummy_role');

        $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
        $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);

        $exportManager = $this->prophesize(ExportManager::class);
        $exportManager->getExport('dummy')->willReturn($export->reveal());

        $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
        $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);

        $centerRepository = $this->prophesize(CenterRepositoryInterface::class);
        $centerRepository->findActive()->shouldNotBeCalled();

        $generator = new ExportGenerator(
            $exportManager->reveal(),
            $exportConfigNormalizer->reveal(),
            new NullLogger(),
            $authorizationHelper->reveal(),
            new CenterRegroupementResolver(),
            new ExportConfigProcessor($exportManager->reveal()),
            $this->buildParameter(true),
            $centerRepository->reveal(),
        );

        $actual = $generator->generate('dummy', $initialData, $user);

        self::assertInstanceOf(FormattedExportGeneration::class, $actual);
        self::assertEquals('export result', $actual->content);
        self::assertEquals('text/text', $actual->contentType);
    }

    public function testGenerateHappyScenarioWithoutCenterFiltering()
    {
        $initialData = ['initial' => 'test'];
        $fullConfig = [
            'export' => $formExportData = ['key' => 'form1'],
            'filters' => [
                'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']],
                'disabled_filter' => ['enabled' => false],
            ],
            'aggregators' => [
                'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']],
                'disabled_aggregator' => ['enabled' => false],
            ],
            'pick_formatter' => 'xlsx',
            'formatter' => $formatterData = ['key' => 'form4'],
            'centers' => ['centers' => [], 'regroupments' => []],
        ];
        $user = new User();
        $centerA = new Center();
        $centerB = new Center();

        $export = $this->prophesize(ExportInterface::class);
        $filter = $this->prophesize(FilterInterface::class);
        $filter->applyOn()->willReturn('tagada');
        $aggregator = $this->prophesize(AggregatorInterface::class);
        $aggregator->applyOn()->willReturn('tsointsoin');
        $formatter = $this->prophesize(FormatterInterface::class);

        $query = $this->prophesize(QueryBuilder::class);
        $query->getDQL()->willReturn('dummy');
        $dqlQuery = $this->prophesize(Query::class);
        $dqlQuery->getSQL()->willReturn('dummy');
        $query->getQuery()->willReturn($dqlQuery->reveal());

        // required methods
        $export->initiateQuery(
            ['tagada', 'tsointsoin'],
            Argument::that(function ($arg) use ($centerB, $centerA) {
                if (!is_array($arg)) {
                    return false;
                }
                if (2 !== count($arg)) {
                    return false;
                }

                foreach ($arg as $item) {
                    if ([] !== $item['circles']) {
                        return false;
                    }
                    if (!in_array($item['center'], [$centerA, $centerB], true)) {
                        return false;
                    }
                }

                return true;
            }),
            ['key' => 'form1'],
            Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
        )->shouldBeCalled()->willReturn($query->reveal());
        $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
            ->shouldBeCalled()->willReturn([['result0' => '0']]);
        $export->requiredRole()->willReturn('dummy_role');

        $filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
            ->shouldBeCalled();
        $aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
            ->shouldBeCalled();

        $formatter->generate(
            [['result0' => '0']],
            $formatterData,
            'dummy',
            $formExportData,
            ['dummy_filter' => $formFilterData],
            ['dummy_aggregator' => $formAggregatorData],
            Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
        )
            ->shouldBeCalled()
            ->willReturn(new FormattedExportGeneration('export result', 'text/text'));

        $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
        $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);

        $exportManager = $this->prophesize(ExportManager::class);
        $exportManager->getExport('dummy')->willReturn($export->reveal());
        $exportManager->getFilter('dummy_filter')->willReturn($filter->reveal());
        $exportManager->hasFilter('dummy_filter')->willReturn(true);
        $exportManager->hasFilter('disabled_filter')->willReturn(true);
        $exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal());
        $exportManager->hasAggregator('dummy_aggregator')->willReturn(true);
        $exportManager->hasAggregator('disabled_aggregator')->willReturn(true);
        $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());

        $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
        $authorizationHelper->getReachableCenters($user, 'dummy_role')->shouldNotBeCalled();

        $centerRepository = $this->prophesize(CenterRepositoryInterface::class);
        $centerRepository->findActive()->willReturn([$centerA, $centerB])->shouldBeCalled();

        $generator = new ExportGenerator(
            $exportManager->reveal(),
            $exportConfigNormalizer->reveal(),
            new NullLogger(),
            $authorizationHelper->reveal(),
            new CenterRegroupementResolver(),
            new ExportConfigProcessor($exportManager->reveal()),
            $this->buildParameter(false),
            $centerRepository->reveal(),
        );

        $actual = $generator->generate('dummy', $initialData, $user);

        self::assertEquals('export result', $actual->content);
        self::assertEquals('text/text', $actual->contentType);
    }
}
