<?php

namespace FiloBlu\Esb\Model\Consumer;

use FiloBlu\Esb\Api\Data\MessageInterface;
use FiloBlu\Esb\Api\Data\StatusInterface;
use FiloBlu\Esb\Core\Exception\NonRecoverableException;
use FiloBlu\Esb\Framework\Template\TemplateInterface;
use FiloBlu\Esb\Model\ResponseFactory;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Sales\Api\Data\OrderInterface;
use RuntimeException;

use function in_array;

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

    /** @var string */
    public const VARIABLES = 'variables';

    /** @var string */
    public const ITEM_VARIABLES = 'item_variables';

    /** @var string */
    public const OBJECT_ITEMS = '##object_items##';

    /** @var string */
    public const ALLOWED_PRODUCT_ITEMS_TYPE = 'allowed_product_items_type';


    /**
     * @param MessageInterface $message
     * @return MessageInterface
     * @throws NoSuchEntityException
     * @throws NonRecoverableException
     */
    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;
    }

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

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