<?php

namespace FiloBlu\Esb\Model\Consumer;

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\Converter\ConverterResolverInterface;
use FiloBlu\Esb\Core\Exception\NonRecoverableException;
use FiloBlu\Esb\Core\Expression\EvaluatorInterfaceFactory;
use FiloBlu\Esb\Core\Expression\SwitchExpressionEvaluatorFactory;
use FiloBlu\Esb\Core\Extractor\ObjectTypeFromMessage;
use FiloBlu\Esb\Framework\Template\TemplateInterface;
use FiloBlu\Esb\Framework\Template\TemplateInterfaceFactory;
use Magento\Framework\DataObjectFactory;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\HTTP\ZendClientFactory;
use Magento\Sales\Api\Data\OrderInterface;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Zend_Http_Client_Exception;

use function in_array;

/**
 * Class SendOrderItemToEndpointConsumer
 * @package FiloBlu\Esb\Model\Consumer
 */
class SendOrderItemToEndpointConsumer extends SendObjectToEndpointConsumer
{
    /** @var string */
    const ITEM_TEMPLATE_BODY = 'item_template_message';

    /** @var string */
    const VARIABLES = "variables";

    /** @var string */
    const ITEM_VARIABLES = "item_variables";

    /** @var string */
    const OBJECT_ITEMS = "##object_items##";

    /** @var string */
    const ALLOWED_PRODUCT_ITEMS_TYPE = "allowed_product_items_type";

    /**
     * SendOrderToEndpointConsumer constructor.
     * @param ZendClientFactory $httpClientFactory
     * @param EvaluatorInterfaceFactory $evaluatorFactory
     * @param SwitchExpressionEvaluatorFactory $switchEvaluatorFactory
     * @param ObjectTypeDescriptorInterface $objectTypeDescriptor
     * @param MessageInterfaceFactory $messageFactory
     * @param ObjectTypeFromMessage $objectTypeFromMessage
     * @param ConverterResolverInterface $converterResolver
     * @param StatusInterfaceFactory $statusFactory
     * @param TemplateInterfaceFactory $templateFactory
     * @param DataObjectFactory $dataObjectFactory
     * @param LoggerInterface $logger
     */
    public function __construct(
        ZendClientFactory $httpClientFactory,
        EvaluatorInterfaceFactory $evaluatorFactory,
        SwitchExpressionEvaluatorFactory $switchEvaluatorFactory,
        ObjectTypeDescriptorInterface $objectTypeDescriptor,
        MessageInterfaceFactory $messageFactory,
        ObjectTypeFromMessage $objectTypeFromMessage,
        ConverterResolverInterface $converterResolver,
        StatusInterfaceFactory $statusFactory,
        TemplateInterfaceFactory $templateFactory,
        DataObjectFactory $dataObjectFactory,
        LoggerInterface $logger
    )
    {
        parent::__construct(
            $httpClientFactory,
            $evaluatorFactory,
            $switchEvaluatorFactory,
            $objectTypeDescriptor,
            $messageFactory,
            $objectTypeFromMessage,
            $converterResolver,
            $statusFactory,
            $templateFactory,
            $dataObjectFactory,
            $logger
        );
    }

    /**
     * @param MessageInterface $message
     * @return MessageInterface
     * @throws NoSuchEntityException
     * @throws NonRecoverableException
     * @throws Zend_Http_Client_Exception
     */
    public function consume(MessageInterface $message): MessageInterface
    {
        $order = $this->loadObject($message);

        if (!$this->isConditionSatisfied($this->getDataForEvaluation($order))) {
            $this->getStatus()
                ->setCode(StatusInterface::SKIPPED)
                ->setOutputData('Condition was not satisfied');
            return $message;
        }

        $outputMessage = parent::consume($message);

        if ($this->getStatus()->getCode() !== StatusInterface::SUCCESS) {
            return $outputMessage;
        }

        $this->performAction($message);
        return $outputMessage;
    }

    /**
     * @inheritDoc
     */
    public function getName(): string
    {
        return 'Send Order Item to E.P.';
    }

    /**
     * @inheritDoc
     */
    public function getUniqueName(): string
    {
        return 'FiloBlu_Esb::send_order_item_to_endpoint';
    }

