<?php

declare(strict_types=1);

namespace App\Tests\Unit\Service\Organization;

use App\ApiResources\Organization\OrganizationCreationRequest;
use App\ApiResources\Organization\OrganizationDeletionRequest;
use App\ApiResources\Organization\OrganizationMemberCreationRequest;
use App\Entity\Access\Access;
use App\Entity\Access\FunctionType;
use App\Entity\Core\ContactPoint;
use App\Entity\Core\Country;
use App\Entity\Education\Cycle;
use App\Entity\Network\Network;
use App\Entity\Network\NetworkOrganization;
use App\Entity\Organization\Organization;
use App\Entity\Organization\OrganizationAddressPostal;
use App\Entity\Organization\Parameters;
use App\Entity\Organization\Settings;
use App\Entity\Organization\Subdomain;
use App\Entity\Person\Person;
use App\Entity\Person\PersonAddressPostal;
use App\Enum\Access\FunctionEnum;
use App\Enum\Core\ContactPointTypeEnum;
use App\Enum\Education\CycleEnum;
use App\Enum\Network\NetworkEnum;
use App\Enum\Organization\AddressPostalOrganizationTypeEnum;
use App\Enum\Organization\LegalEnum;
use App\Enum\Organization\PrincipalTypeEnum;
use App\Enum\Organization\SettingsProductEnum;
use App\Enum\Person\AddressPostalPersonTypeEnum;
use App\Enum\Person\GenderEnum;
use App\Repository\Access\FunctionTypeRepository;
use App\Repository\Core\CountryRepository;
use App\Repository\Organization\OrganizationIdentificationRepository;
use App\Repository\Organization\OrganizationRepository;
use App\Repository\Person\PersonRepository;
use App\Service\ApiLegacy\ApiLegacyRequestService;
use App\Service\Dolibarr\DolibarrApiService;
use App\Service\File\FileManager;
use App\Service\Organization\OrganizationFactory;
use App\Service\Organization\Utils as OrganizationUtils;
use App\Service\Typo3\BindFileService;
use App\Service\Typo3\SubdomainService;
use App\Service\Typo3\Typo3Service;
use App\Service\Utils\DatesUtils;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberUtil;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\ResponseInterface;

class TestableOrganizationFactory extends OrganizationFactory
{
    public function setPhoneNumberUtil(PhoneNumberUtil $phoneNumberUtil): void
    {
        $this->phoneNumberUtil = $phoneNumberUtil;
    }

    public function interruptIfOrganizationExists(OrganizationCreationRequest $organizationCreationRequest): void
    {
        parent::interruptIfOrganizationExists($organizationCreationRequest);
    }

    public function validateSubdomain(string $subdomainValue): void
    {
        parent::validateSubdomain($subdomainValue);
    }

    public function makeOrganizationWithRelations(
        OrganizationCreationRequest $organizationCreationRequest,
    ): Organization {
        return parent::makeOrganizationWithRelations($organizationCreationRequest);
    }

    public function makeOrganization(OrganizationCreationRequest $organizationCreationRequest): Organization
    {
        return parent::makeOrganization($organizationCreationRequest);
    }

    public function makeParameters(OrganizationCreationRequest $organizationCreationRequest): Parameters
    {
        return parent::makeParameters($organizationCreationRequest);
    }

    public function makeSettings(OrganizationCreationRequest $organizationCreationRequest): Settings
    {
        return parent::makeSettings($organizationCreationRequest);
    }

    public function makePostalAddress(OrganizationCreationRequest $organizationCreationRequest): OrganizationAddressPostal
    {
        return parent::makePostalAddress($organizationCreationRequest);
    }

    public function makeContactPoint(OrganizationCreationRequest $organizationCreationRequest): ContactPoint
    {
        return parent::makeContactPoint($organizationCreationRequest);
    }

    public function makeNetworkOrganization(OrganizationCreationRequest $organizationCreationRequest): NetworkOrganization
    {
        return parent::makeNetworkOrganization($organizationCreationRequest);
    }

    public function makeAdminAccess(OrganizationCreationRequest $organizationCreationRequest): Access
    {
        return parent::makeAdminAccess($organizationCreationRequest);
    }

    public function makeCycles(): array
    {
        return parent::makeCycles();
    }

    public function makeAccess(int|OrganizationMemberCreationRequest $creationRequestData, FunctionEnum $function, \DateTime $creationDate, ?int $authorId): Access
    {
        return parent::makeAccess($creationRequestData, $function, $creationDate, $authorId);
    }

    public function makePersonPostalAddress(OrganizationMemberCreationRequest $organizationMemberCreationRequest, \DateTime $creationDate, ?int $authorId): PersonAddressPostal
    {
        return parent::makePersonPostalAddress($organizationMemberCreationRequest, $creationDate, $authorId);
    }

    public function makePersonContactPoint(OrganizationMemberCreationRequest $organizationMemberCreationRequest, \DateTime $creationDate, ?int $authorId): ContactPoint
    {
        return parent::makePersonContactPoint($organizationMemberCreationRequest, $creationDate, $authorId);
    }

    public function makeSubdomain(OrganizationCreationRequest $organizationCreationRequest): Subdomain
    {
        return parent::makeSubdomain($organizationCreationRequest);
    }

    public function createTypo3Website(Organization $organization): ?int
    {
        return parent::createTypo3Website($organization);
    }

    public function updateAdminassosDb(Organization $organization): void
    {
        parent::updateAdminassosDb($organization);
    }

    public function normalizeIdentificationField(string $value): string
    {
        return parent::normalizeIdentificationField($value);
    }

    public function deleteTypo3Website(int $organizationId): void
    {
        parent::deleteTypo3Website($organizationId);
    }

    public function switchDolibarrSocietyToProspect(int $organizationId): void
    {
        parent::switchDolibarrSocietyToProspect($organizationId);
    }

    public function getFutureOrphanPersons(Organization $organization): array
    {
        return parent::getFutureOrphanPersons($organization);
    }
}

class OrganizationFactoryTest extends TestCase
{
    private readonly MockObject|SubdomainService $subdomainService;
    private readonly MockObject|OrganizationRepository $organizationRepository;
    private readonly MockObject|CountryRepository $countryRepository;
    private readonly MockObject|OrganizationUtils $organizationUtils;
    private readonly MockObject|Typo3Service $typo3Service;
    private readonly MockObject|DolibarrApiService $dolibarrApiService;
    private readonly MockObject|EntityManagerInterface $entityManager;
    private readonly MockObject|PersonRepository $personRepository;
    private readonly MockObject|BindFileService $bindFileService;
    private readonly MockObject|LoggerInterface $logger;
    private readonly MockObject|OrganizationIdentificationRepository $organizationIdentificationRepository;
    private readonly MockObject|ApiLegacyRequestService $apiLegacyRequestService;
    private readonly MockObject|PhoneNumberUtil $phoneNumberUtil;
    private readonly MockObject|FunctionTypeRepository $functionTypeRepository;

    public function setUp(): void
    {
        $this->subdomainService = $this->getMockBuilder(SubdomainService::class)->disableOriginalConstructor()->getMock();
        $this->organizationRepository = $this->getMockBuilder(OrganizationRepository::class)->disableOriginalConstructor()->getMock();
        $this->countryRepository = $this->getMockBuilder(CountryRepository::class)->disableOriginalConstructor()->getMock();
        $this->organizationUtils = $this->getMockBuilder(OrganizationUtils::class)->disableOriginalConstructor()->getMock();
        $this->typo3Service = $this->getMockBuilder(Typo3Service::class)->disableOriginalConstructor()->getMock();
        $this->dolibarrApiService = $this->getMockBuilder(DolibarrApiService::class)->disableOriginalConstructor()->getMock();
        $this->entityManager = $this->getMockBuilder(EntityManagerInterface::class)->disableOriginalConstructor()->getMock();
        $this->personRepository = $this->getMockBuilder(PersonRepository::class)->disableOriginalConstructor()->getMock();
        $this->bindFileService = $this->getMockBuilder(BindFileService::class)->disableOriginalConstructor()->getMock();
        $this->logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
        $this->organizationIdentificationRepository = $this->getMockBuilder(OrganizationIdentificationRepository::class)->disableOriginalConstructor()->getMock();
        $this->apiLegacyRequestService = $this->getMockBuilder(ApiLegacyRequestService::class)->disableOriginalConstructor()->getMock();
        $this->phoneNumberUtil = $this->getMockBuilder(PhoneNumberUtil::class)->disableOriginalConstructor()->getMock();
        $this->functionTypeRepository = $this->getMockBuilder(FunctionTypeRepository::class)->disableOriginalConstructor()->getMock();
        $this->fileManager = $this->getMockBuilder(FileManager::class)->disableOriginalConstructor()->getMock();
    }

