<?php

namespace App\Tests\Unit\Service\Constraint;

use App\Entity\Access\Access;
use App\Entity\Organization\Organization;
use App\Repository\Access\AccessRepository;
use App\Service\Constraint\ActivityYearConstraint;
use App\Service\Organization\Utils as OrganizationUtils;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;

class TestableActivityYearConstraint extends ActivityYearConstraint {
    public function hasCustomPeriods($historical): bool { return parent::hasCustomPeriods($historical); }
    public function addConstraint(array $contraints, array $newContraint): array { return parent::addConstraint($contraints, $newContraint); }
    public function cleanConstraints(array $constraints): array { return parent::cleanConstraints($constraints); }

    public function getRangeYear(Access $access, string $dateStart, string $dateEnd): array { return parent::getRangeYear($access, $dateStart, $dateEnd); }
    public function presentConstraint(int $year): array { return parent::presentConstraint($year); }
    public function pastConstraint($year): array { return parent::pastConstraint($year); }
    public function futureConstraint($year): array { return parent::futureConstraint($year); }
    public function customConstraint($years): array { return parent::customConstraint($years); }
}


class ActivityYearConstraintTest extends TestCase
{
    private EntityManagerInterface $entityManager;
    private OrganizationUtils $organizationUtils;

    public function setUp(): void {
        $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
        $this->organizationUtils = $this->getMockBuilder(OrganizationUtils::class)->disableOriginalConstructor()->getMock();
    }

    /**
     * @see ActivityYearConstraint::invoke()
     */
    public function testInvokePresentNoCustomPeriods(): void
    {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['invoke'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();

        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
        $accessRepository->method('find')->with(123)->willReturn($access);

        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);

        $historical = ["future" => false,"past" => false,"present" => true,"dateStart" => null,"dateEnd" => null];

        $access->method('getHistorical')->willReturn($historical);
        $access->method('getActivityYear')->willReturn(2020);

        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);

        $constraint = ['foo'];
        $activityYearConstraint->method('presentConstraint')->willReturn($constraint);

        $activityYearConstraint
            ->expects(self::once())
            ->method('addConstraint')
            ->with(['start' => [], 'end' => []] , $constraint)
            ->willReturn(['bar']);

        $activityYearConstraint
            ->expects(self::once())
            ->method('cleanConstraints')
            ->with(['bar']);

