<?php

namespace App\Tests\Unit\Service\Doctrine;

use App\Filter\Doctrine\TimeConstraint\AbstractTimeFilter;
use App\Filter\Doctrine\TimeConstraint\ActivityYearFilter;
use App\Filter\Doctrine\TimeConstraint\DatetimeFilter;
use App\Service\Constraint\ActivityYearConstraint;
use App\Service\Constraint\DateTimeConstraint;
use App\Service\Doctrine\FiltersConfigurationService;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\FilterCollection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class TestableFiltersConfigurationService extends FiltersConfigurationService
{
    public function getFilters(): FilterCollection
    {
        return parent::getFilters();
    }

    public function timeFiltersAlreadyDisabled(): bool
    {
        return parent::timeFiltersAlreadyDisabled();
    }

    public function disableFilter(string $name): void
    {
        parent::disableFilter($name);
    }

    public function enableFilter(string $name): void
    {
        parent::enableFilter($name);
    }

    public function setPreviousTimeConstraintState(?bool $value): void
    {
        $this->previousTimeConstraintState = $value;
    }

    public function getPreviousTimeConstraintState(): ?bool
    {
        return $this->previousTimeConstraintState;
    }
}

class FiltersConfigurationServiceTest extends TestCase
{
    private \PHPUnit\Framework\MockObject\MockObject|EntityManagerInterface $em;
    private DateTimeConstraint|MockObject $dateTimeConstraint;
    private ActivityYearConstraint|MockObject $activityYearConstraint;

    public function setUp(): void
    {
        $this->em = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
        $this->dateTimeConstraint = $this->getMockBuilder(DateTimeConstraint::class)->disableOriginalConstructor()->getMock();
        $this->activityYearConstraint = $this->getMockBuilder(ActivityYearConstraint::class)->disableOriginalConstructor()->getMock();
    }

    public function getFiltersConfigurationServiceMockFor(string $methodName): MockObject|TestableFiltersConfigurationService
    {
        return $this
            ->getMockBuilder(TestableFiltersConfigurationService::class)
            ->setConstructorArgs([$this->em, $this->dateTimeConstraint, $this->activityYearConstraint])
            ->setMethodsExcept([$methodName, 'getPreviousTimeConstraintState', 'setPreviousTimeConstraintState'])
            ->getMock();
    }

    public function testGetFilters(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('getFilters');

        $filterCollection = $this->getMockBuilder(FilterCollection::class)->disableOriginalConstructor()->getMock();

        $this->em->expects(self::once())->method('getFilters')->willReturn($filterCollection);

        $this->assertEquals(
            $filterCollection,
            $filterConfigurationService->getFilters()
        );
    }

    public function testConfigureTimeConstraintFilters(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('configureTimeConstraintFilters');

        $accessId = 123;

        $filterCollection = $this->getMockBuilder(FilterCollection::class)->disableOriginalConstructor()->getMock();

        $filterConfigurationService
            ->method('getFilters')
            ->willReturn($filterCollection);

        $datetimeFilter = $this->getMockBuilder(DatetimeFilter::class)->disableOriginalConstructor()->getMock();
        $activityYearFilter = $this->getMockBuilder(ActivityYearFilter::class)->disableOriginalConstructor()->getMock();

        $filterCollection
            ->expects(self::exactly(2))
            ->method('enable')
            ->willReturnMap([
                ['date_time_filter', $datetimeFilter],
                ['activity_year_filter', $activityYearFilter],
            ]);

        $datetimeFilter->expects(self::once())->method('setAccessId')->with($accessId);
        $datetimeFilter->expects(self::once())->method('setTimeConstraint')->with($this->dateTimeConstraint);

        $activityYearFilter->expects(self::once())->method('setAccessId')->with($accessId);
        $activityYearFilter->expects(self::once())->method('setTimeConstraint')->with($this->activityYearConstraint);

        $filterConfigurationService->configureTimeConstraintFilters($accessId);
    }

    public function testSuspendTimeConstraintFilters(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('suspendTimeConstraintFilters');

        $filterConfigurationService->expects(self::once())->method('timeFiltersAlreadyDisabled')->willReturn(false);
        $filterConfigurationService->expects(self::exactly(2))->method('disableFilter')->withConsecutive(['date_time_filter'], ['activity_year_filter']);

        $filterConfigurationService->suspendTimeConstraintFilters();

        $this->assertEquals(
            true,
            $filterConfigurationService->getPreviousTimeConstraintState()
        );
    }

    public function testSuspendTimeConstraintFiltersAlreadySuspended(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('suspendTimeConstraintFilters');

        $filterConfigurationService->setPreviousTimeConstraintState(true);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('time constraints is already suspended');

        $filterConfigurationService->suspendTimeConstraintFilters();
    }

