<?php

namespace App\Tests\Service\OnChange\Organization;

use App\Entity\Access\Access;
use App\Entity\Organization\Organization;
use App\Entity\Organization\Subdomain;
use App\Message\Command\MailerCommand;
use App\Message\Command\Typo3\Typo3UpdateCommand;
use App\Service\Mailer\Model\SubdomainChangeModel;
use App\Service\OnChange\OnChangeContext;
use App\Service\OnChange\Organization\OnSubdomainChange;
use App\Service\Organization\Utils as OrganizationUtils;
use App\Service\Typo3\BindFileService;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Security\Core\Security;

class OnSubdomainChangeTest extends TestCase
{
    private OrganizationUtils $organizationUtils;
    private Security $security;
    private BindFileService $bindFileService;
    private MessageBusInterface $messageBus;

    public function setUp():void
    {
        $this->organizationUtils = $this->getMockBuilder(OrganizationUtils::class)->disableOriginalConstructor()->getMock();
        $this->security = $this->getMockBuilder(Security::class)->disableOriginalConstructor()->getMock();
        $this->bindFileService = $this->getMockBuilder(BindFileService::class)->disableOriginalConstructor()->getMock();
        $this->messageBus = $this->getMockBuilder(MessageBusInterface::class)->disableOriginalConstructor()->getMock();
    }

    private function makeOnSubdomainChangeMock(string $methodName): MockObject | OnSubdomainChange {
        return $this->getMockBuilder(OnSubdomainChange::class)
            ->setConstructorArgs([$this->organizationUtils, $this->bindFileService, $this->messageBus, $this->security])
            ->setMethodsExcept([$methodName])
            ->getMock();
    }

    /**
     * @see OnSubdomainChange::validate()
     */
    public function testValidateIsOk(): void
    {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('validate');

        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
        $context->method('isPostRequest')->willReturn(true);

        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
        $organization->expects(self::once())->method('getSubdomains')->willReturn(new ArrayCollection([1,2]));

        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $subdomain->expects(self::once())->method('getOrganization')->willReturn($organization);

        $onSubdomainChange->validate($subdomain, $context);
    }

    /**
     * @see OnSubdomainChange::validate()
     */
    public function testValidateIsPutRequest(): void
    {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('validate');

        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
        $context->method('isPostRequest')->willReturn(false);

        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $subdomain->expects(self::never())->method('getOrganization');

        $onSubdomainChange->validate($subdomain, $context);
    }

    /**
     * @see OnSubdomainChange::validate()
     */
    public function testValidateMaxReached(): void
    {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('validate');

        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
        $context->method('isPostRequest')->willReturn(true);

        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
        $organization->expects(self::once())->method('getSubdomains')->willReturn(new ArrayCollection([1,2,3]));

        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $subdomain->expects(self::once())->method('getOrganization')->willReturn($organization);

        $this->expectException(\RuntimeException::class);
        $onSubdomainChange->validate($subdomain, $context);
    }

    /**
     * @see OnSubdomainChange::beforeChange()
     */
    public function testBeforeChangeActivated(): void
    {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('beforeChange');

        // Le sous-domaine qu'on vient d'activer
        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $subdomain->method('isActive')->willReturn(true);

        // Son état précédent
        $previousData = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $previousData->method('isActive')->willReturn(false);

        // Le sous domaine qui était actif jusqu'ici, et que le OnChange devrait désactiver
        $otherSubdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $otherSubdomain->method('isActive')->willReturn(true);
        $otherSubdomain->expects(self::once())->method('setActive')->with(false);

        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
        $organization->expects(self::once())->method('getSubdomains')->willReturn(new ArrayCollection([$subdomain, $otherSubdomain]));

        $subdomain->method('getOrganization')->willReturn($organization);

        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
        $context->method('previousData')->willReturn($previousData);
        $context->method('isPutRequest')->willReturn(true);
        $context->method('isPostRequest')->willReturn(false);

        $onSubdomainChange->beforeChange($subdomain, $context);
    }

    /**
     * @see OnSubdomainChange::onChange()
     */
    public function testOnChangeNoChange(): void
    {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('onChange');

        $this->bindFileService->expects(self::never())->method('registerSubdomain');
        $this->messageBus->expects(self::never())->method('dispatch');
        $onSubdomainChange->expects(self::never())->method('sendEmailAfterSubdomainChange');

        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();

        $onSubdomainChange->onChange($subdomain, $context);
    }

