<?php

namespace App\Tests\Unit\Service\OnlineRegistration;

use App\Entity\Access\Access;
use App\Entity\AccessWish\AccessFamilyWish;
use App\Entity\AccessWish\AccessWish;
use App\Entity\AccessWish\EducationStudentWish;
use App\Enum\OnlineRegistration\RegistrationStatusEnum;
use App\Enum\OnlineRegistration\WishRegistrationEnum;
use App\Service\OnlineRegistration\RegistrationStatusService;
use App\Service\Utils\DatesUtils;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;

class TestableRegistrationStatusService extends RegistrationStatusService {
    public function getCurrentAccessWish(Access $access): AccessWish | null { return parent::getCurrentAccessWish($access); }
    public function getDaysSinceLastUpdate(AccessWish $currentAccessWish): int {
        return parent::getDaysSinceLastUpdate($currentAccessWish);
    }
    public function countEducationsByRegistrationStatus(AccessWish $currentAccessWish): array | null {
        return parent::countEducationsByRegistrationStatus($currentAccessWish);
    }
}


class RegistrationStatusServiceTest extends TestCase
{
    public function tearDown(): void
    {
        DatesUtils::clearFakeDatetime();
    }

    private function makeRegistrationStatusServiceMockForMethod(string $methodName): MockObject | TestableRegistrationStatusService {
        return $this->getMockBuilder(TestableRegistrationStatusService::class)
            ->setMethodsExcept([$methodName])
            ->getMock();
    }

    private function _getStatusWith(int $negotiable, int $accepted, int $pending, int $denied): string | null {
        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('getStatus');

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

        $accessWish = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessFamilyWish = $this->getMockBuilder(AccessFamilyWish::class)->getMock();
        $accessFamilyWish->method('isRegistrationCompleted')->willReturn(true);
        $accessWish->method('getAccessFamilyWish')->willReturn($accessFamilyWish);

        $registrationStatusService->method('getCurrentAccessWish')->with($access)->willReturn($accessWish);

        $wishesCountByStatus = [
            RegistrationStatusEnum::NEGOTIABLE()->getValue() => $negotiable,
            RegistrationStatusEnum::ACCEPTED()->getValue() => $accepted,
            RegistrationStatusEnum::PENDING()->getValue() => $pending,
            RegistrationStatusEnum::DENIED()->getValue() => $denied
        ];

        $registrationStatusService
            ->expects(self::once())
            ->method('countEducationsByRegistrationStatus')
            ->with($accessWish)
            ->willReturn($wishesCountByStatus);

        return $registrationStatusService->getStatus($access);
    }


    public function testGetStatusIsNegotiable(): void {
        $this->assertEquals(
            $this->_getStatusWith(1, 0, 0, 0),
            RegistrationStatusEnum::NEGOTIABLE()->getValue()
        );
        $this->assertEquals(
            RegistrationStatusEnum::NEGOTIABLE()->getValue(),
            $this->_getStatusWith(1, 0, 0, 1)

        );
    }

    public function testGetStatusIsPending(): void {
        $this->assertEquals(
            RegistrationStatusEnum::PENDING()->getValue(),
            $this->_getStatusWith(0, 0, 1, 0)
        );
        $this->assertEquals(
            RegistrationStatusEnum::PENDING()->getValue(),
            $this->_getStatusWith(1, 0, 1, 0)
        );
    }

    public function testGetStatusIsAccepted(): void {
        $this->assertEquals(
            RegistrationStatusEnum::ACCEPTED()->getValue(),
            $this->_getStatusWith(0, 1, 0, 0)
        );
        $this->assertEquals(
            RegistrationStatusEnum::ACCEPTED()->getValue(),
            $this->_getStatusWith(1, 1, 1, 1)
        );
    }

    public function testGetStatusIsDenied(): void {
        $this->assertEquals(
            RegistrationStatusEnum::DENIED()->getValue(),
            $this->_getStatusWith(0, 0, 0, 1)
        );
    }

    public function testGetStatusNoAccessWish(): void {
        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('getStatus');
        $access = $this->getMockBuilder(Access::class)->getMock();
        $registrationStatusService->method('getCurrentAccessWish')->with($access)->willReturn(null);

        $this->assertEquals(
            null,
            $registrationStatusService->getStatus($access)
        );
    }

