<?php

namespace Fifa\ConnectId\Sdk\ApiClient\Person;

use DateTime;
use Exception;
use Fifa\ConnectId\Sdk\ActorResolver\ActorResolverInterface;
use Fifa\ConnectId\Sdk\Api\Model\AddRegistrationsRequestType;
use Fifa\ConnectId\Sdk\Api\Model\BulkDeactivateRegistrationsRequestType;
use Fifa\ConnectId\Sdk\Api\Model\BulkDeactivateRegistrationsResponseType;
use Fifa\ConnectId\Sdk\Api\Model\GetDuplicatesByFIFAIdRequestType;
use Fifa\ConnectId\Sdk\Api\Model\GetDuplicatesOfRegisteredPersonResponseType;
use Fifa\ConnectId\Sdk\Api\Model\GetDuplicatesRequestType;
use Fifa\ConnectId\Sdk\Api\Model\GetDuplicatesResponseType;
use Fifa\ConnectId\Sdk\Api\Model\MergePersonsRequestType;
use Fifa\ConnectId\Sdk\Api\Model\PersonDuplicateType;
use Fifa\ConnectId\Sdk\Api\Model\PersonType;
use Fifa\ConnectId\Sdk\Api\Model\RegisterPersonRequestType;
use Fifa\ConnectId\Sdk\Api\Model\RegisterResponseType;
use Fifa\ConnectId\Sdk\Api\Model\RegistrationType;
use Fifa\ConnectId\Sdk\Api\Model\UnmergePersonsRequestType;
use Fifa\ConnectId\Sdk\Api\Model\UpdatePersonRequestType;
use Fifa\ConnectId\Sdk\Api\Model\UpdateRegistrationsRequestType;
use Fifa\ConnectId\Sdk\Api\Resource;
use Fifa\ConnectId\Sdk\ApiClient\Hashing\HashingApiInterface;
use Fifa\ConnectId\Sdk\ApiClient\HttpResponseLogging;
use Fifa\ConnectId\Sdk\ApiModelValidation\ContextTypeValidator;
use Fifa\ConnectId\Sdk\ApiModelValidation\FifaIdValidator;
use Fifa\ConnectId\Sdk\Exception\FifaConflictException;
use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
use Fifa\ConnectId\Sdk\Exception\FifaDuplicatedPersonFoundException;
use Fifa\ConnectId\Sdk\Exception\FifaDuplicateOfRegisteredPersonFoundException;
use Fifa\ConnectId\Sdk\Exception\FifaEntityDeletedException;
use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
use Fifa\ConnectId\Sdk\Exception\FifaPersonMergedException;
use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
use Fifa\ConnectId\Sdk\Exception\TooManyRequestsException;
use Fifa\ConnectId\Sdk\ExceptionHandler\ExceptionHandler;
use Fifa\ConnectId\Sdk\ExceptionHandler\Handler\ConflictResponseHandler;
use Fifa\ConnectId\Sdk\ExceptionHandler\Handler\FifaDuplicatedPersonFoundHandler;
use Fifa\ConnectId\Sdk\ExceptionHandler\Handler\FifaDuplicateOfRegisteredPersonFoundHandler;
use Fifa\ConnectId\Sdk\ExceptionHandler\Handler\MergeOperations\FifaPersonMergedHandler;
use Fifa\ConnectId\Sdk\HashedPersonProcessor\HashedPersonProcessorInterface;
use Fifa\ConnectId\Sdk\HashedPersonProcessor\HashedPersonTypeHashingProcessor;
use Fifa\ConnectId\Sdk\HashedPersonProcessor\RegisterPersonRequestProcessorAdapter;
use Fifa\ConnectId\Sdk\HashedPersonProcessor\RegisterPersonRequestProcessorInterface;
use Fifa\ConnectId\Sdk\Model\PersonData;
use Fifa\ConnectId\Sdk\Model\PersonName;
use Fifa\ConnectId\Sdk\Registration\ValidationException;
use Fifa\ConnectId\Sdk\Util\ActorEncoder;
use Fifa\ConnectId\Sdk\Util\DateUtils\TimezoneChecker;
use Fifa\ConnectId\Sdk\Util\ForcePrefixFormatter;
use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;
use Fifa\ConnectServiceBus\Sdk\Exception\UnauthorizedException;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;