        $activityYearConstraint->invoke(123);
    }

    /**
     * @see ActivityYearConstraint::invoke()
     */
    public function testInvokePastNoCustomPeriods(): void
    {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['invoke'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();

        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
        $accessRepository->method('find')->with(123)->willReturn($access);

        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);

        $historical = ["future" => false,"past" => true,"present" => false,"dateStart" => null,"dateEnd" => null];

        $access->method('getHistorical')->willReturn($historical);
        $access->method('getActivityYear')->willReturn(2020);

        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);

        $constraint = ['foo'];
        $activityYearConstraint->method('pastConstraint')->willReturn($constraint);

        $activityYearConstraint
            ->expects(self::once())
            ->method('addConstraint')
            ->with(['start' => [], 'end' => []] , $constraint)
            ->willReturn(['bar']);

        $activityYearConstraint
            ->expects(self::once())
            ->method('cleanConstraints')
            ->with(['bar']);

        $activityYearConstraint->invoke(123);
    }

    /**
     * @see ActivityYearConstraint::invoke()
     */
    public function testInvokeFutureNoCustomPeriods(): void
    {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['invoke'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();

        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
        $accessRepository->method('find')->with(123)->willReturn($access);

        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);

        $historical = ["future" => true,"past" => false,"present" => false,"dateStart" => null,"dateEnd" => null];

        $access->method('getHistorical')->willReturn($historical);
        $access->method('getActivityYear')->willReturn(2020);

        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);

        $constraint = ['foo'];
        $activityYearConstraint->method('futureConstraint')->willReturn($constraint);

        $activityYearConstraint
            ->expects(self::once())
            ->method('addConstraint')
            ->with(['start' => [], 'end' => []] , $constraint)
            ->willReturn(['bar']);

        $activityYearConstraint
            ->expects(self::once())
            ->method('cleanConstraints')
            ->with(['bar']);

        $activityYearConstraint->invoke(123);
    }

    /**
     * @see ActivityYearConstraint::invoke()
     */
    public function testInvokeMultiplePeriodsNoCustom(): void
    {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['invoke'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();

        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
        $accessRepository->method('find')->with(123)->willReturn($access);

        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);

        $historical = ["future" => true,"past" => true,"present" => true,"dateStart" => null,"dateEnd" => null];

        $access->method('getHistorical')->willReturn($historical);
        $access->method('getActivityYear')->willReturn(2020);

        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(false);

        $activityYearConstraint->method('pastConstraint')->willReturn(['pastConstraint']);
        $activityYearConstraint->method('presentConstraint')->willReturn(['presentConstraint']);
        $activityYearConstraint->method('futureConstraint')->willReturn(['futureConstraint']);

        $activityYearConstraint
            ->expects(self::exactly(3))
            ->method('addConstraint')
            ->withConsecutive(
                [['start' => [], 'end' => []] , ['presentConstraint']],
                [['start' => [], 'end' => []] , ['pastConstraint']],
                [['start' => [], 'end' => []] , ['futureConstraint']]
            )->willReturn(['start' => [], 'end' => []]);

        $activityYearConstraint
            ->expects(self::once())
            ->method('cleanConstraints')
            ->with(['start' => [], 'end' => []]);

        $activityYearConstraint->invoke(123);
    }

    /**
     * @see ActivityYearConstraint::invoke()
     */
    public function testInvokeWithCustom(): void {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['invoke'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->getMock();

        $accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
        $accessRepository->method('find')->with(123)->willReturn($access);

        $this->entityManager->method('getRepository')->with(Access::class)->willReturn($accessRepository);

        $historical = ["future" => false,"past" => false,"present" => true,"dateStart" => '2020-01-01',"dateEnd" => '2020-02-01'];

        $access->method('getHistorical')->willReturn($historical);
        $access->method('getActivityYear')->willReturn(2020);

        $activityYearConstraint->method('hasCustomPeriods')->with($historical)->willReturn(true);

        $activityYearConstraint->expects(self::never())->method('pastConstraint');
        $activityYearConstraint->expects(self::never())->method('presentConstraint');
        $activityYearConstraint->expects(self::never())->method('futureConstraint');

        $activityYearConstraint
            ->method('getRangeYear')
            ->with($access, '2020-01-01', '2020-02-01')
            ->willReturn(['dateStart' => 2020, 'dateEnd' => 2020]);

        $activityYearConstraint->method('customConstraint')->with(['dateStart' => 2020, 'dateEnd' => 2020])->willReturn(['bar']);

        $activityYearConstraint
            ->expects(self::once())
            ->method('addConstraint')
            ->with(['start' => [], 'end' => []], ['bar'])
            ->willReturn(['start' => [], 'end' => []]);

        $activityYearConstraint
            ->expects(self::once())
            ->method('cleanConstraints')
            ->with(['start' => [], 'end' => []]);

        $activityYearConstraint->invoke(123);
    }

    /**
     * @see ActivityYearConstraint::getRangeYear()
     */
    public function testGetRangeYear(): void {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['getRangeYear'])
            ->getMock();

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $access = $this->getMockBuilder(Access::class)->getMock();
        $access->method('getOrganization')->willReturn($organization);

        $this->organizationUtils
            ->method('getActivityYearSwitchDate')
            ->with($organization, self::isInstanceOf(\DateTime::class))
            ->willReturnOnConsecutiveCalls(
              2020, 2020, 2020, 2022
            );

        $this->assertEquals(
            ['dateStart' => 2020, 'dateEnd' => 2020],
            $activityYearConstraint->getRangeYear($access, '2020-01-01', '2020-12-31')
        );
        $this->assertEquals(
            ['dateStart' => 2020, 'dateEnd' => 2022],
            $activityYearConstraint->getRangeYear($access, '2020-01-01', '2022-12-31')
        );
    }

    /**
     * @see ActivityYearConstraint::presentConstraint()
     */
    public function testPresentConstraint(): void {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['presentConstraint'])
            ->getMock();

        $this->assertEquals(
            ['start' => [2020 => 3]],
            $activityYearConstraint->presentConstraint(2020)
        );
    }

    /**
     * @see ActivityYearConstraint::pastConstraint()
     */
    public function testPastConstraint(): void {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['pastConstraint'])
            ->getMock();

        $this->assertEquals(
            ['end' => [2020 => 1]],
            $activityYearConstraint->pastConstraint(2020)
        );
    }

    /**
     * @see ActivityYearConstraint::futureConstraint()
     */
    public function testFutureConstraint(): void {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['futureConstraint'])
            ->getMock();

        $this->assertEquals(
            ['start' => [2020 => 5]],
            $activityYearConstraint->futureConstraint(2020)
        );
    }

    /**
     * @see ActivityYearConstraint::customConstraint()
     */
    public function testCustomConstraint(): void {
        $activityYearConstraint = $this->getMockBuilder(TestableActivityYearConstraint::class)
            ->setConstructorArgs([$this->entityManager, $this->organizationUtils])
            ->setMethodsExcept(['customConstraint'])
            ->getMock();

        $this->assertEquals(
            ['start' => [2020 => 8, 2022 => 4]],
            $activityYearConstraint->customConstraint(['dateStart' => 2020, 'dateEnd' => 2022])
        );
    }
}
