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

use Exception;
use Fifa\ConnectId\Sdk\ActorResolver\ActorResolverInterface;
use Fifa\ConnectId\Sdk\Api\Model\FacilityLocalType;
use Fifa\ConnectId\Sdk\Api\Model\FindFacilitiesRequestType;
use Fifa\ConnectId\Sdk\Api\Model\FindFacilitiesResponseType;
use Fifa\ConnectId\Sdk\Api\Model\RegisterResponseType;
use Fifa\ConnectId\Sdk\ApiClient\HttpResponseLogging;
use Fifa\ConnectId\Sdk\ApiModelValidation\ContextTypeValidator;
use Fifa\ConnectId\Sdk\ApiModelValidation\FifaIdValidator;
use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
use Fifa\ConnectId\Sdk\Exception\FifaDuplicatedFacilityFoundException;
use Fifa\ConnectId\Sdk\Exception\FifaEntityNotFoundException;
use Fifa\ConnectId\Sdk\Exception\InvalidClientDataException;
use Fifa\ConnectId\Sdk\ExceptionHandler\ExceptionHandler;
use Fifa\ConnectId\Sdk\ExceptionHandler\Handler\FifaDuplicatedFacilityFoundHandler;
use Fifa\ConnectId\Sdk\Registration\ValidationException;
use Fifa\ConnectId\Sdk\Util\ActorEncoder;
use Fifa\ConnectId\Sdk\Util\ForcePrefixFormatter;
use Fifa\ConnectServiceBus\Sdk\Exception\AuthenticationException;
use Fifa\ConnectServiceBus\Sdk\Exception\UnauthorizedException;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Fifa\ConnectId\Sdk\Api\Resource\FacilityApi as ResourceFacilityApi;

/**
 * Class FacilityApi
 * @package Fifa\ConnectId\Sdk\ApiClient\Facility
 */
class FacilityApi implements FacilityApiInterface
{
    use HttpResponseLogging;

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

    /**
     * @var ResourceFacilityApi
     */
    private $facilityApi;

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

    /**
     * Constructor
     *
     * @param ResourceFacilityApi $facilityApi
     * @param LoggerInterface $logger
     * @param ActorResolverInterface $actorResolver
     */
    public function __construct(ResourceFacilityApi $facilityApi, LoggerInterface $logger, ActorResolverInterface $actorResolver)
    {
        $this->logger = $logger;
        $this->facilityApi = $facilityApi;
        $this->actorResolver = $actorResolver;
    }

