<?php
namespace Fifa\ConnectId\Sdk\ApiClient\Hashing;

use Exception;
use Fifa\ConnectId\Sdk\ApiModelValidation\ApiModelValidator;
use Fifa\ConnectId\Sdk\ApiModelValidation\PersonNameValidator;
use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
use Fifa\ConnectId\Sdk\Exception\UnauthorizedException;
use Fifa\ConnectId\Sdk\ExceptionHandler\ExceptionHandler;
use Fifa\ConnectId\Sdk\Model\PersonName;
use Fifa\ConnectId\Sdk\Api\Resource;
use Fifa\ConnectId\Sdk\Api\ApiException;
use Fifa\ConnectId\Sdk\Api\Model\HashedNameType;
use Fifa\ConnectId\Sdk\Registration\ValidationException;
use Fifa\ConnectId\Sdk\Util\ArrayUtils\ArrayParser;
use Fifa\ConnectId\Sdk\Util\ArrayUtils\DuplicatesFilter\HashedNameTypeDuplicatesFilter;
use Fifa\ConnectId\Sdk\ExceptionHandler\ApiExceptionHandler;
use Fifa\ConnectId\Sdk\Util\ArrayUtils\DuplicatesFilter\PersonNameDuplicatesFilter;
use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;
use Fifa\ConnectServiceBus\Sdk\Utils\ToStringConverter;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;

/**
 * Class HashingApi
 * @package Fifa\ConnectId\Sdk\ApiClient\Hashing
 */
class HashingApi implements HashingApiInterface
{
    /**
     * @var Resource\HashApi
     */
    private $hashApi;

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

    /**
     * Constructor
     *
     * @param Resource\HashApi $hashApi
     * @param LoggerInterface $logger
     */
    public function __construct(Resource\HashApi $hashApi, LoggerInterface $logger)
    {
        $this->hashApi = $hashApi;
        $this->logger = $logger;
    }

    /**
     * Compute hashes and return the results as an array
     *
     * @param PersonName[] $personNames
     * @param bool $readOnly
     * @return HashedNameType[]
     *
     * @throws ValidationException
     * @throws InvalidArgumentException
     * @throws InvalidClientDataException
     * @throws FifaConnectIdException
     * @throws AuthenticationException
     * @throws UnauthorizedException
     */
    public function computeHashes(array $personNames, bool $readOnly = false): array
    {
        /** @var PersonName $personName */
        $personNames = ArrayParser::create($personNames)
            ->checkContainsOnlyInstancesOf(PersonName::class)
            ->checkNotEmpty()
            ->removeDuplicates(new PersonNameDuplicatesFilter())
            ->getData();

        if (sizeof($personNames) === 0) {
            throw new InvalidArgumentException('Please provide at least one person name.');
        }

        ApiModelValidator::validateAllInCollection($personNames, PersonNameValidator::class, "personNames");

        $this->logger->info('Computing batch hashes', array('personNames' => ToStringConverter::arrayToString(($personNames))));
        $prefer = $readOnly ? "read-only" : null;

        try {
            $hashes = array();
            foreach ($personNames as $personName) {
                $hashes = array_merge($hashes, $this->computeHashesForPersonName($personName, $prefer));
            }

            $hashes = ArrayParser::create($hashes)
                    ->checkContainsOnlyInstancesOf(HashedNameType::class)
                    ->removeDuplicates(new HashedNameTypeDuplicatesFilter())
                    ->getData();
            $this->logger->debug('Hashes computed', array(
                'hashedNameTypes' => ToStringConverter::arrayToString($hashes)
            ));

            return $hashes;
        }
        catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error('Computing batch hashes error', array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * @param PersonName $personName
     * @param string|null $prefer
     * @return HashedNameType[]
     * @throws ApiException
     */
    private function computeHashesForPersonName(PersonName $personName, ?string $prefer): array
    {
        $this->logger->info('Computing hashes for PersonName', array('personName' => $personName->__toString()));
        try {
            list($hashedNameTypes, $statusCode, $httpHeaders) = $this->hashApi->hashComputeByRequestFirstnameAndRequestLastnameAndRequestPreferWithHttpInfo(
                $personName->getLastName(), $personName->getFirstName(), $prefer
            );

            $this->logger->debug('Hashes computed for PersonName', array(
                'personName'      => $personName->__toString(),
                'hashedNameTypes' => ToStringConverter::arrayToString($hashedNameTypes),
                'statusCode'      => ToStringConverter::mixedToString($statusCode),
                'httpHeader'      => ToStringConverter::arrayToString($httpHeaders)
            ));

            return $hashedNameTypes;
        }
        catch (Exception $e) {
            $this->logger->error('Computing hashes for PersonName error', array('exception' => $e->__toString()));
            throw $e;
        }
    }
}