    public function tearDown(): void
    {
        DatesUtils::clearFakeDatetime();
    }

    private function getOrganizationFactoryMockFor(string $methodName): TestableOrganizationFactory|MockObject
    {
        $organizationFactory = $this
            ->getMockBuilder(TestableOrganizationFactory::class)
            ->setConstructorArgs(
                [
                    $this->subdomainService,
                    $this->organizationRepository,
                    $this->countryRepository,
                    $this->organizationUtils,
                    $this->typo3Service,
                    $this->dolibarrApiService,
                    $this->entityManager,
                    $this->personRepository,
                    $this->bindFileService,
                    $this->organizationIdentificationRepository,
                    $this->apiLegacyRequestService,
                    $this->functionTypeRepository,
                    $this->fileManager,
                ])
            ->setMethodsExcept(['setLoggerInterface', 'setPhoneNumberUtil', $methodName])
            ->getMock();

        $organizationFactory->setLoggerInterface($this->logger);
        $organizationFactory->setPhoneNumberUtil($this->phoneNumberUtil);

        return $organizationFactory;
    }

    public function testCreate(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('create');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSubdomain')->willReturn('subdomain');
        $organizationCreationRequest->method('isClient')->willReturn(false);
        $organizationCreationRequest->method('getCreateWebsite')->willReturn(true);

        $this->entityManager->expects(self::once())->method('beginTransaction');

        $organizationFactory->expects(self::once())->method('interruptIfOrganizationExists');
        $organizationFactory->expects(self::once())->method('validateSubdomain')->with('subdomain');

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeOrganizationWithRelations')
            ->with($organizationCreationRequest)
            ->willReturn($organization);

        $this->entityManager->expects(self::once())->method('persist')->with($organization);
        $this->entityManager->expects(self::once())->method('flush');
        $this->entityManager->expects(self::once())->method('commit');

        $this->dolibarrApiService
            ->expects(self::once())
            ->method('createSociety')
            ->with($organization, false)
            ->willReturn(456);

        $this->bindFileService
            ->expects(self::once())
            ->method('registerSubdomain')
            ->with('subdomain');

        $organizationFactory
            ->expects(self::once())
            ->method('createTypo3Website')
            ->with($organization)
            ->willReturn(789);

        $this->logger
            ->method('info')
            ->withConsecutive(
                ["Start the creation of a new organization named 'foo'"],
                ["Subdomain is valid and available : 'subdomain'"],
                ['Organization created with all its relations'],
                ['Organization persisted in the DB'],
                ['New dolibarr structure created (uid : 456)'],
                ['Subdomain registered'],
                ['Typo3 website created (root uid: 789)'],
                ['Adminassos db updated']
            );

        $organizationCreationRequest
            ->expects(self::once())
            ->method('setStatus')
            ->with(OrganizationCreationRequest::STATUS_OK);

        $result = $organizationFactory->create($organizationCreationRequest);

        $this->assertEquals(
            $organization,
            $result
        );
    }

    public function testCreateWithRollback(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('create');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSubdomain')->willReturn('subdomain');
        $organizationCreationRequest->method('isClient')->willReturn(false);
        $organizationCreationRequest->method('getCreateWebsite')->willReturn(true);

        $this->entityManager->expects(self::once())->method('beginTransaction');

        $organizationFactory->expects(self::once())->method('interruptIfOrganizationExists');
        $organizationFactory->expects(self::once())->method('validateSubdomain')->with('subdomain');

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeOrganizationWithRelations')
            ->with($organizationCreationRequest)
            ->willReturn($organization);

        $this->entityManager->expects(self::once())->method('persist')->with($organization);
        $this->entityManager->expects(self::once())->method('flush')->willThrowException(new \RuntimeException('some error'));
        $this->entityManager->expects(self::once())->method('rollback');

        $this->dolibarrApiService
            ->expects(self::never())
            ->method('createSociety');

        $this->bindFileService
            ->expects(self::never())
            ->method('registerSubdomain');

        $organizationFactory
            ->expects(self::never())
            ->method('createTypo3Website');

        $this->logger
            ->method('info')
            ->withConsecutive(
                ["Start the creation of a new organization named 'foo'"],
                ["Subdomain is valid and available : 'subdomain'"],
                ['Organization created with all its relations']
            );

        $this->logger
            ->method('critical')
            ->with(
                $this->matchesRegularExpression('/^An error happened, operation cancelled\nRuntimeException: some error.*/')
            );

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

        $result = $organizationFactory->create($organizationCreationRequest);
    }

    public function testCreateWithExistingOrganization(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('create');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');

        $this->entityManager->expects(self::once())->method('beginTransaction');

        $organizationFactory->expects(self::once())->method('interruptIfOrganizationExists')->willThrowException(new \RuntimeException('An organization named foo already exists'));

        $organizationFactory->expects(self::never())->method('validateSubdomain');

        $organizationFactory
            ->expects(self::never())
            ->method('makeOrganizationWithRelations');

        $this->entityManager->expects(self::never())->method('persist');
        $this->entityManager->expects(self::never())->method('flush');
        $this->entityManager->expects(self::once())->method('rollback');

        $this->dolibarrApiService
            ->expects(self::never())
            ->method('createSociety');

        $this->bindFileService
            ->expects(self::never())
            ->method('registerSubdomain');

        $organizationFactory
            ->expects(self::never())
            ->method('createTypo3Website');

        $this->logger
            ->method('info')
            ->withConsecutive(
                ["Start the creation of a new organization named 'foo'"],
            );

        $this->logger
            ->method('critical')
            ->with(
                $this->matchesRegularExpression("/^An error happened, operation cancelled\nRuntimeException: An organization named foo already exists.*/")
            );

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('An organization named foo already exists');

        $result = $organizationFactory->create($organizationCreationRequest);
    }

    public function testCreateNoWebsiteAndIsClient(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('create');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSubdomain')->willReturn('subdomain');
        $organizationCreationRequest->method('isClient')->willReturn(true);
        $organizationCreationRequest->method('getCreateWebsite')->willReturn(false);

        $organizationFactory->expects(self::once())->method('interruptIfOrganizationExists');

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeOrganizationWithRelations')
            ->with($organizationCreationRequest)
            ->willReturn($organization);

        $this->dolibarrApiService
            ->method('createSociety')
            ->with($organization, true)
            ->willReturn(456);

        $organizationFactory
            ->expects(self::never())
            ->method('createTypo3Website');

        $this->logger
            ->expects(self::once())
            ->method('warning')
            ->with('Typo3 website creation was not required');

        $result = $organizationFactory->create($organizationCreationRequest);

        $this->assertEquals(
            $organization,
            $result
        );
    }

    public function testCreateWithErrors(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('create');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSubdomain')->willReturn('subdomain');
        $organizationCreationRequest->method('isClient')->willReturn(true);
        $organizationCreationRequest->method('getCreateWebsite')->willReturn(true);

        $organizationFactory->expects(self::once())->method('interruptIfOrganizationExists');

        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeOrganizationWithRelations')
            ->with($organizationCreationRequest)
            ->willReturn($organization);

        $this->dolibarrApiService
            ->method('createSociety')
            ->with($organization, true)
            ->willThrowException(new \RuntimeException('An error happened'));

        $this->bindFileService
            ->method('registerSubdomain')
            ->with('subdomain')
            ->willThrowException(new \RuntimeException('An error happened'));

        $organizationFactory
            ->method('createTypo3Website')
            ->with($organization)
            ->willThrowException(new \RuntimeException('An error happened'));

        $organizationFactory
            ->method('updateAdminassosDb')
            ->with($organization)
            ->willThrowException(new \RuntimeException('An error happened'));

        $this->logger
            ->expects(self::exactly(4))
            ->method('critical')
            ->willReturnOnConsecutiveCalls([
                'An error happened while creating the dolibarr society, please proceed manually.',
                'An error happened while updating the bind file, please proceed manually.',
                'An error happened while creating the typo3 website, please proceed manually.',
                'An error happened while updating the adminassos db, please proceed manually.',
            ]);

        $this->logger
            ->expects(self::once())
            ->method('warning')
            ->with('-- Operation ended with errors, check the logs for more information --');

        $organizationCreationRequest
            ->expects(self::once())
            ->method('setStatus')
            ->with(OrganizationCreationRequest::STATUS_OK_WITH_ERRORS);

        $result = $organizationFactory->create($organizationCreationRequest);

        $this->assertEquals(
            $organization,
            $result
        );
    }