    public function testSuspendTimeConstraintFiltersAlreadyDisabled(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('suspendTimeConstraintFilters');

        $filterConfigurationService->expects(self::once())->method('timeFiltersAlreadyDisabled')->willReturn(true);

        $filterConfigurationService->suspendTimeConstraintFilters();

        $this->assertEquals(
            false,
            $filterConfigurationService->getPreviousTimeConstraintState()
        );
    }

    public function testTimeFiltersAlreadyDisabledReturnTrue(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('timeFiltersAlreadyDisabled');

        $filterCollection = $this->getMockBuilder(FilterCollection::class)->disableOriginalConstructor()->getMock();
        $filterConfigurationService->expects(self::once())->method('getFilters')->willReturn($filterCollection);

        $filterCollection->expects(self::exactly(2))->method('isEnabled')->withConsecutive(['date_time_filter'], ['activity_year_filter'])->willReturnOnConsecutiveCalls(false, false);

        $this->assertEquals(
            true,
            $filterConfigurationService->timeFiltersAlreadyDisabled()
        );
    }

    public function testTimeFiltersAlreadyDisabledReturnFalseFirstArgTrue(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('timeFiltersAlreadyDisabled');

        $filterCollection = $this->getMockBuilder(FilterCollection::class)->disableOriginalConstructor()->getMock();
        $filterConfigurationService->expects(self::once())->method('getFilters')->willReturn($filterCollection);

        $filterCollection->expects(self::once())->method('isEnabled')->withConsecutive(['date_time_filter'])->willReturn(true);

        $this->assertEquals(
            false,
            $filterConfigurationService->timeFiltersAlreadyDisabled()
        );
    }

    public function testTimeFiltersAlreadyDisabledReturnFalseSecondArgTrue(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('timeFiltersAlreadyDisabled');

        $filterCollection = $this->getMockBuilder(FilterCollection::class)->disableOriginalConstructor()->getMock();
        $filterConfigurationService->expects(self::once())->method('getFilters')->willReturn($filterCollection);

        $filterCollection->expects(self::exactly(2))->method('isEnabled')->withConsecutive(['date_time_filter'], ['activity_year_filter'])->willReturnOnConsecutiveCalls(false, true);

        $this->assertEquals(
            false,
            $filterConfigurationService->timeFiltersAlreadyDisabled()
        );
    }

    public function testDisableFilters(): void
    {
        $fooFilter = 'filter_name';

        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('disableFilter');

        $filterCollection = $this->getMockBuilder(FilterCollection::class)->disableOriginalConstructor()->getMock();
        $filterConfigurationService->expects(self::once())->method('getFilters')->willReturn($filterCollection);

        $abstractTimeFilter = $this->getMockBuilder(AbstractTimeFilter::class)->disableOriginalConstructor()->getMock();
        $filterCollection->expects(self::once())->method('getFilter')->withConsecutive([$fooFilter])->willReturn($abstractTimeFilter);

        $abstractTimeFilter->expects(self::once())->method('disable');

        $filterConfigurationService->disableFilter($fooFilter);
    }

    public function testEnableFilter(): void
    {
        $fooFilter = 'filter_name';

        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('enableFilter');

        $filterCollection = $this->getMockBuilder(FilterCollection::class)->disableOriginalConstructor()->getMock();
        $filterConfigurationService->expects(self::once())->method('getFilters')->willReturn($filterCollection);

        $abstractTimeFilter = $this->getMockBuilder(AbstractTimeFilter::class)->disableOriginalConstructor()->getMock();
        $filterCollection->expects(self::once())->method('getFilter')->withConsecutive([$fooFilter])->willReturn($abstractTimeFilter);

        $abstractTimeFilter->expects(self::once())->method('enable');

        $filterConfigurationService->enableFilter($fooFilter);
    }

    public function testRestoreTimeConstraintFilters(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('restoreTimeConstraintFilters');

        $filterConfigurationService->setPreviousTimeConstraintState(true);

        $filterConfigurationService->expects(self::exactly(2))->method('enableFilter')->withConsecutive(['date_time_filter'], ['activity_year_filter']);

        $filterConfigurationService->restoreTimeConstraintFilters();

        $this->assertEquals(
            null,
            $filterConfigurationService->getPreviousTimeConstraintState()
        );
    }

    public function testRestoreTimeConstraintFiltersNotAlreadySuspended(): void
    {
        $filterConfigurationService = $this->getFiltersConfigurationServiceMockFor('restoreTimeConstraintFilters');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('time constraints has not been suspended, can not be restored');

        $filterConfigurationService->restoreTimeConstraintFilters();
    }
}
