<?php

namespace Fifa\ConnectId\Sdk\Registration\Event;

use Exception;
use Fifa\ConnectId\Sdk\Exception\FifaConnectIdException;
use Fifa\ConnectId\Sdk\MessageHandlers\GenericMessageHandler;
use Fifa\ConnectId\Sdk\MessageHandlers\MessageHandlerInterface;
use Fifa\ConnectId\Sdk\MessageHandlers\PersonDetailsMessageHandler;
use Fifa\ConnectId\Sdk\MessageHandlers\PersonDetailsRequestMessageHandler;
use Fifa\ConnectId\Sdk\Registration\Event\Handler\DefaultExceptionEventHandler;
use Fifa\ConnectServiceBus\Sdk\FifaConnectServiceBusClientInterface;
use Psr\Log\LoggerInterface;

/**
 * Class MessageEventHandler
 * @package Fifa\ConnectId\Sdk\Registration\Model
 */
class MessageEventHandler implements MessageEventHandlerInterface
{
    /**
     * @var EventHandlerCollection
     */
    private $personDetailsReceivedEvent;

    /**
     * @var EventHandlerCollection
     */
    private $personDetailsRequestReceivedEvent;

    /**
     * @var EventHandlerCollection
     */
    private $genericMessageReceivedEvent;

    /**
     * @var EventHandlerCollection
     */
    private $serviceBusExceptionEvent;

    /**
     * @var MessageEventHandlerInterface
     */
    private $extensibleMessageEventHandler;

    /**
     * Constructor
     *
     * @param LoggerInterface $logger
     * @param FifaConnectServiceBusClientInterface $serviceBusClient
     * @param int $receivingOperationExecutionTime
     * @param MessageHandlerInterface[] $messageHandlers
     */
    public function __construct(LoggerInterface $logger, FifaConnectServiceBusClientInterface $serviceBusClient, int $receivingOperationExecutionTime, MessageHandlerInterface ...$messageHandlers)
    {
        $this->personDetailsReceivedEvent = new EventHandlerCollection();
        $this->personDetailsRequestReceivedEvent = new EventHandlerCollection();
        $this->genericMessageReceivedEvent = new EventHandlerCollection();
        $this->serviceBusExceptionEvent = new EventHandlerCollection();
        $this->extensibleMessageEventHandler = $this->createHandler($serviceBusClient, $logger, $receivingOperationExecutionTime, $messageHandlers);
    }

    private function createHandler(FifaConnectServiceBusClientInterface $client, LoggerInterface $logger, $receivingOperationExecutionTime, array $handlers = null): ExtensibleMessageEventHandler
    {
        $personDetailsRequestFailed = new EventHandlerCollection();
        $personDetailsRequestHandler = new PersonDetailsRequestMessageHandler(
            function ($target) {
                $this->getPersonDetailsRequestReceivedEvent()->raiseEvent($target);
            },
            function ($target) use ($personDetailsRequestFailed) {
                $personDetailsRequestFailed->raiseEvent($target);
            }
        );

        $messageHandlers = [$personDetailsRequestHandler];
        $messageHandlers[] = new PersonDetailsMessageHandler(function ($target) {
            $this->personDetailsReceivedEvent->raiseEvent($target);
        });
        if (!empty($handlers)) {
            $messageHandlers = array_merge($messageHandlers, $handlers);
        }
        $messageHandlers[] = new GenericMessageHandler(function ($target) {
            $this->genericMessageReceivedEvent->raiseEvent($target);
        });

        $listener = new ExtensibleMessageEventHandler($logger, $client, $receivingOperationExecutionTime, $messageHandlers);
        $listener->serviceBusExceptionEvent->addEventHandler(
            new DefaultExceptionEventHandler(function ($target) {
                $this->serviceBusExceptionEvent->raiseEvent($target);
            })
        );

        $personDetailsRequestFailed->addEventHandler(
            new DefaultExceptionEventHandler(
                function (Exception $target) use ($listener) {
                    $listener->letClientHandleServiceBusException($target, $target->getMessage());
                }
            )
        );

        return $listener;
    }

    /**
     * Get a maximum number of messages that can be received in listener run.
     *
     * @return int
     */
    public function getStopAfter(): int
    {
        return $this->extensibleMessageEventHandler->getStopAfter();
    }

    /**
     * Set a maximum number of messages that can be received in listener run.
     *
     * @param int $stopAfter
     * @return MessageEventHandler
     */
    public function setStopAfter(int $stopAfter): MessageEventHandler
    {
        $this->extensibleMessageEventHandler->setStopAfter($stopAfter);
        return $this;
    }

    /**
     * @return int
     */
    public function getExceptionCounterLimit(): int
    {
        return $this->extensibleMessageEventHandler->getExceptionCounterLimit();
    }

    /**
     * @param int $exceptionCounterLimit
     * @return MessageEventHandler
     */
    public function setExceptionCounterLimit(int $exceptionCounterLimit): MessageEventHandler
    {
        $this->extensibleMessageEventHandler->setExceptionCounterLimit($exceptionCounterLimit);
        return $this;
    }

    /**
     * @return int
     */
    public function getExceptionSleepTimeSeconds(): int
    {
        return $this->extensibleMessageEventHandler->getExceptionSleepTimeSeconds();
    }

    /**
     * @param int $exceptionSleepTimeSeconds
     * @return MessageEventHandler
     */
    public function setExceptionSleepTimeSeconds(int $exceptionSleepTimeSeconds): MessageEventHandler
    {
        $this->extensibleMessageEventHandler->setExceptionSleepTimeSeconds($exceptionSleepTimeSeconds);
        return $this;
    }

    /**
     * Getter for PersonDetailsReceivedEvent
     *
     * @return EventHandlerCollection
     */
    public function getPersonDetailsReceivedEvent(): EventHandlerCollection
    {
        return $this->personDetailsReceivedEvent;
    }

    /**
     * Getter for PersonDetailRequestReceivedEvent
     *
     * @return EventHandlerCollection
     */
    public function getPersonDetailsRequestReceivedEvent(): EventHandlerCollection
    {
        return $this->personDetailsRequestReceivedEvent;
    }

    /**
     * Getter for genericMessageReceivedEvent
     *
     * @return EventHandlerCollection
     */
    public function getGenericMessageReceivedEvent(): EventHandlerCollection
    {
        return $this->genericMessageReceivedEvent;
    }

    /**
     * Getter for serviceBusExceptionEvent
     *
     * @return EventHandlerCollection
     */
    public function getServiceBusExceptionEvent(): EventHandlerCollection
    {
        return $this->serviceBusExceptionEvent;
    }

    /**
     * Run the listener (SDK part) that receives and handles the messages from service bus
     *
     * @return int
     * @throws FifaConnectIdException
     */
    public function run(): int
    {
        return $this->extensibleMessageEventHandler->run();
    }
}