    public function testGetStatusIncompleteRegistration(): void {
        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('getStatus');

        $access = $this->getMockBuilder(Access::class)->getMock();
        $accessWish = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessFamilyWish = $this->getMockBuilder(AccessFamilyWish::class)->getMock();
        $accessFamilyWish->method('isRegistrationCompleted')->willReturn(false);
        $accessWish->method('getAccessFamilyWish')->willReturn($accessFamilyWish);

        $registrationStatusService->method('getCurrentAccessWish')->with($access)->willReturn($accessWish);

        $this->assertEquals(
            null,
            $registrationStatusService->getStatus($access)
        );
    }

    public function testGetStatusNoCountByStatuses(): void {
        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('getStatus');

        $access = $this->getMockBuilder(Access::class)->getMock();
        $accessWish = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessFamilyWish = $this->getMockBuilder(AccessFamilyWish::class)->getMock();
        $accessFamilyWish->method('isRegistrationCompleted')->willReturn(true);
        $accessWish->method('getAccessFamilyWish')->willReturn($accessFamilyWish);

        $registrationStatusService->method('getCurrentAccessWish')->with($access)->willReturn($accessWish);

        $registrationStatusService
            ->method('countEducationsByRegistrationStatus')
            ->with($accessWish)
            ->willReturn(null);

        $this->assertEquals(
            null,
            $registrationStatusService->getStatus($access)
        );
    }

    public function testGetStatusExpired(): void {
        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('getStatus');

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

        $accessWish = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessFamilyWish = $this->getMockBuilder(AccessFamilyWish::class)->getMock();
        $accessFamilyWish->method('isRegistrationCompleted')->willReturn(true);
        $accessWish->method('getAccessFamilyWish')->willReturn($accessFamilyWish);

        $registrationStatusService->method('getCurrentAccessWish')->with($access)->willReturn($accessWish);

        $wishesCountByStatus = [
            RegistrationStatusEnum::NEGOTIABLE()->getValue() => 0,
            RegistrationStatusEnum::ACCEPTED()->getValue() => 1,
            RegistrationStatusEnum::PENDING()->getValue() => 0,
            RegistrationStatusEnum::DENIED()->getValue() => 0
        ];

        $registrationStatusService
            ->method('countEducationsByRegistrationStatus')
            ->with($accessWish)
            ->willReturn($wishesCountByStatus);



        $registrationStatusService
            ->method('getDaysSinceLastUpdate')
            ->with($accessWish)
            ->willReturn($registrationStatusService::DISPLAYING_TIME + 1);

        $this->assertEquals(
            null,
            $registrationStatusService->getStatus($access)
        );
    }

    public function testGetStatusNoResult(): void {
        $this->assertEquals(
            null,
            $this->_getStatusWith(0, 0, 0, 0)
        );
    }

    public function testGetDaysSinceLastUpdate(): void {
        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('getDaysSinceLastUpdate');

        DatesUtils::setFakeDatetime('2023-01-11');

        $accessWish = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessFamilyWish = $this->getMockBuilder(AccessFamilyWish::class)->getMock();
        $accessFamilyWish->method('getUpdateDate')->willReturn(new \DateTime('2023-01-01'));
        $accessWish->method('getAccessFamilyWish')->willReturn($accessFamilyWish);

        $this->assertEquals(
            10,
            $registrationStatusService->getDaysSinceLastUpdate($accessWish)
        );
    }

    public function testGetCurrentAccessWish(): void {

        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('getCurrentAccessWish');

        DatesUtils::setFakeDatetime('2023-01-01');

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

        $accessWish1 = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessWish1->method('getCreateDate')->willReturn(new \DateTime('2021-01-01'));

        $accessWish2 = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessWish2->method('getCreateDate')->willReturn(new \DateTime('2022-01-01'));

        $accessWish3 = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessWish3->method('getCreateDate')->willReturn(new \DateTime('2023-01-01'));

        $access->method('getAccessWishes')->willReturn(new ArrayCollection([$accessWish1, $accessWish2, $accessWish3]));

        $this->assertEquals(
            $accessWish3,
            $registrationStatusService->getCurrentAccessWish($access)
        );
    }