    public function testInterruptIfOrganizationExistsNotExisting(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('interruptIfOrganizationExists');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSiretNumber')->willReturn('');
        $organizationCreationRequest->method('getWaldecNumber')->willReturn('');
        $organizationCreationRequest->method('getIdentifier')->willReturn('');
        $organizationCreationRequest->method('getStreetAddress1')->willReturn('part1');
        $organizationCreationRequest->method('getStreetAddress2')->willReturn('part2');
        $organizationCreationRequest->method('getStreetAddress3')->willReturn('part3');
        $organizationCreationRequest->method('getCity')->willReturn('Paris');
        $organizationCreationRequest->method('getPostalCode')->willReturn('75000');

        $this->organizationIdentificationRepository
            ->expects(self::exactly(2))
            ->method('findOneBy')
            ->willReturn(null);

        $organizationFactory
            ->method('normalizeIdentificationField')
            ->willReturnMap([
                ['foo', 'foo'],
                ['part1 part2 part3', 'part1 part2 part3'],
            ]);

        $organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
    }

    public function testInterruptIfOrganizationExistsNotExistingNonExistingWithIdentifiers(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('interruptIfOrganizationExists');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSiretNumber')->willReturn('123456');
        $organizationCreationRequest->method('getWaldecNumber')->willReturn('W123456');
        $organizationCreationRequest->method('getIdentifier')->willReturn('FR000000000000');
        $organizationCreationRequest->method('getStreetAddress1')->willReturn('part1');
        $organizationCreationRequest->method('getStreetAddress2')->willReturn('part2');
        $organizationCreationRequest->method('getStreetAddress3')->willReturn('part3');
        $organizationCreationRequest->method('getCity')->willReturn('Paris');
        $organizationCreationRequest->method('getPostalCode')->willReturn('75000');

        $organizationFactory
            ->method('normalizeIdentificationField')
            ->willReturnMap([
                ['foo', 'foo'],
                ['part1 part2 part3', 'part1 part2 part3'],
            ]);

        $this->organizationIdentificationRepository
            ->expects(self::exactly(5))
            ->method('findOneBy')
            ->willReturnMap([
                [['siretNumber' => '123456'], null, null],
                [['waldecNumber' => 'W123456'], null, null],
                [['identifier' => 'FR000000000000'], null, null],
                [['normalizedName' => 'foo', 'addressCity' => 'Paris'], null, null],
                [['normalizedAddress' => 'part1 part2 part3', 'addressCity' => 'Paris', 'postalCode' => '75000'], null, null],
            ]);

        $organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
    }

    public function testInterruptIfOrganizationExistsExistingWithSiret(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('interruptIfOrganizationExists');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSiretNumber')->willReturn('123456');
        $organizationCreationRequest->method('getWaldecNumber')->willReturn('W123456');
        $organizationCreationRequest->method('getIdentifier')->willReturn('FR000000000000');
        $organizationCreationRequest->method('getStreetAddress1')->willReturn('part1');
        $organizationCreationRequest->method('getStreetAddress2')->willReturn('part2');
        $organizationCreationRequest->method('getStreetAddress3')->willReturn('part3');
        $organizationCreationRequest->method('getCity')->willReturn('Paris');
        $organizationCreationRequest->method('getPostalCode')->willReturn('75000');

        $organizationFactory
            ->method('normalizeIdentificationField')
            ->willReturnMap([
                ['foo', 'foo'],
                ['part1 part2 part3', 'part1 part2 part3'],
            ]);

        $organization = $this->getMockBuilder(Organization::class)->getMock();

        $this->organizationIdentificationRepository
            ->method('findOneBy')
            ->willReturnMap([
                [['siretNumber' => '123456'], null, $organization],
                [['waldecNumber' => 'W123456'], null, null],
                [['identifier' => 'FR000000000000'], null, null],
                [['normalizedName' => 'foo', 'addressCity' => 'Paris'], null, null],
                [['normalizedAddress' => 'part1 part2 part3', 'addressCity' => 'Paris', 'postalCode' => '75000'], null, null],
            ]);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage("This siret number is already registered : '123456'");

        $organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
    }

    public function testInterruptIfOrganizationExistsExistingWithWaldec(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('interruptIfOrganizationExists');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSiretNumber')->willReturn('123456');
        $organizationCreationRequest->method('getWaldecNumber')->willReturn('W123456');
        $organizationCreationRequest->method('getIdentifier')->willReturn('FR000000000000');
        $organizationCreationRequest->method('getStreetAddress1')->willReturn('part1');
        $organizationCreationRequest->method('getStreetAddress2')->willReturn('part2');
        $organizationCreationRequest->method('getStreetAddress3')->willReturn('part3');
        $organizationCreationRequest->method('getCity')->willReturn('Paris');
        $organizationCreationRequest->method('getPostalCode')->willReturn('75000');

        $organizationFactory
            ->method('normalizeIdentificationField')
            ->willReturnMap([
                ['foo', 'foo'],
                ['part1 part2 part3', 'part1 part2 part3'],
            ]);

        $organization = $this->getMockBuilder(Organization::class)->getMock();

        $this->organizationIdentificationRepository
            ->method('findOneBy')
            ->willReturnMap([
                [['siretNumber' => '123456'], null, null],
                [['waldecNumber' => 'W123456'], null, $organization],
                [['identifier' => 'FR000000000000'], null, null],
                [['normalizedName' => 'foo', 'addressCity' => 'Paris'], null, null],
                [['normalizedAddress' => 'part1 part2 part3', 'addressCity' => 'Paris', 'postalCode' => '75000'], null, null],
            ]);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage("This RNA identifier (waldec number) is already registered : 'W123456'");

        $organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
    }

    public function testInterruptIfOrganizationExistsExistingWithCMFIdentifier(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('interruptIfOrganizationExists');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSiretNumber')->willReturn('123456');
        $organizationCreationRequest->method('getWaldecNumber')->willReturn('W123456');
        $organizationCreationRequest->method('getIdentifier')->willReturn('FR000000000000');
        $organizationCreationRequest->method('getStreetAddress1')->willReturn('part1');
        $organizationCreationRequest->method('getStreetAddress2')->willReturn('part2');
        $organizationCreationRequest->method('getStreetAddress3')->willReturn('part3');
        $organizationCreationRequest->method('getCity')->willReturn('Paris');
        $organizationCreationRequest->method('getPostalCode')->willReturn('75000');

        $organizationFactory
            ->method('normalizeIdentificationField')
            ->willReturnMap([
                ['foo', 'foo'],
                ['part1 part2 part3', 'part1 part2 part3'],
            ]);

        $organization = $this->getMockBuilder(Organization::class)->getMock();

        $this->organizationIdentificationRepository
            ->method('findOneBy')
            ->willReturnMap([
                [['siretNumber' => '123456'], null, null],
                [['waldecNumber' => 'W123456'], null, null],
                [['identifier' => 'FR000000000000'], null, $organization],
                [['normalizedName' => 'foo', 'addressCity' => 'Paris'], null, null],
                [['normalizedAddress' => 'part1 part2 part3', 'addressCity' => 'Paris', 'postalCode' => '75000'], null, null],
            ]);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage("This CMF identifier is already registered : 'FR000000000000'");

        $organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
    }

    public function testInterruptIfOrganizationExistsExistingWithSameNameAndCity(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('interruptIfOrganizationExists');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSiretNumber')->willReturn('123456');
        $organizationCreationRequest->method('getWaldecNumber')->willReturn('W123456');
        $organizationCreationRequest->method('getIdentifier')->willReturn('FR000000000000');
        $organizationCreationRequest->method('getStreetAddress1')->willReturn('part1');
        $organizationCreationRequest->method('getStreetAddress2')->willReturn('part2');
        $organizationCreationRequest->method('getStreetAddress3')->willReturn('part3');
        $organizationCreationRequest->method('getCity')->willReturn('Paris');
        $organizationCreationRequest->method('getPostalCode')->willReturn('75000');

        $organizationFactory
            ->method('normalizeIdentificationField')
            ->willReturnMap([
                ['foo', 'foo'],
                ['part1 part2 part3', 'part1 part2 part3'],
            ]);

        $organization = $this->getMockBuilder(Organization::class)->getMock();

        $this->organizationIdentificationRepository
            ->method('findOneBy')
            ->willReturnMap([
                [['siretNumber' => '123456'], null, null],
                [['waldecNumber' => 'W123456'], null, null],
                [['identifier' => 'FR000000000000'], null, null],
                [['normalizedName' => 'foo', 'addressCity' => 'Paris'], null, $organization],
                [['normalizedAddress' => 'part1 part2 part3', 'addressCity' => 'Paris', 'postalCode' => '75000'], null, null],
            ]);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage("An organization named 'foo' already exists in Paris");

        $organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
    }

