<?php
namespace App\Tests\Unit\Service\Access;

use App\ApiResources\Profile\AccessProfile;
use App\ApiResources\Profile\OrganizationProfile;
use App\Entity\Access\Access;
use App\Entity\Core\File;
use App\Entity\Organization\Organization;
use App\Entity\Person\Person;
use App\Repository\Access\AccessRepository;
use App\Service\Access\AccessProfileCreator;
use App\Service\Access\Utils as AccessUtils;
use App\Service\Organization\OrganizationProfileCreator;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

class AccessProfileCreatorTest extends TestCase
{
    private MockObject | OrganizationProfileCreator $organizationProfileCreator;
    private MockObject | AccessRepository $accessRepository;
    private MockObject | AccessUtils $accessUtils;
    private MockObject | AccessProfileCreator $accessProfileCreator;
    private MockObject | AccessProfile $accessProfile;
    private MockObject | Collection $emptyCollection;
    private MockObject | Collection $nonEmptyCollection;
    private MockObject | Organization $organization;
    private MockObject | Access $access;
    private MockObject | OrganizationProfile $organizationProfile;

    public function setUp():void
    {
        $this->organizationProfileCreator = $this->getMockBuilder(OrganizationProfileCreator::class)->disableOriginalConstructor()->getMock();
        $this->accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
        $this->accessUtils = $this->getMockBuilder(AccessUtils::class)->disableOriginalConstructor()->getMock();

        $this->emptyCollection = $this->getMockBuilder(Collection::class)->getMock();
        $this->emptyCollection->method('isEmpty')->willReturn(true);

        $this->nonEmptyCollection = $this->getMockBuilder(Collection::class)->getMock();
        $this->nonEmptyCollection->method('isEmpty')->willReturn(false);

        $this->organization = $this->getMockBuilder(Organization::class)->getMock();
        $this->access = $this->getMockBuilder(Access::class)->getMock();
        $this->organizationProfile = $this->getMockBuilder(OrganizationProfile::class)->getMock();
        $this->accessProfile = $this->getMockBuilder(AccessProfile::class)->getMock();
    }

    /**
     * @see AccessProfileCreator::getAccessProfile()
     */
    public function testGetAccessProfileFailed(): void
    {

        $this->accessProfileCreator = $this
            ->getMockBuilder(AccessProfileCreator::class)
            ->setConstructorArgs([$this->organizationProfileCreator, $this->accessRepository, $this->accessUtils])
            ->setMethodsExcept(['getAccessProfile'])
            ->getMock();

        $this->accessRepository
            ->method('findAllValidAccesses')
            ->with($this->access)
            ->willReturn([]);

        $this->expectException(AuthenticationException::class);
        $this->accessProfileCreator->getAccessProfile($this->access);
    }