/**
 * Class PersonApi
 * @package Fifa\ConnectId\Sdk\ApiClient\Person
 */
class PersonApi implements PersonApiInterface
{
    use HttpResponseLogging;

    /**
     * @var Resource\PersonApi
     */
    private $personApi;

    /**
     * @var HashingApiInterface
     */
    private $hashingApi;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var ActorResolverInterface
     */
    private $actorResolver;

    /**
     * @var RegisterPersonRequestProcessorInterface
     */
    private $registerPersonRequestProcessor;

    /**
     * Constructor
     *
     * @param Resource\PersonApi $personApi
     * @param HashingApiInterface $hashingApi
     * @param LoggerInterface $logger
     * @param ActorResolverInterface $actorResolver
     * @param HashedPersonProcessorInterface|null $hashedPersonProcessor
     */
    public function __construct(Resource\PersonApi             $personApi, HashingApiInterface $hashingApi, LoggerInterface $logger, ActorResolverInterface $actorResolver,
                                HashedPersonProcessorInterface $hashedPersonProcessor = null)
    {
        $this->personApi = $personApi;
        $this->hashingApi = $hashingApi;
        $this->logger = $logger;
        $this->actorResolver = $actorResolver;
        if (is_null($hashedPersonProcessor)) {
            $hashedPersonProcessor = new HashedPersonTypeHashingProcessor();
        }
        $this->registerPersonRequestProcessor = new RegisterPersonRequestProcessorAdapter($hashedPersonProcessor);
    }

    /**
     * Gets person data associated with the specified FIFA Unique Identifier.
     *
     * @param string $fifaId FIFA Unique Identifier
     * @return PersonType
     *
     * @throws InvalidClientDataException
     * @throws FifaEntityNotFoundException
     * @throws FifaConnectIdException
     * @throws FifaPersonMergedException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function getPersonData(string $fifaId): PersonType
    {
        FifaIdValidator::validate($fifaId, "fifaId");
        $this->logger->info('Getting person data', array('personFifaId' => $fifaId));
        try {
            list($connectIdPersonType, $statusCode, $httpHeaders) = $this->personApi->personGetByPersonfifaidWithHttpInfo($fifaId);
            $this->logResponse($this->logger, $connectIdPersonType, $statusCode, $httpHeaders, 'Person data received');

            return $connectIdPersonType;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e, new FifaPersonMergedHandler());
            $this->logger->error('Getting person data error', array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Gets data of the registration belonging to the primary data provider of the specified FIFA Unique Identifier.
     *
     * @param string $fifaId FIFA Unique Identifier
     * @return RegistrationType
     *
     * @throws InvalidClientDataException
     * @throws FifaEntityNotFoundException
     * @throws FifaConnectIdException
     * @throws FifaPersonMergedException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function getPrimaryDataProviderRegistration(string $fifaId): ?RegistrationType
    {
        FifaIdValidator::validate($fifaId, "fifaId");
        $this->logger->info('Getting primary data provider registration', array('personFifaId' => $fifaId));
        try {
            list($registrationType, $statusCode, $httpHeaders) = $this->personApi->personGetPrimaryDataProviderRegistrationByPersonfifaidWithHttpInfo($fifaId);
            $this->logResponse($this->logger, $registrationType, $statusCode, $httpHeaders, 'Registration data received');

            return $registrationType;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e, new FifaPersonMergedHandler());
            $this->logger->error('Getting primary data provider registration error', array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Gets a list of people this person may be duplicated with.
     * Uses 'PersonLocalType' as defined in FIFA Data Standard.
     *
     * @param PersonData $personData - object containing information about local and international names of the person, as well as the date of birth.
     * @param string[]|null $organisationsIds - Organisations FIFA Unique Identifiers
     * @return PersonDuplicateType[]
     *
     * @throws InvalidClientDataException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function getDuplicates(PersonData $personData, ?array $organisationsIds = null): array
    {
        ContextTypeValidator::validate($personData, "personData");
        $this->logger->info('Getting duplicates for a person', array('person' => $personData->__toString()));
        try {
            /** @var GetDuplicatesResponseType $duplicates */
            list($duplicates, $statusCode, $httpHeaders) = $this->personApi->personGetDuplicatesWithHttpInfo(
                $this->createGetDuplicatesRequestType($personData, null, $organisationsIds)
            );
            $this->logResponse($this->logger, $duplicates, $statusCode, $httpHeaders, 'Duplicates received');

