<?php

namespace FiloBlu\Esb\Framework\Consumer;

use Exception;
use FiloBlu\Esb\Api\Data\ConsumerConfigurationInterface;
use FiloBlu\Esb\Api\Data\ConsumerInterface;
use FiloBlu\Esb\Api\Data\MessageInterface;
use FiloBlu\Esb\Api\Data\MessageInterfaceFactory;
use FiloBlu\Esb\Api\Data\ObjectTypeDescriptorInterface;
use FiloBlu\Esb\Api\Data\StatusInterface;
use FiloBlu\Esb\Api\Data\StatusInterfaceFactory;
use FiloBlu\Esb\Core\Exception\NonRecoverableException;
use FiloBlu\Esb\Core\Expression\EvaluatorInterface;
use FiloBlu\Esb\Core\Expression\EvaluatorInterfaceFactory;
use FiloBlu\Esb\Core\Extractor\ObjectTypeFromMessage;
use FiloBlu\Esb\Framework\ObjectType\Endpoint\AbstractEndpoint;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\NoSuchEntityException;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Throwable;

/**
 * Class AbstractConsumer
 * @package FiloBlu\Esb\Model\Consumer
 */
abstract class AbstractConsumer extends AbstractEndpoint implements ConsumerInterface
{
    /**
     * Key for conditions in parameters
     * @var string
     */
    const CONDITIONS = 'conditions';

    /**
     * @var ConsumerConfigurationInterface
     */
    protected $configuration;

    /**
     * @var EvaluatorInterfaceFactory
     */
    protected $evaluatorFactory;

    /**
     * @var MessageInterfaceFactory
     */
    protected $messageFactory;

    /**
     * @var ObjectTypeFromMessage
     */
    protected $objectTypeFromMessage;

    /**
     * @var StatusInterface
     */
    protected $status;

    /**
     * @var StatusInterfaceFactory
     */
    protected $statusFactory;
    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * AbstractConsumer constructor.
     * @param EvaluatorInterfaceFactory $evaluatorFactory
     * @param ObjectTypeDescriptorInterface $objectTypeDescriptor
     * @param MessageInterfaceFactory $messageFactory
     * @param ObjectTypeFromMessage $objectTypeFromMessage
     * @param StatusInterfaceFactory $statusFactory
     * @param LoggerInterface $logger
     */
    public function __construct(
        EvaluatorInterfaceFactory $evaluatorFactory,
        ObjectTypeDescriptorInterface $objectTypeDescriptor,
        MessageInterfaceFactory $messageFactory,
        ObjectTypeFromMessage $objectTypeFromMessage,
        StatusInterfaceFactory $statusFactory,
        LoggerInterface $logger
    ) {
        parent::__construct($objectTypeDescriptor);
        $this->evaluatorFactory = $evaluatorFactory;
        $this->messageFactory = $messageFactory;
        $this->objectTypeFromMessage = $objectTypeFromMessage;
        $this->statusFactory = $statusFactory;
        $this->logger = $logger;
    }

    /**
     * @param DataObject $dataToEvaluate
     * @return bool
     */
    public function isConditionSatisfied(DataObject $dataToEvaluate): bool
    {
        $parameters = $this->getConfiguration()->getParameters();

        if (!$parameters->hasData(self::CONDITIONS)) {
            return true;
        }

        $conditions = $parameters->getData(self::CONDITIONS);

        if ($conditions === null || empty($conditions)) {
            return true;
        }

        /** @var EvaluatorInterface $evaluator */
        try {
            return $this->evaluatorFactory->create()->compile($conditions)->evaluate($dataToEvaluate);
        } catch (Exception $exception) {
            $this->logger->warning($exception->getMessage(), ['exception' => $exception]);
            return false;
        } catch (Throwable $throwable) {
            $this->logger->warning($throwable->getMessage(), ['exception' => $throwable]);
            return false;
        }
    }

    /**
     * @return ConsumerConfigurationInterface
     */
    public function getConfiguration(): ConsumerConfigurationInterface
    {
        return $this->configuration;
    }

    /**
     * @param MessageInterface $message
     * @return mixed
     * @throws NoSuchEntityException
     * @throws RuntimeException
     * @throws NonRecoverableException
     */
    public function loadObject(MessageInterface $message)
    {
        $objectType = $this->objectTypeFromMessage->extract($message);

        if (!$this->isSuitableFor($objectType)) {
            throw new NonRecoverableException('This consumer received wrong message');
        }

        return $this->configuration->getObjectLoader()->get($objectType);
    }

    /**
     * @param $object
     */
    public function saveObject($object)
    {
        $this->configuration->getObjectSaver()->save($this->getObjectTypeDescriptor(), $object);
    }

    /**
     * @param ConsumerConfigurationInterface $configuration
     * @return ConsumerInterface
     */
    public function configure(ConsumerConfigurationInterface $configuration): ConsumerInterface
    {
        $this->configuration = $configuration;
        return $this;
    }

    /**
     *
     * @param MessageInterface $message
     * @return MessageInterface
     */
    abstract public function consume(MessageInterface $message): MessageInterface;

    /**
     * @return StatusInterface
     */
    public function getStatus(): StatusInterface
    {
        return $this->status ?: ($this->status = $this->statusFactory->create());
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->getUniqueName();
    }
}