    /**
     * Access valide avec multicompte et famille
     *
     * @see AccessProfileCreator::getAccessProfile()
     */
    public function testGetAccessProfile(): void
    {
        $accessProfileCreator = $this
            ->getMockBuilder(AccessProfileCreator::class)
            ->setConstructorArgs([$this->organizationProfileCreator, $this->accessRepository, $this->accessUtils])
            ->setMethodsExcept(['getAccessProfile'])
            ->getMock();

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

        // Find valid accesses
        $this->accessRepository
            ->method('findAllValidAccesses')
            ->with($this->access)
            ->willReturn([$this->access, $otherAccess1, $otherAccess2]);

        // create the profile
        $accessProfileCreator
            ->expects(self::once())
            ->method('createCompleteAccessProfile')
            ->with($this->access)
            ->willReturn($this->accessProfile);

        // Multi compte
        $otherOrganization1 = $this->getMockBuilder(Organization::class)->getMock();
        $otherOrganization2 = $this->getMockBuilder(Organization::class)->getMock();

        $otherAccess1->method('getOrganization')->willReturn($otherOrganization1);
        $otherAccess2->method('getOrganization')->willReturn($otherOrganization2);

        $otherOrganizationProfile1 = $this->getMockBuilder(OrganizationProfile::class)->getMock();
        $otherOrganizationProfile2 = $this->getMockBuilder(OrganizationProfile::class)->getMock();

        $this->organizationProfileCreator
            ->expects(self::exactly(2))
            ->method('createLightOrganizationProfile')
            ->willReturnMap(
                [
                    [$otherOrganization1, $otherOrganizationProfile1],
                    [$otherOrganization2, $otherOrganizationProfile2],
                ]
            );

        $this->accessUtils
            ->expects(self::once())
            ->method('filterAccesses')
            ->with([$this->access, $otherAccess1, $otherAccess2], $this->access)
            ->willReturn([$otherAccess1, $otherAccess2]);

        $this->accessProfile
            ->expects(self::exactly(2))
            ->method('addMultiAccess')
            ->withConsecutive(
                [$otherOrganizationProfile1],
                [$otherOrganizationProfile2]
            );

        // Accesses famille
        $children1 = $this->getMockBuilder(Access::class)->getMock();
        $children2 = $this->getMockBuilder(Access::class)->getMock();
        $childrenProfile1 = $this->getMockBuilder(AccessProfile::class)->getMock();
        $childrenProfile2 = $this->getMockBuilder(AccessProfile::class)->getMock();

        $this->access->expects(self::once())->method('getChildren')->willReturn(new ArrayCollection([$children1, $children2]));

        $accessProfileCreator
            ->expects(self::exactly(2))
            ->method('createLightAccessProfile')
            ->willReturnMap([
                [$children1, $childrenProfile1],
                [$children2, $childrenProfile2]
            ]);

        $this->accessProfile
            ->expects(self::exactly(2))
            ->method('addFamilyAccess')
            ->withConsecutive(
                [$childrenProfile1],
                [$childrenProfile2]
            );

        $accessProfileCreator->getAccessProfile($this->access);
    }

    /**
     * Test d'un access en compte switch
     *
     * @see AccessProfileCreator::getAccessProfile()
     */
    public function testGetAccessProfileSwitch(): void {
        $accessProfileCreator = $this
            ->getMockBuilder(AccessProfileCreator::class)
            ->setConstructorArgs([$this->organizationProfileCreator, $this->accessRepository, $this->accessUtils])
            ->setMethodsExcept(['getAccessProfile'])
            ->getMock();

        $this->accessRepository
            ->method('findAllValidAccesses')
            ->with($this->access)
            ->willReturn([$this->access]);

        $accessProfileCreator
            ->expects(self::once())
            ->method('createCompleteAccessProfile')
            ->with($this->access)
            ->willReturn($this->accessProfile);

        $this->accessUtils
            ->expects(self::once())
            ->method('filterAccesses')
            ->with([$this->access], $this->access)
            ->willReturn([]);

        $this->access->method('getChildren')->willReturn(new ArrayCollection([]));

        $originalAccess = $this->getMockBuilder(Access::class)->getMock();
        $originalAccessProfile = $this->getMockBuilder(AccessProfile::class)->getMock();

        $accessProfileCreator
            ->expects(self::once())
            ->method('createLightAccessProfile')
            ->with($originalAccess)
            ->willReturn($originalAccessProfile);

        $this->accessProfile->expects(self::once())->method('setOriginalAccess')->with($originalAccessProfile);

        $accessProfileCreator->getAccessProfile($this->access, $originalAccess);
    }