    /**
     * @see OnSubdomainChange::onChange()
     */
    public function testOnChangeActivated(): void {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('onChange');

        $this->bindFileService->expects(self::never())->method('registerSubdomain');
        $this->messageBus
            ->expects(self::once())
            ->method('dispatch')
            ->with(self::isInstanceOf(Typo3UpdateCommand::class))
            ->willReturn(new Envelope(new Typo3UpdateCommand(1)));

        $onSubdomainChange->expects(self::once())->method('sendEmailAfterSubdomainChange');

        // Le sous-domaine qu'on vient d'activer
        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $subdomain->method('isActive')->willReturn(true);

        // Son état précédent
        $previousData = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $previousData->method('isActive')->willReturn(false);

        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
        $organization->method('getId')->willReturn(1);

        $subdomain->method('getOrganization')->willReturn($organization);

        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
        $context->method('previousData')->willReturn($previousData);
        $context->method('isPutRequest')->willReturn(true);
        $context->method('isPostRequest')->willReturn(false);

        $onSubdomainChange->onChange($subdomain, $context);
    }

    /**
     * @see OnSubdomainChange::onChange()
     */
    public function testOnChangeCreated(): void {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('onChange');

        $this->bindFileService->expects(self::once())->method('registerSubdomain');
        $this->messageBus
            ->expects(self::once())
            ->method('dispatch')
            ->with(self::isInstanceOf(Typo3UpdateCommand::class))
            ->willReturn(new Envelope(new Typo3UpdateCommand(1)));

        $onSubdomainChange->expects(self::once())->method('sendEmailAfterSubdomainChange');

        // Le sous-domaine qu'on vient d'activer
        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $subdomain->method('isActive')->willReturn(true);

        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();
        $organization->method('getId')->willReturn(1);

        $subdomain->method('getOrganization')->willReturn($organization);

        $context = $this->getMockBuilder(OnChangeContext::class)->disableOriginalConstructor()->getMock();
        $context->method('previousData')->willReturn(null);
        $context->method('isPutRequest')->willReturn(false);
        $context->method('isPostRequest')->willReturn(true);

        $onSubdomainChange->onChange($subdomain, $context);
    }

    /**
     * @see OnSubdomainChange::sendEmailAfterSubdomainChange()
     */
    public function testSendEmailAfterSubdomainChange(): void {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('sendEmailAfterSubdomainChange');

        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $subdomainChangeModel = $this->getMockBuilder(SubdomainChangeModel::class)->disableOriginalConstructor()->getMock();

        $onSubdomainChange
            ->expects(self::once())
            ->method('getMailModel')
            ->with($subdomain)
            ->willReturn($subdomainChangeModel);

        $this->messageBus
            ->expects(self::once())
            ->method('dispatch')
            ->with(self::isInstanceOf(MailerCommand::class))
            ->willReturn(new Envelope(new MailerCommand($subdomainChangeModel)));

        $onSubdomainChange->sendEmailAfterSubdomainChange($subdomain);
    }

    /**
     * @see OnSubdomainChange::getMailModel()
     */
    public function testGetMailModel(): void {
        $onSubdomainChange = $this->makeOnSubdomainChangeMock('getMailModel');

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

        $this->organizationUtils
            ->expects(self::once())
            ->method('getOrganizationWebsite')
            ->with($organization)
            ->willReturn('mysubdomain.opentalent.fr');

        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
        $access->expects(self::once())->method('getId')->willReturn(1);
        $this->security
            ->expects(self::once())
            ->method('getUser')
            ->willReturn($access);

        $subdomain = $this->getMockBuilder(Subdomain::class)->disableOriginalConstructor()->getMock();
        $subdomain->expects(self::exactly(2))->method('getOrganization')->willReturn($organization);
        $subdomain->expects(self::once())->method('getId')->willReturn(1);

        $mailerModel = $onSubdomainChange->getMailModel($subdomain);

        $this->assertInstanceOf(SubdomainChangeModel::class, $mailerModel);
        $this->assertEquals($mailerModel->getSenderId(), 1);
        $this->assertEquals($mailerModel->getOrganizationId(), 1);
        $this->assertEquals($mailerModel->getSubdomainId(), 1);
        $this->assertEquals($mailerModel->getUrl(), 'mysubdomain.opentalent.fr');
    }
}
