<?php

namespace App\Tests\Unit\Service\Security;

use App\Entity\Organization\Organization;
use App\Entity\Organization\Settings;
use App\Service\Cotisation\Utils as CotisationUtils;
use App\Service\Security\Module;
use App\Service\Utils\ConfigUtils;
use App\Service\Utils\Parser\YamlParser;
use App\Service\Utils\Reflection;
use Hoa\Iterator\Mock;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use PHPUnit\Util\Test;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

class TestableModule extends Module {
    public function getModuleByConditionsConfig(): array { return parent::getModuleByConditionsConfig(); }
}

class ModuleTest extends TestCase
{
    private MockObject | Reflection $reflection;
    private MockObject | ParameterBagInterface $parameterBag;

    public function setUp():void
    {
        $this->reflection = $this->getMockBuilder(Reflection::class)->disableOriginalConstructor()->getMock();
        $this->parameterBag = $this->getMockBuilder(ParameterBagInterface::class)->disableOriginalConstructor()->getMock();
    }

    public function getMockForMethod(string $methodName): MockObject | TestableModule {
        return $this->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parameterBag])
            ->setMethodsExcept([$methodName])
            ->getMock();
    }

    /**
     * @see Module::getOrganizationModules()
     */
    public function testGetOrganizationModules(): void
    {
        $module = $this->getMockForMethod('getOrganizationModules');

        $module->expects(self::once())->method('getModuleBySettings')->willReturn(['Sms']);
        $module->expects(self::once())->method('getModulesByConditions')->willReturn(['CotisationCall']);

        $settings = $this->getMockBuilder(Settings::class)->disableOriginalConstructor()->getMock();
        $settings->expects(self::once())->method('getProduct')->willReturn('school');

        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
        $organization->expects(self::once())->method('getSettings')->willReturn($settings);

        $module->expects(self::once())->method('getModulesByProductConfiguration')->willReturn(['Notification']);

        $this->assertEqualsCanonicalizing(
            ['Sms', 'CotisationCall', 'Notification'],
            $module->getOrganizationModules($organization)
        );
    }

    /**
     * @see Module::getModuleBySettings()
     */
    public function testGetModuleBySettings(): void
    {
        $module = $this->getMockForMethod('getModuleBySettings');

        $settings = $this->getMockBuilder(Settings::class)->getMock();
        $settings
            ->expects($this->once())
            ->method('getModules')
            ->willReturn(["Sms" => true]);

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $organization
            ->expects($this->once())
            ->method('getSettings')
            ->willReturn($settings);

        $this->assertContains('Sms', $module->getModuleBySettings($organization)) ;
    }

    /**
     * @see Module::getModulesByConditions()
     */
    public function testGetModulesByConditions(): void
    {
        $module = $this->getMockForMethod('getModulesByConditions');

        $this->parameterBag->method('get')->with('opentalent.modulesbyconditions')->willReturn(
            ['CotisationCall' => [
                'roles' => ['ROLE_COTISATION'],
                'conditions' => [
                    'service' => [
                        'name' => CotisationUtils::class,
                        'function' => 'isLastParentAndCMF'
                    ]
                ]
            ]]
        );

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $this->reflection
            ->method('dynamicInvokeServiceWithArgsAndMethod')
            ->with(CotisationUtils::class, 'isLastParentAndCMF', array($organization))
            ->willReturn(true);

        $this->assertContains('CotisationCall', $module->getModulesByConditions($organization)) ;
    }

    /**
     * @see Module::getModulesByConditions()
     */
    public function testGetModulesByConditionsLogicError(): void
    {
        $module = $this->getMockForMethod('getModulesByConditions');

        $this->parameterBag->method('get')->with('opentalent.modulesbyconditions')->willReturn(
            ['CotisationCall' => []]
        );

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $this->reflection
            ->method('dynamicInvokeServiceWithArgsAndMethod')
            ->with(CotisationUtils::class, 'isLastParentAndCMF', array($organization))
            ->willThrowException(new \Exception());

        $this->expectException(\LogicException::class);

        $module->getModulesByConditions($organization);
    }

    /**
     * @see Module::getModulesByProductConfiguration()
     */
    public function testGetModulesByProductConfiguration(): void
    {
        $module = $this->getMockForMethod('getModulesByProductConfiguration');

        $this->parameterBag->method('get')->with('opentalent.products')->willReturn(
            ['artist' => ['modules' => ['foo']]]
        );

        $this->assertEquals(['foo'], $module->getModulesByProductConfiguration('artist')) ;
    }

    /**
     * @see Module::getModulesByProductConfiguration()
     */
    public function testGetModulesByProductConfigurationExtend(): void
    {
        $module = $this->getMockForMethod('getModulesByProductConfiguration');

        $this->parameterBag->method('get')->with('opentalent.products')->willReturn(
            [
                'artist' => ['modules' => ['foo']],
                'artist_premium' => ['extend' => 'artist', 'modules' => ['bar']]
            ]
        );

        $this->assertEqualsCanonicalizing(
            ['foo', 'bar'],
            $module->getModulesByProductConfiguration('artist-premium')
        ) ;
    }

    /**
     * @see Module::getModulesByProductConfiguration()
     */
    public function testGetModulesByProductConfigurationAccessDenied(): void
    {
        $module = $this->getMockBuilder(TestableModule::class);

        $module = $this->getMockForMethod('getModulesByProductConfiguration');

        $this->parameterBag->method('get')->with('opentalent.products')->willReturn(
            ['artist' => ['modules' => ['foo']]]
        );

        $this->expectException(AccessDeniedHttpException::class);
        $this->expectExceptionMessage('The product artist_premium does not exist !');

        $module->getModulesByProductConfiguration('artist-premium');
    }

    /**
     * @see Module::getModuleByResourceName()
     */
    public function testGetModuleByResourceName(): void {
        $module = $this->getMockForMethod('getModuleByResourceName');

        $this->parameterBag->method('get')->with('opentalent.modules')->willReturn(
            ['Core' => ['entities' => ['foo', 'bar']]]
        );

        $this->assertEquals('Core', $module->getModuleByResourceName('foo'));
    }

    /**
     * @see Module::getModuleByResourceName()
     */
    public function testGetModuleByResourceNameNotFound(): void {
        $module = $this->getMockForMethod('getModuleByResourceName');

        $this->parameterBag->method('get')->with('opentalent.modules')->willReturn(
            ['Core' => ['entities' => ['bar']]]
        );

        $this->assertNull($module->getModuleByResourceName('foo'));
    }
}