    /**
     * @param $message
     * @throws NonRecoverableException
     * @throws NoSuchEntityException
     */
    protected function performAction($message)
    {
        $parameters = $this->getConfiguration()->getParameters();

        $action = $parameters->getData('on_success_action');

        if ($action === null) {
            return;
        }

        /** @var OrderInterface $object */
        $object = $this->loadObject($message);
        $argument = $action['parameters'];

        switch ($action['action']) {
            case 'change_status':
                $endpoint = $parameters->getData('endpoint');
                $url = $endpoint['url'];

                // Compatibility with Magento 2.0 - 2.1
                if (method_exists($object, 'addStatusHistoryComment')) {
                    $object->addStatusHistoryComment(__('Order successfully sent to %1', $url), $argument);
                } else {
                    $object->addCommentToStatusHistory(__('Order successfully sent to %1', $url), $argument);
                }
                break;
            case 'add_comment':
                $endpoint = $parameters->getData('endpoint');
                $url = $endpoint['url'];
                // Compatibility with Magento 2.0 - 2.1
                if (method_exists($object, 'addStatusHistoryComment')) {
                    $object->addStatusHistoryComment(__('%1', $argument));
                } else {
                    $object->addCommentToStatusHistory(__('%1', $argument));
                }
                break;
            default:
                return;
        }

        $this->saveObject($object);
    }

    /**
     * @param MessageInterface $message
     * @param string $contentType
     * @return string
     * @throws NoSuchEntityException
     * @throws NonRecoverableException
     */
    protected function getBody(MessageInterface $message, string $contentType): string
    {
        $object = $this->loadObject($message);

        $itemDataObject = $this->getDataObjectFactory()->create();

        $objectData = $this->converterResolver->getFor($object)->convert($object);
        $parameters = $this->getConfiguration()->getParameters();
        $templateVariables = $parameters->getDataByPath(self::TEMPLATE_VARIABLES) ?? [];
        $templateItemVariables = [];
        $itemTemplateBody = $parameters->getDataByPath(self::ITEM_TEMPLATE_BODY) ?? [];
        $templateBody = $parameters->getDataByPath(self::TEMPLATE_BODY) ?? [];
        $variables = $parameters->getDataByPath(self::VARIABLES) ?? [];
        $itemVariables = $parameters->getDataByPath(self::ITEM_VARIABLES) ?? [];
        $productItemsType = $parameters->getDataByPath(self::ALLOWED_PRODUCT_ITEMS_TYPE) ?? [];

        $switchEvaluator = $this->switchEvaluatorFactory->create();
        $variableExpander = $switchEvaluator->getVariableExpander();

        $items = [];
        foreach($objectData->getItems() as $item) {
            if(!in_array($item['product_type'], $productItemsType, false)){
                continue;
            }
            $itemData = $itemDataObject->setData($item);
            foreach ($itemVariables as $name => $value) {
                $name = $variableExpander->expand($name, $itemData);

                if ($switchEvaluator->is($value)) {
                    $templateItemVariables[$name] = $switchEvaluator->evaluate($value, $itemData);
                    continue;
                }

                $templateItemVariables[$name] = $variableExpander->expand($value, $itemData);
            }
            $items[] = $templateItemVariables;
        }

        foreach ($variables as $name => $value) {
            $name = $variableExpander->expand($name, $objectData);

            if ($switchEvaluator->is($value)) {
                $templateVariables[$name] = $switchEvaluator->evaluate($value, $objectData);
                continue;
            }

            $templateVariables[$name] = $variableExpander->expand($value, $objectData);
        }

        $template = $this->templateFactory->create();

        $bodyItem = [];
        foreach($items as $i) {
            $vars = array_merge($i, $templateVariables);
            $el = $template
                ->setVariables($vars)
                ->setTemplate($itemTemplateBody)
                ->render();
            $bodyItem[] = $el->getData();
        }

        foreach ($templateVariables as $name => $value) {
            if ($value === self::OBJECT_TAG) {
                $templateVariables[$name] = $objectData->getData();
                continue;
            }

            if($value === self::OBJECT_ITEMS) {
                $templateVariables[$name] = $bodyItem;
                continue;
            }

            if ($switchEvaluator->is($value)) {
                $templateVariables[$name] = $switchEvaluator
                    ->evaluate(
                        $value,
                        $this->getDataForEvaluation($object)
                    );
            }
        }

        /** @var TemplateInterface $template */

        $body = $template
            ->setVariables($templateVariables)
            ->setTemplate($templateBody)
            ->render();
        if(empty($templateBody)){
            $body = $template
                ->setTemplate($bodyItem)
                ->render();
        }

        // TODO : use an encoder instead
        switch (strtolower($contentType)) {
            case 'application/json':
                return $body->toJson();
            case 'text/xml':
            case 'application/xml':
                return $body->toXml();
        }

        throw new RuntimeException(__('Unable to encode output for content-type "%1"', $contentType));
    }
}