    /**
     * Registers a new facility and assigns FIFA Unique Identifier.
     * Uses facility data as defined in FIFA Data Standard.
     *
     * @param FacilityLocalType $facility
     * @param bool $force - registers facility even if possible duplicates are detected
     * @return RegisterResponseType
     *
     * @throws InvalidClientDataException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws FifaDuplicatedFacilityFoundException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function registerFacility(FacilityLocalType $facility, bool $force = false): RegisterResponseType
    {
        ContextTypeValidator::validate($facility, "FacilityLocalType");
        $this->logger->info('Registering a facility, forced: ' . ($force ? 'true' : 'false'), array('facility' => $facility->__toString()));
        try {
            list($responseType, $statusCode, $httpHeaders) =
                $this->facilityApi->facilityRegisterByForceWithHttpInfo($facility, $force ? 'true' : 'false', ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $responseType, $statusCode, $httpHeaders, 'Registered facility');

            return $responseType;
        }
        catch (Exception $e) {
            $exception = ExceptionHandler::create($e, new FifaDuplicatedFacilityFoundHandler());
            $this->logger->error(self::getFailedToRegisterFacilityMessage($facility, $force), array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Gets facility data associated with the specified FIFA Unique Identifier.
     *
     * @param string $facilityId
     * @return FacilityLocalType
     *
     * @throws InvalidClientDataException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws FifaEntityNotFoundException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function getFacility(string $facilityId): FacilityLocalType
    {
        if (is_null($facilityId)) {
            throw new InvalidArgumentException('facilityId parameter cannot be null');
        }

        FifaIdValidator::validate($facilityId, "facilityId");

        $this->logger->info('Getting facility with id: ' . $facilityId);
        try {
            list($response, $statusCode, $httpHeaders) = $this->facilityApi->facilityGetByFacilityfifaidWithHttpInfo($facilityId);
            $this->logResponse($this->logger, $response, $statusCode, $httpHeaders, 'Facility data received');

            return $response;
        }
        catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error(sprintf('Failed to get facility %s', $facilityId), ['exception' => $exception->__toString()]);
            throw $exception;
        }
    }

    /**
     * Updates the facility.
     *
     * @param FacilityLocalType $facility - Object containing information about the facility to update
     * @param bool $force - updates facility even if possible duplicates are detected
     * @return FacilityLocalType
     *
     * @throws InvalidClientDataException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws FifaEntityNotFoundException
     * @throws FifaDuplicatedFacilityFoundException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function update(FacilityLocalType $facility, bool $force = false): FacilityLocalType
    {
        ContextTypeValidator::validate($facility, "FacilityLocalType");
        $this->logger->info('Updating a facility, forced: ' . ($force ? 'true' : 'false'), array('facility' => $facility->__toString()));
        try {

            list($facilityType, $statusCode, $httpHeaders) =
                $this->facilityApi->facilityUpdateByForceWithHttpInfo($facility, $force ? 'true' : 'false', ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger,$facilityType, $statusCode, $httpHeaders, 'Updated facility data received');

            return $facilityType;
        }
        catch (Exception $e) {
            $exception = ExceptionHandler::create($e, new FifaDuplicatedFacilityFoundHandler());
            $this->logger->error('Failed to ' . ForcePrefixFormatter::forceToString($force, "update") . ' the facility ' . $facility->getFacilityFifaId(), array('exception' => $exception->__toString()));
            throw $exception;
        }
    }

    /**
     * Searches for facilities.
     *
     * @param FindFacilitiesRequestType $request
     * @return FindFacilitiesResponseType
     *
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function findFacilities(FindFacilitiesRequestType $request): FindFacilitiesResponseType
    {
        ContextTypeValidator::validate($request, "FindFacilitiesRequestType");
        try {
            $this->logger->info('Searching for facilities', ['request' => $request->__toString()]);
            list($response, $statusCode, $httpHeaders) =
                $this->facilityApi->facilityFindWithHttpInfo($request, ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $response, $statusCode, $httpHeaders, 'Received find facilities response');

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

    /**
     * Gets a list of facilities this facility may be duplicated with.
     * Uses facility data as defined in FIFA Data Standard.
     *
     * @param FacilityLocalType $facility - Object containing information about the organisation
     * @return array[]
     *
     * @throws InvalidClientDataException
     * @throws FifaConnectIdException
     * @throws UnauthorizedException
     * @throws AuthenticationException
     * @throws ValidationException
     */
    public function getDuplicates(FacilityLocalType $facility): array
    {
        ContextTypeValidator::validate($facility, "FacilityLocalType");
        try {
            $this->logger->info('Getting duplicates for a facility', ['request' => $facility->__toString()]);
            $response = $this->facilityApi->facilityGetDuplicatesWithHttpInfo($facility, ActorEncoder::encode($this->actorResolver));
            $this->logResponse($this->logger, $response[0], $response[1], $response[2],'Received facility duplicates from the ID Directory service');

            return $response;
        }
        catch (Exception $e) {
            $exception = ExceptionHandler::create($e);
            $this->logger->error('Failed to get facility duplicates', ['exception' => $exception->__toString()]);
            throw $exception;
        }
    }

    /**
     * Build message for facility registration failure
     *
     * @param FacilityLocalType $facility
     * @param bool $force
     * @return string
     */
    private static function getFailedToRegisterFacilityMessage(FacilityLocalType $facility, bool $force): string
    {
        $message = 'Failed to '. ForcePrefixFormatter::forceToString($force, 'register') .' facility';

        $organisationName = $facility->getInternationalName();
        if (strlen($organisationName) === 0) {
            $organisationName = $facility->getInternationalShortName();
        }

        if (strlen($organisationName) === 0) {
            return $message;
        }

        return $message . ' ' . $organisationName;
    }
}