<?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\Parser;
use App\Service\Utils\Reflection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

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

class ModuleTest extends TestCase
{
    private const OPENTALENT_CONFIG = __DIR__.'/../../../config/opentalent';

    private MockObject | Reflection $reflection;
    private MockObject | Parser $parser;

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

    /**
     * @see Module::getOrganizationModules()
     */
    public function testGetOrganizationModules(): void
    {
        $module = $this->getMockBuilder(Module::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getOrganizationModules'])
            ->getMock();

        $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->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModuleBySettings'])
            ->getMock();

        $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->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModulesByConditions'])
            ->getMock();

        $module->method('getModuleByConditionsConfig')->willReturn(
            ['opentalent' =>
                ['modulesbyconditions' =>
                    ['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->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModulesByConditions'])
            ->getMock();

        $module->method('getModuleByConditionsConfig')->willReturn(
            ['opentalent' =>
                ['modulesbyconditions' =>
                    ['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->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModulesByProductConfiguration'])
            ->getMock();

        $module->method('getModuleConfig')->willReturn(['opentalent' => ['products' => ['artist' => ['modules' => ['foo']]]]]);

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

    /**
     * @see Module::getModulesByProductConfiguration()
     */
    public function testGetModulesByProductConfigurationExtend(): void
    {
        $module = $this->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModulesByProductConfiguration'])
            ->getMock();

        $module->method('getModuleConfig')->willReturn(
            ['opentalent' =>
                ['products' =>
                    [
                        '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)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModulesByProductConfiguration'])
            ->getMock();

        $module->method('getModuleConfig')->willReturn(['opentalent' => ['products' => ['artist' => ['modules' => ['foo']]]]]);

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

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

    /**
     * @see Module::getModuleConfig()
     */
    public function testGetModuleConfig(): void {
        $module = $this->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModuleConfig'])
            ->getMock();

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

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

    /**
     * @see Module::loadModuleConfig()
     */
    public function testLoadModuleConfig(): void {
        $module = $this->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['loadModuleConfig'])
            ->getMock();

        $this->parser
            ->expects(self::once())
            ->method('yamlParser')
            ->with(self::OPENTALENT_CONFIG, 'products.yaml')
            ->willReturn(['foo']);

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

    /**
     * @see Module::getModuleByConditionsConfig()
     */
    public function testGetModuleByConditionsConfig(): void {
        $module = $this->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModuleByConditionsConfig'])
            ->getMock();

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

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

    /**
     * @see Module::loadModuleByConditionsConfig()
     */
    public function testLoadModuleByConditionsConfig(): void {
        $module = $this->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['loadModuleByConditionsConfig'])
            ->getMock();

        $this->parser->expects(self::once())
            ->method('yamlParser')
            ->with(self::OPENTALENT_CONFIG, 'modulesbyconditions.yaml')
            ->willReturn(['foo']);

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

    /**
     * @see Module::getModuleByResourceName()
     */
    public function testGetModuleByResourceName(): void {
        $module = $this->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModuleByResourceName'])
            ->getMock();


        $module->method('getModuleConfig')->willReturn(
            ['opentalent' =>
                ['modules' =>
                    ['Core' => ['entities' => ['foo', 'bar']]]
                ]
            ]
        );

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

    /**
     * @see Module::getModuleByResourceName()
     */
    public function testGetModuleByResourceNameNotFound(): void {
        $module = $this->getMockBuilder(TestableModule::class)
            ->setConstructorArgs([$this->reflection, $this->parser, self::OPENTALENT_CONFIG])
            ->setMethodsExcept(['getModuleByResourceName'])
            ->getMock();

        $module->method('getModuleConfig')->willReturn(
            ['opentalent' =>
                ['modules' =>
                    ['Core' => ['entities' => ['bar']]]
                ]
            ]
        );

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