    /**
     * Setup mocks for the tests on createCompleteAccessProfile
     * @see AccessProfileCreator::createCompleteAccessProfile()
     */
    private function setupCreateCompleteAccessProfile(): void
    {
        $this->accessProfileCreator = $this
            ->getMockBuilder(AccessProfileCreator::class)
            ->setConstructorArgs([$this->organizationProfileCreator, $this->accessRepository, $this->accessUtils])
            ->setMethodsExcept(['createCompleteAccessProfile'])
            ->getMock();

        $this->access->expects(self::once())->method('getAdminAccess')->willReturn(true);
        $this->access->expects(self::once())->method('getHistorical')->willReturn(['present' => true]);
        $this->access->expects(self::once())->method('getOrganization')->willReturn($this->organization);

        $this->accessUtils->expects(self::once())->method('getAllRoles')->with($this->access)->willReturn(['FOO']);

        $this->organizationProfileCreator
            ->expects(self::once())
            ->method('createCompleteOrganizationProfile')
            ->with($this->organization)
            ->willReturn($this->organizationProfile);

        $this->accessProfile->method('setIsAdminAccess')->willReturnSelf();
        $this->accessProfile->method('setRoles')->willReturnSelf();
        $this->accessProfile->method('setHistorical')->willReturnSelf();
        $this->accessProfile->method('setOrganization')->willReturnSelf();
        $this->accessProfile->method('setIsGuardian')->willReturnSelf();
        $this->accessProfile->method('setIsPayor')->willReturnSelf();

        $this->accessProfileCreator
            ->expects(self::once())
            ->method('createLightAccessProfile')
            ->with($this->access)
            ->willReturn($this->accessProfile);
    }

    /**
     * Default situation: the access is neither guardian nor payer or admin
     *
     * @see AccessProfileCreator::createCompleteAccessProfile()
     */
    public function testCreateCompleteAccessProfile(): void
    {
        $this->setupCreateCompleteAccessProfile();

        $this->access->method('getChildren')->willReturn($this->emptyCollection);
        $this->access->method('getBillingPayers')->willReturn($this->emptyCollection);
        $this->access->method('getBillingReceivers')->willReturn($this->emptyCollection);
        $this->access->method('getAccessIntangibles')->willReturn($this->emptyCollection);

        $this->accessProfile->expects(self::once())->method('setIsAdminAccess')->with(true)->willReturnSelf();
        $this->accessProfile->expects(self::once())->method('setRoles')->with(['FOO'])->willReturnSelf();
        $this->accessProfile->expects(self::once())->method('setHistorical')->with(['present' => true])->willReturnSelf();
        $this->accessProfile->expects(self::once())->method('setOrganization')->with($this->organizationProfile)->willReturnSelf();
        $this->accessProfile->expects(self::once())->method('setIsGuardian')->with(false)->willReturnSelf();
        $this->accessProfile->expects(self::once())->method('setIsPayor')->with(false)->willReturnSelf();

        $this->accessProfileCreator->createCompleteAccessProfile($this->access);
    }

    /**
     * If the access has children, isGuardian shall be set to true
     *
     * @see AccessProfileCreator::createCompleteAccessProfile()
     */
    public function testCreateCompleteAccessProfileIsGuardian(): void
    {
        $this->setupCreateCompleteAccessProfile();

        $this->access->method('getChildren')->willReturn($this->nonEmptyCollection);
        $this->access->method('getBillingPayers')->willReturn($this->emptyCollection);
        $this->access->method('getBillingReceivers')->willReturn($this->emptyCollection);
        $this->access->method('getAccessIntangibles')->willReturn($this->emptyCollection);

        $this->accessProfile->expects(self::once())->method('setIsGuardian')->with(true)->willReturnSelf();

        $this->accessProfileCreator->createCompleteAccessProfile($this->access);
    }

    /**
     * If the access has billingPayers, isPayor shall be set to true
     *
     * @see AccessProfileCreator::createCompleteAccessProfile()
     */
    public function testCreateCompleteAccessProfileIsPayorWithBillingPayer(): void
    {
        $this->setupCreateCompleteAccessProfile();

        $this->access->method('getChildren')->willReturn($this->emptyCollection);
        $this->access->method('getBillingPayers')->willReturn($this->nonEmptyCollection);
        $this->access->method('getBillingReceivers')->willReturn($this->emptyCollection);
        $this->access->method('getAccessIntangibles')->willReturn($this->emptyCollection);

        $this->accessProfile->expects(self::once())->method('setIsPayor')->with(true)->willReturnSelf();

        $this->accessProfileCreator->createCompleteAccessProfile($this->access);
    }

