<?php

namespace FiloBlu\Esb\Framework\Producer;

use Exception;
use FiloBlu\Esb\Api\Data\MessageInterfaceFactory;
use FiloBlu\Esb\Api\Data\ObjectTypeDescriptorInterface;
use FiloBlu\Esb\Api\Data\ProducerConfigurationInterface;
use FiloBlu\Esb\Api\Data\ProducerInterface;
use FiloBlu\Esb\Api\Data\StatusInterface;
use FiloBlu\Esb\Api\Data\StatusInterfaceFactory;
use FiloBlu\Esb\Core\Expression\EvaluatorInterface;
use FiloBlu\Esb\Core\Expression\EvaluatorInterfaceFactory;
use FiloBlu\Esb\Core\Filler\MessageFiller;
use FiloBlu\Esb\Framework\ObjectType\Endpoint\AbstractEndpoint;
use Magento\Framework\DataObject;
use Magento\Framework\DataObjectFactory;
use Psr\Log\LoggerInterface;
use Throwable;

/**
 * Class AbstractProducer
 * @package FiloBlu\Esb\Framework\Producer
 */
abstract class AbstractProducer extends AbstractEndpoint implements ProducerInterface
{
    /**
     * Key for conditions in parameters
     * @var string
     */
    const CONDITIONS = 'conditions';

    /**
     * Key for payload template
     * @var string
     */
    const PAYLOAD_KEY = 'payload';

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

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

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

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

    /**
     * @var DataObjectFactory
     */
    protected $dataObjectFactory;

    /**
     * @var MessageFiller
     */
    protected $messageFiller;

    /** @var StatusInterface */
    protected $status;
    /**
     * @var StatusInterfaceFactory
     */
    private $statusFactory;

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

    /**
     * @return DataObjectFactory
     */
    public function getDataObjectFactory()
    {
        return $this->dataObjectFactory;
    }

    /**
     * @return MessageInterfaceFactory
     */
    public function getMessageFactory()
    {
        return $this->messageFactory;
    }

    /**
     * @inheritDoc
     */
    public function configure(ProducerConfigurationInterface $configuration): ProducerInterface
    {
        $this->configuration = $configuration;
        return $this;
    }

    /**
     * @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;
        }
    }

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

    /**
     * @return StatusInterface
     */
    public function getStatus(): StatusInterface
    {
        if ($this->status !== null) {
            return $this->status;
        }

        $this->status = $this->statusFactory->create();
        return $this->status;
    }
}