    public function testGetCurrentAccessWishNoResult(): void {

        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('getCurrentAccessWish');

        DatesUtils::setFakeDatetime('2023-01-01');

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

        $accessWish1 = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessWish1->method('getCreateDate')->willReturn(new \DateTime('2021-01-01'));

        $accessWish2 = $this->getMockBuilder(AccessWish::class)->getMock();
        $accessWish2->method('getCreateDate')->willReturn(new \DateTime('2022-01-01'));

        $access->method('getAccessWishes')->willReturn(new ArrayCollection([$accessWish1, $accessWish2]));

        $this->assertEquals(
            null,
            $registrationStatusService->getCurrentAccessWish($access)
        );
    }

    public function testCountEducationsByRegistrationStatus(): void {
        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('countEducationsByRegistrationStatus');

        $accessWish = $this->getMockBuilder(AccessWish::class)->getMock();

        $educationStudentWish1 = $this->getMockBuilder(EducationStudentWish::class)->getMock();
        $educationStudentWish1->expects(self::once())->method('getRegistrationStatus')->willReturn(RegistrationStatusEnum::NEGOTIABLE()->getValue());

        $educationStudentWish2 = $this->getMockBuilder(EducationStudentWish::class)->getMock();
        $educationStudentWish2->expects(self::once())->method('getRegistrationStatus')->willReturn(RegistrationStatusEnum::PENDING()->getValue());

        $educationStudentWish3 = $this->getMockBuilder(EducationStudentWish::class)->getMock();
        $educationStudentWish3->expects(self::once())->method('getRegistrationStatus')->willReturn(RegistrationStatusEnum::ACCEPTED()->getValue());

        $accessWish
            ->method('getEducationStudentWishes')
            ->willReturn(new ArrayCollection([$educationStudentWish1, $educationStudentWish2, $educationStudentWish3]));

        $reregistrationWish1 = $this->getMockBuilder(EducationStudentWish::class)->getMock();
        $reregistrationWish1->method('getWishRegistration')->willReturn(WishRegistrationEnum::REREGISTER()->getValue());
        $reregistrationWish1->expects(self::once())->method('getRegistrationStatus')->willReturn(RegistrationStatusEnum::ACCEPTED()->getValue());

        $reregistrationWish2 = $this->getMockBuilder(EducationStudentWish::class)->getMock();
        $reregistrationWish2->method('getWishRegistration')->willReturn(WishRegistrationEnum::REREGISTER()->getValue());
        $reregistrationWish2->expects(self::once())->method('getRegistrationStatus')->willReturn(RegistrationStatusEnum::DENIED()->getValue());

        $reregistrationWish3 = $this->getMockBuilder(EducationStudentWish::class)->getMock();
        $reregistrationWish3->method('getWishRegistration')->willReturn(WishRegistrationEnum::NO_INFORMATION()->getValue());
        $reregistrationWish3->expects(self::never())->method('getRegistrationStatus');

        $accessWish
            ->method('getEducationStudentReregistrationsWishes')
            ->willReturn(new ArrayCollection([$reregistrationWish1, $reregistrationWish2, $reregistrationWish3]));

        $expected = [
            RegistrationStatusEnum::NEGOTIABLE()->getValue() => 1,
            RegistrationStatusEnum::ACCEPTED()->getValue() => 2,
            RegistrationStatusEnum::PENDING()->getValue() => 1,
            RegistrationStatusEnum::DENIED()->getValue() => 1
        ];

        $this->assertEquals(
            $expected,
            $registrationStatusService->countEducationsByRegistrationStatus($accessWish)
        );
    }

    public function testCountEducationsByRegistrationStatusNoWishes(): void {
        $registrationStatusService = $this->makeRegistrationStatusServiceMockForMethod('countEducationsByRegistrationStatus');

        $accessWish = $this->getMockBuilder(AccessWish::class)->getMock();

        $accessWish
            ->method('getEducationStudentWishes')
            ->willReturn(new ArrayCollection([]));

        $accessWish
            ->method('getEducationStudentReregistrationsWishes')
            ->willReturn(new ArrayCollection([]));

        $this->assertEquals(
            null,
            $registrationStatusService->countEducationsByRegistrationStatus($accessWish)
        );
    }
}