    public function testInterruptIfOrganizationExistsExistingWithSameAddress(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('interruptIfOrganizationExists');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getName')->willReturn('foo');
        $organizationCreationRequest->method('getSiretNumber')->willReturn('123456');
        $organizationCreationRequest->method('getWaldecNumber')->willReturn('W123456');
        $organizationCreationRequest->method('getIdentifier')->willReturn('FR000000000000');
        $organizationCreationRequest->method('getStreetAddress1')->willReturn('part1');
        $organizationCreationRequest->method('getStreetAddress2')->willReturn('part2');
        $organizationCreationRequest->method('getStreetAddress3')->willReturn('part3');
        $organizationCreationRequest->method('getCity')->willReturn('Paris');
        $organizationCreationRequest->method('getPostalCode')->willReturn('75000');

        $organizationFactory
            ->method('normalizeIdentificationField')
            ->willReturnMap([
                ['foo', 'foo'],
                ['part1 part2 part3', 'part1 part2 part3'],
            ]);

        $organization = $this->getMockBuilder(Organization::class)->getMock();

        $this->organizationIdentificationRepository
            ->method('findOneBy')
            ->willReturnMap([
                [['siretNumber' => '123456'], null, null],
                [['waldecNumber' => 'W123456'], null, null],
                [['identifier' => 'FR000000000000'], null, null],
                [['normalizedName' => 'foo', 'addressCity' => 'Paris'], null, null],
                [['normalizedAddress' => 'part1 part2 part3', 'addressCity' => 'Paris', 'postalCode' => '75000'], null, $organization],
            ]);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('An organization already exists at this address.');

        $organizationFactory->interruptIfOrganizationExists($organizationCreationRequest);
    }

    public function testValidateSubdomain(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('validateSubdomain');

        $this->subdomainService->expects(self::once())->method('isValidSubdomain')->willReturn(true);
        $this->subdomainService->expects(self::once())->method('isReservedSubdomain')->willReturn(false);
        $this->subdomainService->expects(self::once())->method('isRegistered')->willReturn(false);

        $organizationFactory->validateSubdomain('foo');
    }

    public function testValidateSubdomainIsNotValid(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('validateSubdomain');

        $this->subdomainService->method('isValidSubdomain')->willReturn(false);
        $this->subdomainService->method('isReservedSubdomain')->willReturn(false);
        $this->subdomainService->method('isRegistered')->willReturn(false);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Not a valid subdomain : foo');

        $organizationFactory->validateSubdomain('foo');
    }

    public function testValidateSubdomainIsReserved(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('validateSubdomain');

        $this->subdomainService->method('isValidSubdomain')->willReturn(true);
        $this->subdomainService->method('isReservedSubdomain')->willReturn(true);
        $this->subdomainService->method('isRegistered')->willReturn(false);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('This subdomain is not available : foo');

        $organizationFactory->validateSubdomain('foo');
    }

    public function testValidateSubdomainIsRegistered(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('validateSubdomain');

        $this->subdomainService->method('isValidSubdomain')->willReturn(true);
        $this->subdomainService->method('isReservedSubdomain')->willReturn(false);
        $this->subdomainService->method('isRegistered')->willReturn(true);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('This subdomain is already registered : foo');

        $organizationFactory->validateSubdomain('foo');
    }