    /**
     * If the access has no billingPayers but has AccessIntangible, isPayor shall be set to true
     *
     * @see AccessProfileCreator::createCompleteAccessProfile()
     */
    public function testCreateCompleteAccessProfileIsPayorWithAccessIntangible(): void
    {
        $this->setupCreateCompleteAccessProfile();

        $this->access->method('getChildren')->willReturn($this->emptyCollection);
        $this->access->method('getBillingPayers')->willReturn($this->emptyCollection);
        $this->access->method('getBillingReceivers')->willReturn($this->emptyCollection);
        $this->access->method('getAccessIntangibles')->willReturn($this->nonEmptyCollection);

        $this->accessProfile->expects(self::once())->method('setIsPayor')->with(true)->willReturnSelf();

        $this->accessProfileCreator->createCompleteAccessProfile($this->access);
    }

    /**
     * If the access has no billingPayers, has AccessIntangible but also have children, isPayor shall be set to false
     *
     * @see AccessProfileCreator::createCompleteAccessProfile()
     */
    public function testCreateCompleteAccessProfileNotPayorWithAccessIntangibleBecauseChildren(): void
    {
        $this->setupCreateCompleteAccessProfile();

        $this->access->method('getChildren')->willReturn($this->nonEmptyCollection);
        $this->access->method('getBillingPayers')->willReturn($this->emptyCollection);
        $this->access->method('getBillingReceivers')->willReturn($this->emptyCollection);
        $this->access->method('getAccessIntangibles')->willReturn($this->nonEmptyCollection);

        $this->accessProfile->expects(self::once())->method('setIsPayor')->with(false)->willReturnSelf();

        $this->accessProfileCreator->createCompleteAccessProfile($this->access);
    }

    /**
     * @see AccessProfileCreator::createLightAccessProfile()
     */
    public function testCreateLightAccessProfile(): void
    {
        $accessProfileCreator = $this
            ->getMockBuilder(AccessProfileCreator::class)
            ->setConstructorArgs([$this->organizationProfileCreator, $this->accessRepository, $this->accessUtils])
            ->setMethodsExcept(['createLightAccessProfile'])
            ->getMock();

        $image = $this->getMockBuilder(File::class)->getMock();
        $image->expects(self::once())->method('getId')->willReturn(123);

        $person = $this->getMockBuilder(Person::class)->getMock();
        $person->expects(self::once())->method('getName')->willReturn('Foo');
        $person->expects(self::once())->method('getGivenName')->willReturn('Bar');
        $person->expects(self::once())->method('getGender')->willReturn('MR');
        $person->expects(self::once())->method('getImage')->willReturn($image);

        $this->access->expects(self::once())->method('getId')->willReturn(1);
        $this->access->method('getOrganization')->willReturn($this->organization);
        $this->access->method('getPerson')->willReturn($person);
        $this->access->expects(self::once())->method('getActivityYear')->willReturn(2020);
        $this->access->expects(self::once())->method('getSuperAdminAccess')->willReturn(false);

        $this->organizationProfileCreator
            ->expects(self::once())
            ->method('createLightOrganizationProfile')
            ->with($this->organization)
            ->willReturn($this->organizationProfile);

        $accessProfile = $accessProfileCreator->createLightAccessProfile($this->access);

        $this->assertEquals(1, $accessProfile->getId());
        $this->assertEquals('Foo', $accessProfile->getName());
        $this->assertEquals('Bar', $accessProfile->getGivenName());
        $this->assertEquals('MR', $accessProfile->getGender());
        $this->assertEquals(2020, $accessProfile->getActivityYear());
        $this->assertEquals(123, $accessProfile->getAvatarId());
        $this->assertEquals(false, $accessProfile->getIsSuperAdminAccess());
        $this->assertEquals($this->organizationProfile, $accessProfile->getOrganization());
    }
}