            return $duplicates->getDuplicates();
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error('Error when getting duplicates for a person', array('exception' => $exception->__toString()));
            throw $exception;
        }
    }


    /**
     * Gets a list of people this person may be duplicated with.
     * Duplicates marked as known duplicate by trusted organisations would not be returned.
     *
     * @param string $personFifaId
     * @param string[]|null $trustedOrganisationsIds
     * @return GetDuplicatesOfRegisteredPersonResponseType
     *
     * @throws InvalidClientDataException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function getDuplicatesExceptTrustedKnownDuplicates(string $personFifaId, ?array $trustedOrganisationsIds = null): GetDuplicatesOfRegisteredPersonResponseType
    {
        FifaIdValidator::validate($personFifaId, "personFifaId");
        $this->logger->info('Getting duplicates by person FIFA ID', array('personFifaId' => $personFifaId));
        try {
            /** @var GetDuplicatesOfRegisteredPersonResponseType $duplicates */
            list($duplicates, $statusCode, $httpHeaders) = $this->personApi->personGetDuplicatesByFIFAIdWithHttpInfo(
                $this->createGetDuplicatesByFIFAIdRequestType($personFifaId, $trustedOrganisationsIds)
            );
            $this->logResponse($this->logger, $duplicates, $statusCode, $httpHeaders, 'Duplicates received');

            return $duplicates;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error('Error when getting duplicates for a person by FIFA ID', array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Registers a new person and assigns FIFA Unique Identifier.
     * If duplicates are found the person is not registered.
     * Uses 'PersonLocalType' as defined in FIFA Data Standard.
     *
     * @param PersonData $personData - object containing information about local and international names of the person, as well as the date of birth.
     * @param string|null $personLocalId - Local identifier of person
     * @return string
     *
     * @throws InvalidClientDataException
     * @throws FifaDuplicatedPersonFoundException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function register(PersonData $personData, ?string $personLocalId = null): string
    {
        return $this->registerPerson($personData, $personLocalId, false);
    }

    /**
     * Registers a new person even if possible duplicates are found.
     * Uses 'PersonLocalType' as defined in FIFA Data Standard.
     *
     * @param PersonData $personData - object containing information about local and international names of the person, as well as the date of birth.
     * @param string|null $personLocalId - Local identifier of person
     * @return string
     *
     * @throws InvalidClientDataException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function forceRegister(PersonData $personData, ?string $personLocalId = null): string
    {
        return $this->registerPerson($personData, $personLocalId, true);
    }

    /**
     * Method adds registrations into person
     *
     * @param AddRegistrationsRequestType $request
     * @param bool $force
     * @return PersonType
     *
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function addRegistrations(AddRegistrationsRequestType $request, bool $force): PersonType
    {
        ContextTypeValidator::validate($request, "AddRegistrationsRequestType");

        $this->logger->info('Adding registrations to a person, forced: ' . ($force ? 'true' : 'false'), array('request' => $request->__toString()));
        try {
            list($personType, $statusCode, $httpHeaders) =
                $this->personApi->personAddRegistrationsByForceWithHttpInfo($request, $force ? 'true' : 'false', ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $personType, $statusCode, $httpHeaders, 'Registration(s) added to a person');

            return $personType;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error('Error during ' . ForcePrefixFormatter::forceToString($force, 'adding') . ' registrations',
                array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Method updates registrations for a person
     *
     * @param UpdateRegistrationsRequestType $request
     * @param bool $force
     * @return PersonType
     *
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws InvalidClientDataException
     * @throws ValidationException
     */
    public function updateRegistrations(UpdateRegistrationsRequestType $request, bool $force): PersonType
    {
        ContextTypeValidator::validate($request, "UpdateRegistrationsRequestType");

        $this->logger->info('Updating person registrations, forced: ' . ($force ? 'true' : 'false'), array('request' => $request->__toString()));
        try {
            list($personType, $statusCode, $httpHeaders) =
                $this->personApi->personUpdateRegistrationsByForceWithHttpInfo($request, $force ? 'true' : 'false', ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $personType, $statusCode, $httpHeaders, 'Person registrations updated');

            return $personType;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error('Error during ' . ForcePrefixFormatter::forceToString($force, 'updating') . ' registrations',
                array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Updates person data.
     *
     * @param string $fifaId
     * @param PersonName[]|null $names . Null if not changed
     * @param DateTime|null $dateOfBirth . Null if not changed
     * @param string|null $gender
     * @param bool $forceUpdate
     * @return PersonType
     *
     * @throws AuthenticationException
     * @throws FifaConnectIdException
     * @throws InvalidClientDataException
     * @throws ValidationException
     * @throws TooManyRequestsException
     * @throws \Fifa\ConnectId\Sdk\Exception\UnauthorizedException
     */
    public function updatePerson(string $fifaId, ?array $names = null, ?DateTime $dateOfBirth = null, ?string $gender = null, bool $forceUpdate = false): PersonType
    {
        if (($names == null || sizeof($names) === 0) && $dateOfBirth == null && empty($gender)) {
            throw new InvalidArgumentException('At least one of parameters "$names", "$dateOfBirth" or "$gender" is required.');
        }

        try {
            $request = new UpdatePersonRequestType();
            $request->setPersonId($fifaId);
            $request->setDateOfBirth($dateOfBirth);
            $request->setGender($gender);

            ContextTypeValidator::validate($request, "UpdatePersonRequestType");

            if ($names != null && sizeof($names) > 0) {
                $readOnly = false;
                $nameHashes = $this->hashingApi->computeHashes($names, $readOnly);
                $request->setNameHashes($nameHashes);
            }

            $this->logger->info('Updating person, forced: ' . ($forceUpdate ? 'true' : 'false'), array('request' => $request->__toString()));
            list($personType, $statusCode, $httpHeaders) =
                $this->personApi->personUpdatePersonByForceWithHttpInfo($request, $forceUpdate ? 'true' : 'false', ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $personType, $statusCode, $httpHeaders, 'Person updated');

            return $personType;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e, new FifaDuplicateOfRegisteredPersonFoundHandler());
            $this->logger->error('Error during ' . ForcePrefixFormatter::forceToString($forceUpdate, 'updating') . ' person',
                array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Merges two persons. Returns the primary person.
     *
     * @param string $primaryPersonId - Primary person FIFA Unique Identifier
     * @param string $secondaryPersonId - Secondary person FIFA Unique Identifier
     * @return PersonType
     *
     * @throws InvalidClientDataException
     * @throws FifaEntityNotFoundException
     * @throws FifaConflictException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function mergePersons(string $primaryPersonId, string $secondaryPersonId): PersonType
    {
        FifaIdValidator::validate($primaryPersonId, "primaryPersonId");
        FifaIdValidator::validate($secondaryPersonId, "secondaryPersonId");

        try {

            $this->logger->info('Merging two persons: ' . $primaryPersonId . ' (Primary) and ' . $secondaryPersonId . ' (Secondary)');
            list($personType, $statusCode, $httpHeaders) = $this->personApi->personMergeWithHttpInfo(new MergePersonsRequestType(array(
                'primary_person_fifa_id' => $primaryPersonId,
                'secondary_person_fifa_id' => $secondaryPersonId
            )), ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $personType, $statusCode, $httpHeaders, 'Persons merged');

            return $personType;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e, new ConflictResponseHandler());
            $this->logger->error(
                'Failed to merge persons ' . $primaryPersonId . ' (Primary) and ' . $secondaryPersonId . ' (Secondary)',
                array('exception' => $exception->__toString())
            );
            throw $exception;
        }
    }

    /**
     * Reverts merge operation. Returns the secondary person which will be active again when unmerge operations is completed.
     *
     * @param $primaryPersonId - Primary person FIFA Unique Identifier
     * @param $secondaryPersonId - Secondary person FIFA Unique Identifier
     * @return PersonType
     *
     * @throws InvalidClientDataException
     * @throws FifaEntityNotFoundException
     * @throws FifaConflictException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function unmergePersons(string $primaryPersonId, string $secondaryPersonId): PersonType
    {
        FifaIdValidator::validate($primaryPersonId, "primaryPersonId");
        FifaIdValidator::validate($secondaryPersonId, "secondaryPersonId");

        try {

            $this->logger->info('Unmerging persons: ' . $primaryPersonId . ' (Primary) and ' . $secondaryPersonId . ' (Secondary)');
            list($personType, $statusCode, $httpHeaders) = $this->personApi->personUnmergeWithHttpInfo(new UnmergePersonsRequestType(array(
                'primary_person_fifa_id' => $primaryPersonId,
                'secondary_person_fifa_id' => $secondaryPersonId
            )), ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $personType, $statusCode, $httpHeaders, 'Persons unmerged');

            return $personType;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e, new ConflictResponseHandler());
            $this->logger->error(
                'Failed to unmerge persons ' . $primaryPersonId . ' (Primary) and ' . $secondaryPersonId . ' (Secondary)',
                array('exception' => $exception->__toString())
            );
            throw $exception;
        }
    }

    /**
     * Bulk deactivate registrations given in request.
     *
     * @param BulkDeactivateRegistrationsRequestType $request
     * @return BulkDeactivateRegistrationsResponseType
     *
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws InvalidClientDataException
     * @throws ValidationException
     */
    public function bulkDeactivateRegistrations(BulkDeactivateRegistrationsRequestType $request): BulkDeactivateRegistrationsResponseType
    {
        ContextTypeValidator::validate($request, "BulkDeactivateRegistrationsRequestType");
        try {
            $this->logger->info('Bulk deactivating registrations', ['request' => $request->__toString()]);
            list($response, $statusCode, $httpHeaders) = $this->personApi->personBulkDeactivateRegistrationsWithHttpInfo($request, ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $response, $statusCode, $httpHeaders, 'Registrations deactivated');

            return $response;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error('Failed to bulk deactivate registrations', array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Gets secondary FIFA IDs directly merged into person
     *
     * @param string $personFifaId
     * @return string[]
     *
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws InvalidClientDataException
     * @throws FifaEntityNotFoundException
     * @throws FifaEntityDeletedException
     * @throws ValidationException
     * @throws TooManyRequestsException
     * @throws FifaConnectIdException
     */
    public function getMergedSecondaryFifaIdsForPerson(string $personFifaId): array
    {
        FifaIdValidator::validate($personFifaId, 'personFifaId');
        $this->logger->info('Getting merged secondary FIFA IDs for person ' . $personFifaId);
        try {
            list($responseType, $statusCode, $httpHeaders) =
                $this->personApi->personGetMergedSecondaryFifaIdsByPersonfifaidWithHttpInfo($personFifaId);
            $this->logResponse($this->logger, $responseType, $statusCode, $httpHeaders, 'Merged secondary FIFA IDs for person ' . $personFifaId . ' received');

            return $responseType;
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error('Failed to get merged secondary FIFA IDs for person ' . $personFifaId, array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * @param PersonData $personData
     * @param string|null $personLocalId - Local identifier of person
     * @param boolean $forceRegister
     * @return string
     *
     * @throws InvalidClientDataException
     * @throws FifaDuplicatedPersonFoundException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    private function registerPerson(PersonData $personData, ?string $personLocalId, bool $forceRegister): string
    {
        ContextTypeValidator::validate($personData, "personData");
        TimezoneChecker::checkIsUTC($personData->getPersonType()->getDateOfBirth(), 'Date of birth must be specified in UTC');

        $this->logger->debug(ForcePrefixFormatter::forceToString($forceRegister, 'registering') . ' a person', array('person' => $personData->__toString(), 'forceRegister' => $forceRegister));
        try {
            $hashedPersonType = $this->createRegisterPersonRequestType($personData, $personLocalId);

            /** @var RegisterResponseType $registerResponseType */
            $request = $this->registerPersonRequestProcessor->process($hashedPersonType);
            list($registerResponseType, $statusCode, $httpHeaders) = $forceRegister
                ? $this->personApi->personForceRegisterWithHttpInfo($request, ActorEncoder::encode($this->actorResolver))
                : $this->personApi->personRegisterWithHttpInfo($request, ActorEncoder::encode($this->actorResolver));

            $this->logResponse($this->logger, $registerResponseType, $statusCode, $httpHeaders);

            return $registerResponseType->getUniqueFifaId();
        } catch (Exception $e) {
            $exception = ExceptionHandler::create($e, new FifaDuplicatedPersonFoundHandler());
            $this->logger->error('Error during ' . ForcePrefixFormatter::forceToString($forceRegister, "registering"). ' a person',
                array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * @param PersonData $personData
     * @param string|null $personLocalId - Local identifier of person
     * @return RegisterPersonRequestType
     * @throws AuthenticationException
     * @throws FifaConnectIdException
     * @throws InvalidClientDataException
     */
    private function createRegisterPersonRequestType(PersonData $personData, ?string $personLocalId = null): RegisterPersonRequestType
    {
        $requestType = new RegisterPersonRequestType();
        $readOnly = false;
        $requestType->setPerson($personData->getPersonType())
            ->setNameHashes($this->hashingApi->computeHashes($personData->getPersonNames(), $readOnly))
            ->setLocalId($personLocalId);

        return $requestType;
    }

    /**
     * @param PersonData $personData
     * @param string|null $personLocalId - Local identifier of person
     * @param array|null $organisationsIds
     * @return GetDuplicatesRequestType
     * @throws AuthenticationException
     * @throws FifaConnectIdException
     * @throws InvalidClientDataException
     */
    private function createGetDuplicatesRequestType(PersonData $personData, string $personLocalId = null, ?array $organisationsIds = null): GetDuplicatesRequestType
    {
        $requestType = new GetDuplicatesRequestType();
        $readOnly = true;
        $requestType->setNameHashes($this->hashingApi->computeHashes($personData->getPersonNames(), $readOnly))
            ->setLocalId($personLocalId)
            ->setPerson($personData->getPersonType())
            ->setOrganisationsFifaIds($organisationsIds);

        return $requestType;
    }

    /**
     * @param string $personFifaId
     * @param string[]|null $trustedOrganisations
     * @return GetDuplicatesByFIFAIdRequestType
     */
    private function createGetDuplicatesByFIFAIdRequestType(string $personFifaId, ?array $trustedOrganisations): GetDuplicatesByFIFAIdRequestType
    {
        $requestType = new GetDuplicatesByFIFAIdRequestType();
        $requestType->setPersonFifaId($personFifaId)
            ->setTrustedOrganisationsFifaIds($trustedOrganisations);

        return $requestType;
    }
}