    public function testMakeOrganizationWithRelations(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeOrganizationWithRelations');

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getCreationDate')->willReturn($creationDate);
        $organizationCreationRequest->method('getAuthorId')->willReturn(1);

        $organization = $this->getMockBuilder(Organization::class)->getMock();

        // Création de l'organisation
        $organizationFactory
            ->expects(self::once())
            ->method('makeOrganization')
            ->with($organizationCreationRequest)
            ->willReturn($organization);

        // Création des Parameters
        $parameters = $this->getMockBuilder(Parameters::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeParameters')
            ->with($organizationCreationRequest)
            ->willReturn($parameters);

        $organization->expects(self::once())->method('setParameters')->with($parameters);

        // Création des Settings
        $settings = $this->getMockBuilder(Settings::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeSettings')
            ->with($organizationCreationRequest)
            ->willReturn($settings);

        $organization->expects(self::once())->method('setSettings')->with($settings);

        // Création de l'adresse postale
        $organizationAddressPostal = $this->getMockBuilder(OrganizationAddressPostal::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makePostalAddress')
            ->with($organizationCreationRequest)
            ->willReturn($organizationAddressPostal);

        $organization->expects(self::once())->method('addOrganizationAddressPostal')->with($organizationAddressPostal);

        // Création du point de contact
        $contactPoint = $this->getMockBuilder(ContactPoint::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeContactPoint')
            ->with($organizationCreationRequest)
            ->willReturn($contactPoint);

        $organization->expects(self::once())->method('addContactPoint')->with($contactPoint);

        // Rattachement au réseau
        $networkOrganization = $this->getMockBuilder(NetworkOrganization::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeNetworkOrganization')
            ->with($organizationCreationRequest)
            ->willReturn($networkOrganization);

        $organization->expects(self::once())->method('addNetworkOrganization')->with($networkOrganization);

        // Créé l'admin
        $adminAccess = $this->getMockBuilder(Access::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeAdminAccess')
            ->with($organizationCreationRequest)
            ->willReturn($adminAccess);

        // Le `$organization->expects(...)->method('addAccess')` est implémenté plus loin,
        // après la création du président et du directeur.

        // Création des cycles
        $cycle1 = $this->getMockBuilder(Cycle::class)->getMock();
        $cycle2 = $this->getMockBuilder(Cycle::class)->getMock();
        $cycle3 = $this->getMockBuilder(Cycle::class)->getMock();

        $organizationFactory
            ->expects(self::once())
            ->method('makeCycles')
            ->willReturn([$cycle1, $cycle2, $cycle3]);

        $organization->expects(self::exactly(3))->method('addCycle')->withConsecutive([$cycle1], [$cycle2], [$cycle3]);

        // Création du président et du directeur
        $organizationMemberCreationRequest1 = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getPresident')->willReturn($organizationMemberCreationRequest1);

        $organizationMemberCreationRequest2 = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getDirector')->willReturn($organizationMemberCreationRequest2);

        $access1 = $this->getMockBuilder(Access::class)->getMock();
        $access2 = $this->getMockBuilder(Access::class)->getMock();
        $organizationFactory
            ->expects(self::exactly(2))
            ->method('makeAccess')
            ->willReturnMap([
                [$organizationMemberCreationRequest1, FunctionEnum::PRESIDENT, $creationDate, 1, $access1],
                [$organizationMemberCreationRequest2, FunctionEnum::DIRECTOR, $creationDate, 1, $access2],
            ]);

        $organization
            ->expects(self::exactly(3))
            ->method('addAccess')
            ->withConsecutive([$adminAccess], [$access1], [$access2]);

        // Création du sous-domaine
        $subdomain = $this->getMockBuilder(Subdomain::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makeSubdomain')
            ->with($organizationCreationRequest)
            ->willReturn($subdomain);

        $organization->expects(self::once())->method('addSubdomain')->with($subdomain);

        // Enregistrement du sous domaine dans Parameters (retrocompatibilité v1)
        $organization->method('getParameters')->willReturn($parameters);

        $organizationCreationRequest->method('getSubdomain')->willReturn('foo');
        $parameters->expects(self::once())->method('setSubDomain')->with('foo');
        $parameters->expects(self::once())->method('setOtherWebsite')->with('https://foo.opentalent.fr');

        $result = $organizationFactory->makeOrganizationWithRelations($organizationCreationRequest);

        $this->assertEquals(
            $result,
            $organization
        );
    }

    public function testMakeOrganizationWithRelationsNoPresidentNoDirector(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeOrganizationWithRelations');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organization = $this->getMockBuilder(Organization::class)->getMock();

        // Création de l'organisation
        $organizationFactory
            ->expects(self::once())
            ->method('makeOrganization')
            ->with($organizationCreationRequest)
            ->willReturn($organization);

        $organizationCreationRequest->method('getPresident')->willReturn(null);
        $organizationCreationRequest->method('getDirector')->willReturn(null);

        // Un seul appel, pour l'adminAccess
        $organization
            ->expects(self::once())
            ->method('addAccess');

        $organizationFactory->makeOrganizationWithRelations($organizationCreationRequest);
    }

    public function testMakeOrganization(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeOrganization');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();

        $organizationCreationRequest->method('getName')->willReturn('My Organization');
        $organizationCreationRequest->method('getLegalStatus')->willReturn(LegalEnum::ASSOCIATION_LAW_1901);
        $organizationCreationRequest->method('getPrincipalType')->willReturn(PrincipalTypeEnum::ARTISTIC_EDUCATION_ONLY);

        $organization = $organizationFactory->makeOrganization($organizationCreationRequest);

        $this->assertEquals(
            'My Organization',
            $organization->getName()
        );

        $this->assertEquals(
            LegalEnum::ASSOCIATION_LAW_1901,
            $organization->getLegalStatus()
        );

        $this->assertEquals(
            PrincipalTypeEnum::ARTISTIC_EDUCATION_ONLY,
            $organization->getPrincipalType()
        );
    }

    public function testMakeParameters(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeParameters');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();

        $parameters = $organizationFactory->makeParameters($organizationCreationRequest);

        $this->assertInstanceOf(Parameters::class, $parameters);
    }

    public function testMakeSettings(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeSettings');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();

        $organizationCreationRequest->method('getProduct')->willReturn(SettingsProductEnum::ARTIST_PREMIUM);

        $settings = $organizationFactory->makeSettings($organizationCreationRequest);

        $this->assertEquals(
            SettingsProductEnum::ARTIST_PREMIUM,
            $settings->getProduct()
        );
    }

    public function testMakePostalAddress(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makePostalAddress');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getStreetAddress1')->willReturn('address1');
        $organizationCreationRequest->method('getStreetAddress2')->willReturn('address2');
        $organizationCreationRequest->method('getStreetAddress3')->willReturn('address3');
        $organizationCreationRequest->method('getPostalCode')->willReturn('00000');
        $organizationCreationRequest->method('getCity')->willReturn('city');
        $organizationCreationRequest->method('getCountryId')->willReturn(1);

        $country = $this->getMockBuilder(Country::class)->getMock();

        $this->countryRepository->expects(self::once())->method('find')->with(1)->willReturn($country);

        $organizationAddressPostal = $organizationFactory->makePostalAddress($organizationCreationRequest);

        $this->assertEquals(
            AddressPostalOrganizationTypeEnum::ADDRESS_HEAD_OFFICE,
            $organizationAddressPostal->getType()
        );

        $addressPostal = $organizationAddressPostal->getAddressPostal();

        $this->assertEquals(
            'address1',
            $addressPostal->getStreetAddress()
        );

        $this->assertEquals(
            'address2',
            $addressPostal->getStreetAddressSecond()
        );

        $this->assertEquals(
            'address3',
            $addressPostal->getStreetAddressThird()
        );

        $this->assertEquals(
            '00000',
            $addressPostal->getPostalCode()
        );

        $this->assertEquals(
            'city',
            $addressPostal->getAddressCity()
        );

        $this->assertEquals(
            $country,
            $addressPostal->getAddressCountry()
        );
    }

    public function testMakePostalAddressNonExistingCountry(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makePostalAddress');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getCountryId')->willReturn(1);

        $this->countryRepository->expects(self::once())->method('find')->with(1)->willReturn(null);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('No country found for id 1');

        $organizationFactory->makePostalAddress($organizationCreationRequest);
    }

    public function testMakeContactPoint(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeContactPoint');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();

        $organizationCreationRequest->method('getPhoneNumber')->willReturn('+33102030405');
        $organizationCreationRequest->method('getEmail')->willReturn('contact@domain.net');

        $this->phoneNumberUtil
            ->method('isPossibleNumber')
            ->with('+33102030405')
            ->willReturn(true);

        $phoneNumber = $this->getMockBuilder(PhoneNumber::class)->getMock();

        $this->phoneNumberUtil
            ->expects(self::once())
            ->method('parse')
            ->with('+33102030405')
            ->willReturn($phoneNumber);

        $contactPoint = $organizationFactory->makeContactPoint($organizationCreationRequest);

        $this->assertEquals(
            'contact@domain.net',
            $contactPoint->getEmail()
        );

        $this->assertEquals(
            $phoneNumber,
            $contactPoint->getTelphone()
        );
    }

    public function testMakeContactPointInvalidPhoneNumber(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeContactPoint');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();

        $organizationCreationRequest->method('getPhoneNumber')->willReturn('invalid');
        $organizationCreationRequest->method('getEmail')->willReturn('contact@domain.net');

        $this->phoneNumberUtil
            ->method('isPossibleNumber')
            ->with('invalid')
            ->willReturn(false);

        $phoneNumber = $this->getMockBuilder(PhoneNumber::class)->getMock();

        $this->phoneNumberUtil
            ->expects(self::never())
            ->method('parse');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Phone number is invalid or missing');

        $organizationFactory->makeContactPoint($organizationCreationRequest);
    }

    public function testMakeNetworkOrganization(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');

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

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getParentId')->willReturn(123);

        $parent = $this->getMockBuilder(Organization::class)->getMock();
        $settings = $this->getMockBuilder(Settings::class)->getMock();
        $settings->method('getProduct')->willReturn(SettingsProductEnum::MANAGER);
        $parent->method('getSettings')->willReturn($settings);
        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn($parent);

        $network = $this->getMockBuilder(Network::class)->getMock();
        $networkOrganization = $this->getMockBuilder(NetworkOrganization::class)->getMock();
        $networkOrganization->method('getNetwork')->willReturn($network);
        $this->organizationUtils
            ->expects(self::once())
            ->method('getActiveNetworkOrganization')
            ->with($parent)
            ->willReturn($networkOrganization);

        $networkOrganization = $organizationFactory->makeNetworkOrganization($organizationCreationRequest);

        $this->assertEquals(
            $parent,
            $networkOrganization->getParent()
        );

        $this->assertEquals(
            $network,
            $networkOrganization->getNetwork()
        );

        $this->assertEquals(
            '2024-01-01',
            $networkOrganization->getStartDate()->format('Y-m-d')
        );
    }

    public function testMakeNetworkOrganizationMissingParent(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getParentId')->willReturn(123);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('No parent organization found for id 123');

        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn(null);

        $organizationFactory->makeNetworkOrganization($organizationCreationRequest);
    }

    public function testMakeNetworkOrganizationParentIsNotManager(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');

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

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getParentId')->willReturn(123);

        $parent = $this->getMockBuilder(Organization::class)->getMock();
        $settings = $this->getMockBuilder(Settings::class)->getMock();
        $settings->method('getProduct')->willReturn(SettingsProductEnum::SCHOOL);
        $parent->method('getSettings')->willReturn($settings);
        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn($parent);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage("Parent organization must have the product 'manager' (actual product: 'school')");

        $organizationFactory->makeNetworkOrganization($organizationCreationRequest);
    }

    public function testMakeNetworkOrganizationMissingNetwork(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getParentId')->willReturn(123);

        $parent = $this->getMockBuilder(Organization::class)->getMock();
        $settings = $this->getMockBuilder(Settings::class)->getMock();
        $settings->method('getProduct')->willReturn(SettingsProductEnum::MANAGER);
        $parent->method('getSettings')->willReturn($settings);
        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn($parent);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('No network found for parent 123');

        $this->organizationRepository->expects(self::once())->method('find')->with(123)->willReturn($parent);

        $organizationFactory->makeNetworkOrganization($organizationCreationRequest);
    }

    public function testMakeNetworkOrganizationIsCMFInvalidIdentifier(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getParentId')->willReturn(123);
        $organizationCreationRequest->method('getIdentifier')->willReturn('invalid');

        $parent = $this->getMockBuilder(Organization::class)->getMock();
        $settings = $this->getMockBuilder(Settings::class)->getMock();
        $settings->method('getProduct')->willReturn(SettingsProductEnum::MANAGER);
        $parent->method('getSettings')->willReturn($settings);
        $this->organizationRepository->method('find')->with(123)->willReturn($parent);

        $network = $this->getMockBuilder(Network::class)->getMock();
        $network->method('getId')->willReturn(NetworkEnum::CMF->value);

        $networkOrganization = $this->getMockBuilder(NetworkOrganization::class)->getMock();
        $networkOrganization->method('getNetwork')->willReturn($network);

        $this->organizationUtils
            ->method('getActiveNetworkOrganization')
            ->with($parent)
            ->willReturn($networkOrganization);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('CMF identifier is missing or invalid.');

        $organizationFactory->makeNetworkOrganization($organizationCreationRequest);
    }

    public function testMakeNetworkOrganizationIsNotCMFInvalidIdentifier(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeNetworkOrganization');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();
        $organizationCreationRequest->method('getParentId')->willReturn(123);
        $organizationCreationRequest->method('getIdentifier')->willReturn('invalid');

        $parent = $this->getMockBuilder(Organization::class)->getMock();
        $settings = $this->getMockBuilder(Settings::class)->getMock();
        $settings->method('getProduct')->willReturn(SettingsProductEnum::MANAGER);
        $parent->method('getSettings')->willReturn($settings);
        $this->organizationRepository->method('find')->with(123)->willReturn($parent);

        $network = $this->getMockBuilder(Network::class)->getMock();
        $network->method('getId')->willReturn(NetworkEnum::OUTOFNET->value);

        $networkOrganization = $this->getMockBuilder(NetworkOrganization::class)->getMock();
        $networkOrganization->method('getNetwork')->willReturn($network);

        $this->organizationUtils
            ->method('getActiveNetworkOrganization')
            ->with($parent)
            ->willReturn($networkOrganization);

        $result = $organizationFactory->makeNetworkOrganization($organizationCreationRequest);

        $this->assertEquals(
            $network,
            $result->getNetwork()
        );
    }

    public function testMakeAdminAccess(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeAdminAccess');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();

        $organizationCreationRequest->method('getSubdomain')->willReturn('foo');

        $adminAccess = $organizationFactory->makeAdminAccess($organizationCreationRequest);

        $this->assertTrue(
            $adminAccess->getAdminAccess()
        );

        $this->assertEquals(
            'adminfoo',
            $adminAccess->getPerson()->getUsername()
        );

        $this->assertEquals(
            32,
            strlen($adminAccess->getPerson()->getPassword())
        );
    }

    public function testMakeCycles(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeCycles');

        $cycles = $organizationFactory->makeCycles();

        $cyclesExpectedData = [
            ['Cycle initiation', 10, CycleEnum::INITIATION_CYCLE],
            ['Cycle 1', 20, CycleEnum::CYCLE_1],
            ['Cycle 2', 30, CycleEnum::CYCLE_2],
            ['Cycle 3', 40, CycleEnum::CYCLE_3],
            ['Cycle 4', 50, CycleEnum::CYCLE_4],
            ['Hors cycle', 60, CycleEnum::OUT_CYCLE],
        ];

        $i = 0;
        foreach ($cycles as $cycle) {
            $this->assertEquals(
                $cyclesExpectedData[$i][0],
                $cycle->getLabel()
            );
            $this->assertEquals(
                $cyclesExpectedData[$i][1],
                $cycle->getOrder()
            );
            $this->assertEquals(
                $cyclesExpectedData[$i][2],
                $cycle->getCycleEnum()
            );
            $this->assertFalse(
                $cycle->getIsSystem()
            );
            ++$i;
        }
    }

    public function testMakeAccessNewPerson(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeAccess');

        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();

        $organizationMemberCreationRequest->method('getUsername')->willReturn('bob');
        $organizationMemberCreationRequest->method('getName')->willReturn('Bob');
        $organizationMemberCreationRequest->method('getGivenName')->willReturn('bOBBy');
        $organizationMemberCreationRequest->method('getGender')->willReturn(GenderEnum::MISTER);

        $this->personRepository
            ->method('findOneBy')
            ->with(['username' => 'bob'], null)
            ->willReturn(null);

        $personAddressPostal = $this->getMockBuilder(PersonAddressPostal::class)->getMock();
        $organizationFactory
            ->expects(self::once())
            ->method('makePersonPostalAddress')
            ->with($organizationMemberCreationRequest)
            ->willReturn($personAddressPostal);

        $contactPoint = $this->getMockBuilder(ContactPoint::class)->getMock();
        $organizationFactory
                ->expects(self::once())
                ->method('makePersonContactPoint')
                ->with($organizationMemberCreationRequest)
                ->willReturn($contactPoint);

        $functionType = $this->getMockBuilder(FunctionType::class)->getMock();

        $this->functionTypeRepository
            ->expects(self::once())
            ->method('findOneBy')
            ->with(['mission' => FunctionEnum::ACCOUNTANT])
            ->willReturn($functionType);

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $access = $organizationFactory->makeAccess(
            $organizationMemberCreationRequest,
            FunctionEnum::ACCOUNTANT,
            $creationDate,
            1
        );

        $this->assertInstanceOf(Access::class, $access);
        $this->assertEquals(
            'bob',
            $access->getPerson()->getUsername()
        );
        $this->assertTrue(
            strlen($access->getPerson()->getPassword()) === 32
        );
        $this->assertEquals(
            'Bob',
            $access->getPerson()->getName()
        );
        $this->assertEquals(
            'Bobby',
            $access->getPerson()->getGivenName()
        );
        $this->assertEquals(
            [$personAddressPostal],
            $access->getPerson()->getPersonAddressPostal()->toArray()
        );
        $this->assertEquals(
            [$contactPoint],
            $access->getPerson()->getContactPoints()->toArray()
        );
        $this->assertEquals(
            $functionType,
            $access->getOrganizationFunction()->first()->getFunctionType()
        );
        $this->assertEquals(
            $creationDate,
            $access->getOrganizationFunction()->first()->getStartDate()
        );
    }

    public function testMakeAccessNewPersonUsernameAlreadyInUse(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeAccess');

        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();

        $organizationMemberCreationRequest->method('getUsername')->willReturn('bob');
        $organizationMemberCreationRequest->method('getName')->willReturn('Bob');
        $organizationMemberCreationRequest->method('getGivenName')->willReturn('bOBBy');
        $organizationMemberCreationRequest->method('getGender')->willReturn(GenderEnum::MISTER);

        $person = $this->getMockBuilder(Person::class)->getMock();
        $this->personRepository
            ->method('findOneBy')
            ->with(['username' => 'bob'], null)
            ->willReturn($person);

        $organizationFactory
            ->expects(self::never())
            ->method('makePersonPostalAddress');

        $organizationFactory
                ->expects(self::never())
                ->method('makePersonContactPoint');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Username already in use : bob');

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $organizationFactory->makeAccess(
            $organizationMemberCreationRequest,
            FunctionEnum::ADHERENT,
            $creationDate,
            1
        );
    }

    public function testMakeAccessExistingPerson(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeAccess');

        $person = $this->getMockBuilder(Person::class)->getMock();

        $this->personRepository
            ->expects(self::once())
            ->method('find')
            ->with(123)
            ->willReturn($person);

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $functionType = $this->getMockBuilder(FunctionType::class)->getMock();

        $this->functionTypeRepository
            ->expects(self::once())
            ->method('findOneBy')
            ->with(['mission' => FunctionEnum::ACCOUNTANT])
            ->willReturn($functionType);

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $access = $organizationFactory->makeAccess(
            123,
            FunctionEnum::ACCOUNTANT,
            $creationDate,
            1
        );

        $this->assertInstanceOf(Access::class, $access);
        $this->assertEquals(
            $person,
            $access->getPerson()
        );

        $this->assertEquals(
            $functionType,
            $access->getOrganizationFunction()->first()->getFunctionType()
        );
        $this->assertEquals(
            $creationDate,
            $access->getOrganizationFunction()->first()->getStartDate()
        );
    }

    public function testMakeAccessPostalAddress(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makePersonPostalAddress');

        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();

        $organizationMemberCreationRequest->method('getStreetAddress1')->willReturn('Aaa');
        $organizationMemberCreationRequest->method('getStreetAddress2')->willReturn('Bbb');
        $organizationMemberCreationRequest->method('getStreetAddress3')->willReturn(null);
        $organizationMemberCreationRequest->method('getPostalCode')->willReturn('00000');
        $organizationMemberCreationRequest->method('getCity')->willReturn('city');
        $organizationMemberCreationRequest->method('getCountryId')->willReturn(123);

        $country = $this->getMockBuilder(Country::class)->getMock();
        $this->countryRepository
            ->expects(self::once())
            ->method('find')
            ->with(123)
            ->willReturn($country);

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $personPostalAddress = $organizationFactory->makePersonPostalAddress(
            $organizationMemberCreationRequest,
            $creationDate,
            1
        );

        $this->assertEquals(
            AddressPostalPersonTypeEnum::ADDRESS_PRINCIPAL,
            $personPostalAddress->getType()
        );

        $postalAddress = $personPostalAddress->getAddressPostal();
        $this->assertEquals('Aaa', $postalAddress->getStreetAddress());
        $this->assertEquals('Bbb', $postalAddress->getStreetAddressSecond());
        $this->assertEquals(null, $postalAddress->getStreetAddressThird());
        $this->assertEquals('00000', $postalAddress->getPostalCode());
        $this->assertEquals('city', $postalAddress->getAddressCity());
        $this->assertEquals($country, $postalAddress->getAddressCountry());
    }

    public function testMakeAccessContactPoint(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makePersonContactPoint');

        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();

        $organizationMemberCreationRequest->method('getPhone')->willReturn('+33102030405');
        $organizationMemberCreationRequest->method('getEmail')->willReturn('email@domain.com');
        $organizationMemberCreationRequest->method('getMobile')->willReturn('+33607080910');

        $this->phoneNumberUtil
            ->expects(self::exactly(2))
            ->method('isPossibleNumber')
            ->willReturnMap([
                ['+33102030405', null, true],
                ['+33607080910', null, true],
            ]);

        $phoneNumber = $this->getMockBuilder(PhoneNumber::class)->getMock();
        $mobilePhoneNumber = $this->getMockBuilder(PhoneNumber::class)->getMock();

        $this->phoneNumberUtil
            ->expects(self::exactly(2))
            ->method('parse')
            ->willReturnMap([
                ['+33102030405', null, null, false, $phoneNumber],
                ['+33607080910', null, null, false, $mobilePhoneNumber],
            ]);

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $contactPoint = $organizationFactory->makePersonContactPoint(
            $organizationMemberCreationRequest,
            $creationDate,
            1
        );

        $this->assertEquals(
            ContactPointTypeEnum::PRINCIPAL,
            $contactPoint->getContactType()
        );

        $this->assertEquals(
            'email@domain.com',
            $contactPoint->getEmail()
        );

        $this->assertEquals(
            $phoneNumber,
            $contactPoint->getTelphone()
        );

        $this->assertEquals(
            $mobilePhoneNumber,
            $contactPoint->getMobilPhone()
        );
    }

    public function testMakeAccessContactPointInvalidPhone(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makePersonContactPoint');

        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();

        $organizationMemberCreationRequest->method('getUsername')->willReturn('bob');
        $organizationMemberCreationRequest->method('getPhone')->willReturn('invalid');
        $organizationMemberCreationRequest->method('getEmail')->willReturn('email@domain.com');
        $organizationMemberCreationRequest->method('getMobile')->willReturn('+33607080910');

        $this->phoneNumberUtil
            ->expects(self::once())
            ->method('isPossibleNumber')
            ->with('invalid')
            ->willReturn(false);

        $this->phoneNumberUtil
            ->expects(self::never())
            ->method('parse');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Phone number is invalid or missing (person: bob)');

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $organizationFactory->makePersonContactPoint(
            $organizationMemberCreationRequest,
            $creationDate,
            1
        );
    }

    public function testMakeAccessContactPointInvalidMobilePhone(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makePersonContactPoint');

        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();

        $organizationMemberCreationRequest->method('getUsername')->willReturn('bob');
        $organizationMemberCreationRequest->method('getPhone')->willReturn('+33102030405');
        $organizationMemberCreationRequest->method('getEmail')->willReturn('email@domain.com');
        $organizationMemberCreationRequest->method('getMobile')->willReturn('invalid');

        $this->phoneNumberUtil
            ->expects(self::exactly(2))
            ->method('isPossibleNumber')
            ->willReturnMap([
                ['+33102030405', null, true],
                ['invalid', null, false],
            ]);

        $this->phoneNumberUtil
            ->expects(self::never())
            ->method('parse');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Mobile phone number is invalid (person: bob)');

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $organizationFactory->makePersonContactPoint(
            $organizationMemberCreationRequest,
            $creationDate,
            1
        );
    }

    public function testMakePersonContactPointNoMobile(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makePersonContactPoint');

        $organizationMemberCreationRequest = $this->getMockBuilder(OrganizationMemberCreationRequest::class)->getMock();

        $organizationMemberCreationRequest->method('getPhone')->willReturn('+33102030405');
        $organizationMemberCreationRequest->method('getMobile')->willReturn(null);

        $this->phoneNumberUtil
            ->expects(self::once())
            ->method('isPossibleNumber')
            ->with('+33102030405')
            ->willReturn(true);

        $creationDate = $this->getMockBuilder(\DateTime::class)->getMock();

        $contactPoint = $organizationFactory->makePersonContactPoint(
            $organizationMemberCreationRequest,
            $creationDate,
            1
        );

        $this->assertEquals(
            null,
            $contactPoint->getMobilPhone()
        );
    }

    public function testMakeSubdomain(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('makeSubdomain');

        $organizationCreationRequest = $this->getMockBuilder(OrganizationCreationRequest::class)->getMock();

        $organizationCreationRequest->method('getSubdomain')->willReturn('subdomain');

        $subdomain = $organizationFactory->makeSubdomain($organizationCreationRequest);

        $this->assertEquals(
            'subdomain',
            $subdomain->getSubdomain()
        );

        $this->assertTrue(
            $subdomain->isActive()
        );
    }

    public function testCreateTypo3Website(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('createTypo3Website');

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

        $response = $this->getMockBuilder(ResponseInterface::class)->disableOriginalConstructor()->getMock();
        $response->method('getStatusCode')->willReturn(Response::HTTP_OK);
        $response->method('getContent')->willReturn('{"root_uid": 456}');

        $this->typo3Service->expects(self::once())->method('createSite')->with(123)->willReturn($response);

        $organization->expects(self::once())->method('setCmsId')->with(456);
        $this->entityManager->expects(self::once())->method('persist')->with($organization);
        $this->entityManager->expects(self::once())->method('flush');

        $result = $organizationFactory->createTypo3Website($organization);

        $this->assertEquals(
            456,
            $result
        );
    }

    public function testCreateTypo3WebsiteWithError(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('createTypo3Website');

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

        $response = $this->getMockBuilder(ResponseInterface::class)->disableOriginalConstructor()->getMock();
        $response->method('getStatusCode')->willReturn(Response::HTTP_FORBIDDEN);
        $response->method('getContent')->willReturn('');

        $this->typo3Service->expects(self::once())->method('createSite')->with(123)->willReturn($response);

        $this->logger
            ->expects(self::once())
            ->method('critical')
            ->with('/!\ A critical error happened while creating the Typo3 website');

        $result = $organizationFactory->createTypo3Website($organization);

        $this->assertNull($result);
    }

    public function testCreateTypo3WebsiteWithInvalidResponse(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('createTypo3Website');

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

        $response = $this->getMockBuilder(ResponseInterface::class)->disableOriginalConstructor()->getMock();
        $response->method('getStatusCode')->willReturn(Response::HTTP_OK);
        $response->method('getContent')->willReturn('<html lang="fr">Login page</html>');

        $this->typo3Service->expects(self::once())->method('createSite')->with(123)->willReturn($response);

        $this->logger
            ->expects(self::once())
            ->method('critical')
            ->with('/!\ A critical error happened while creating the Typo3 website');

        $result = $organizationFactory->createTypo3Website($organization);

        $this->assertNull($result);
    }

    public function testUpdateAdminassosDb(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('updateAdminassosDb');

        $response = $this->getMockBuilder(ResponseInterface::class)->disableOriginalConstructor()->getMock();
        $response->method('getStatusCode')->willReturn(Response::HTTP_OK);

        $this->apiLegacyRequestService
            ->expects(self::once())
            ->method('get')
            ->with('/_internal/request/adminassos/create/organization/123')
            ->willReturn($response);

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

        $organizationFactory->updateAdminassosDb($organization);
    }

    public function testUpdateAdminassosDbError(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('updateAdminassosDb');

        $response = $this->getMockBuilder(ResponseInterface::class)->disableOriginalConstructor()->getMock();
        $response->method('getStatusCode')->willReturn(Response::HTTP_BAD_REQUEST);
        $response->method('getContent')->willReturn('some error');

        $this->apiLegacyRequestService
            ->expects(self::once())
            ->method('get')
            ->with('/_internal/request/adminassos/create/organization/123')
            ->willReturn($response);

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

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('An error happened while updating the adminassos database: some error');

        $organizationFactory->updateAdminassosDb($organization);
    }

    public function testNormalizeIdentificationField(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('normalizeIdentificationField');

        $this->assertEquals(
            'c+est+une+phrase+normalisee+',
            $organizationFactory->normalizeIdentificationField("C'est une phrase normalisée.")
        );
    }

    public function testDelete(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('delete');

        $organizationDeletionRequest = $this->getMockBuilder(OrganizationDeletionRequest::class)->getMock();

        $organizationDeletionRequest->method('getOrganizationId')->willReturn(123);

        $parameters = $this->getMockBuilder(Parameters::class)->getMock();
        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $organization->method('getId')->willReturn(123);
        $organization->method('getParameters')->willReturn($parameters);

        $this->organizationRepository
            ->expects(self::once())
            ->method('find')
            ->with(123)
            ->willReturn($organization);

        $this->entityManager->expects(self::once())->method('beginTransaction');
        $this->entityManager->expects(self::once())->method('flush');
        $this->entityManager->expects(self::once())->method('commit');
        $this->entityManager->expects(self::never())->method('rollback');

        $person1 = $this->getMockBuilder(Person::class)->getMock();
        $person1->method('getId')->willReturn(1);

        $person2 = $this->getMockBuilder(Person::class)->getMock();
        $person2->method('getId')->willReturn(2);

        $person3 = $this->getMockBuilder(Person::class)->getMock();
        $person3->method('getId')->willReturn(3);

        $organizationFactory
            ->method('getFutureOrphanPersons')
            ->with($organization)
            ->willReturn([$person1, $person2, $person3]);

        $this->entityManager->expects(self::exactly(5))->method('remove')->withConsecutive(
            [$parameters],
            [$organization],
            [$person1],
            [$person2],
            [$person3],
        );

        $organizationFactory->expects(self::once())->method('deleteTypo3Website')->with(123);
        $organizationFactory->expects(self::once())->method('switchDolibarrSocietyToProspect')->with(123);

        $this->fileManager->expects(self::once())->method('deleteOrganizationFiles')->with(123);

        $this->fileManager
            ->expects(self::exactly(3))
            ->method('deletePersonFiles')
            ->withConsecutive([1], [2], [3]);

        $organizationDeletionRequest
            ->expects(self::once())
            ->method('setStatus')
            ->with(OrganizationDeletionRequest::STATUS_OK);

        $result = $organizationFactory->delete($organizationDeletionRequest);

        $this->assertEquals(
            $organizationDeletionRequest,
            $result
        );
    }

    public function testDeleteWithRollback(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('delete');

        $organizationDeletionRequest = $this->getMockBuilder(OrganizationDeletionRequest::class)->getMock();

        $organizationDeletionRequest->method('getOrganizationId')->willReturn(123);

        $parameters = $this->getMockBuilder(Parameters::class)->getMock();
        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $organization->method('getParameters')->willReturn($parameters);

        $person1 = $this->getMockBuilder(Person::class)->getMock();
        $person1->method('getId')->willReturn(1);

        $person2 = $this->getMockBuilder(Person::class)->getMock();
        $person2->method('getId')->willReturn(2);

        $person3 = $this->getMockBuilder(Person::class)->getMock();
        $person3->method('getId')->willReturn(3);

        $organizationFactory
            ->method('getFutureOrphanPersons')
            ->with($organization)
            ->willReturn([$person1, $person2, $person3]);

        $this->organizationRepository
            ->expects(self::once())
            ->method('find')
            ->with(123)
            ->willReturn($organization);

        $this->entityManager->expects(self::once())->method('beginTransaction');
        $this->entityManager->expects(self::never())->method('flush');
        $this->entityManager->expects(self::never())->method('commit');
        $this->entityManager->expects(self::once())->method('rollback');

        $this->entityManager->method('remove')->willThrowException(new \Exception('some error'));

        $organizationFactory->expects(self::never())->method('deleteTypo3Website');
        $organizationFactory->expects(self::never())->method('switchDolibarrSocietyToProspect');

        $organizationDeletionRequest
            ->expects(self::never())
            ->method('setStatus');

        $this->logger
            ->expects(self::once())
            ->method('critical')
            ->with($this->callback(function ($arg) {
                return is_string($arg) && str_contains($arg, 'An error happened, operation cancelled') && str_contains($arg, 'some error');
            }));

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

        $organizationFactory->delete($organizationDeletionRequest);
    }

    public function testDeleteWithNonBlockingErrors(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('delete');

        $organizationDeletionRequest = $this->getMockBuilder(OrganizationDeletionRequest::class)->getMock();

        $organizationDeletionRequest->method('getOrganizationId')->willReturn(123);

        $parameters = $this->getMockBuilder(Parameters::class)->getMock();
        $organization = $this->getMockBuilder(Organization::class)->getMock();
        $organization->method('getId')->willReturn(123);
        $organization->method('getParameters')->willReturn($parameters);

        $person1 = $this->getMockBuilder(Person::class)->getMock();
        $person1->method('getId')->willReturn(1);

        $person2 = $this->getMockBuilder(Person::class)->getMock();
        $person2->method('getId')->willReturn(2);

        $person3 = $this->getMockBuilder(Person::class)->getMock();
        $person3->method('getId')->willReturn(3);

        $organizationFactory
            ->method('getFutureOrphanPersons')
            ->with($organization)
            ->willReturn([$person1, $person2, $person3]);

        $this->organizationRepository
            ->expects(self::once())
            ->method('find')
            ->with(123)
            ->willReturn($organization);

        $this->entityManager->expects(self::once())->method('beginTransaction');
        $this->entityManager->expects(self::once())->method('flush');
        $this->entityManager->expects(self::once())->method('commit');
        $this->entityManager->expects(self::never())->method('rollback');

        $this->entityManager->expects(self::exactly(5))->method('remove')->withConsecutive(
            [$parameters],
            [$organization],
            [$person1],
            [$person2],
            [$person3],
        );

        $organizationFactory->expects(self::once())->method('deleteTypo3Website')->willThrowException(new \Exception('some error'));
        $organizationFactory->expects(self::once())->method('switchDolibarrSocietyToProspect')->willThrowException(new \Exception('some error'));

        $organizationDeletionRequest
            ->expects(self::once())
            ->method('setStatus')
            ->with(OrganizationDeletionRequest::STATUS_OK_WITH_ERRORS);

        $this->fileManager
            ->expects(self::once())
            ->method('deleteOrganizationFiles')
            ->with(123)
            ->willThrowException(new \Exception('some error'));

        $this->fileManager
            ->expects(self::exactly(3))
            ->method('deletePersonFiles')
            ->withConsecutive([1], [2], [3])
            ->willThrowException(new \Exception('some error'));

        $this->logger
            ->expects(self::exactly(6))
            ->method('critical')
        ->withConsecutive(
            ['An error happened while deleting the Typo3 website, please proceed manually.'],
            ['An error happened while updating the Dolibarr society, please proceed manually.'],
            ["An error happened while deleting the organization's files, please proceed manually."],
            ["An error happened while deleting the person's files, please proceed manually (id=1)."],
            ["An error happened while deleting the person's files, please proceed manually (id=2)."],
            ["An error happened while deleting the person's files, please proceed manually (id=3)."],
        );

        $result = $organizationFactory->delete($organizationDeletionRequest);

        $this->assertEquals(
            $organizationDeletionRequest,
            $result
        );
    }

    public function testGetFutureOrphanPersons(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('getFutureOrphanPersons');

        $organization = $this->getMockBuilder(Organization::class)->getMock();

        $access1 = $this->getMockBuilder(Access::class)->getMock();
        $person1 = $this->getMockBuilder(Person::class)->getMock();
        $access1->method('getPerson')->willReturn($person1);

        $access2 = $this->getMockBuilder(Access::class)->getMock();
        $person2 = $this->getMockBuilder(Person::class)->getMock();
        $access2->method('getPerson')->willReturn($person2);

        $access3 = $this->getMockBuilder(Access::class)->getMock();
        $person3 = $this->getMockBuilder(Person::class)->getMock();
        $access3->method('getPerson')->willReturn($person3);

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

        $person1->method('getAccesses')->willReturn(new ArrayCollection([$access1, $otherAccess1]));
        $person2->method('getAccesses')->willReturn(new ArrayCollection([$access2, $otherAccess2]));
        $person3->method('getAccesses')->willReturn(new ArrayCollection([$access3]));

        $organization->method('getAccesses')->willReturn(new ArrayCollection([$access1, $access2, $access3]));

        $this->assertEquals(
            [$person3],
            $organizationFactory->getFutureOrphanPersons($organization)
        );
    }

    public function testDeleteTypo3Website(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('deleteTypo3Website');

        $this->typo3Service->expects(self::once())->method('hardDeleteSite')->with(123);

        $organizationFactory->deleteTypo3Website(123);
    }

    public function testSwitchDolibarrSocietyToProspect(): void
    {
        $organizationFactory = $this->getOrganizationFactoryMockFor('switchDolibarrSocietyToProspect');

        $this->dolibarrApiService
            ->expects(self::once())
            ->method('switchSocietyToProspect')
            ->with(123);

        $organizationFactory->switchDolibarrSocietyToProspect(123);
    }
}
