<?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\Consumer\AbstractConsumer;
use FiloBlu\Esb\Framework\Template\TemplateInterfaceFactory;
use Magento\Framework\DataObject;
use Magento\Framework\DataObjectFactory;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\HTTP\ZendClientFactory;
use Magento\Framework\Webapi\ServiceOutputProcessor;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Zend_Http_Client;
use Zend_Http_Client_Exception;
use Zend_Http_Response;

/**
 *
 * {
 *     "endpoint" : {
 *         "url" : "http://talend.filoblu.com:8088/rest/in/magento/create_order",
 *         "content_type" : "application/json",
 *         "method" : "get",
 *         "timeout" : 30,
 *         "max_redirects" : 1
 *     },
 *     "template_variables" : {
 *         "platform" : "Santoni M2",
 *         "market" : "all",
 *         "channeltype" : "magento"
 *         "body": "##object##"
 *     },
 *     "template_message" : {
 *         "platform" : "#platform#",
 *         "market" : "#market#",
 *         "channeltype" : "#channeltype#",
 *         "message" : "#body#"
 *     },
 *    "on_success_action" : {
 *        "action" : "change_status",
 *        "parameters" : "order_exported"
 *    }
 * }
 *
 * Class SendJsonToEndpointConsumer
 * @package FiloBlu\Esb\Model
 */
abstract class RestConsumer extends AbstractConsumer
{
    /** @var string */
    const ENDPOINT_URL = 'endpoint/url';

    /** @var string */
    const ENDPOINT_CONTENT_TYPE = 'endpoint/content_type';

    /** @var string */
    const ENDPOINT_TIMEOUT = 'endpoint/timeout';

    /** @var string */

    const ENDPOINT_MAX_REDIRECTS = 'endpoint/max_redirects';

    /** @var string */
    const ENDPOINT_HTTP_METHOD = 'endpoint/method';

    /** @var string */
    const TEMPLATE_BODY = 'template_message';

    /** @var string */
    const TEMPLATE_VARIABLES = 'template_variables';

    /** @var string */
    const OBJECT_TAG = '##object##';

    /** @var ServiceOutputProcessor */
    protected $serviceOutputProcessor;

    /** @var MessageInterface */
    protected $message;

    /** @var DataObject|null */
    protected $dataForEvaluation;

    /** @var ZendClientFactory */

    protected $httpClientFactory;

    /** @var ConverterResolverInterface */
    protected $converterResolver;

    /** @var TemplateInterfaceFactory */
    protected $templateFactory;

    /** @var SwitchExpressionEvaluatorFactory */
    protected $switchEvaluatorFactory;

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

    /**
     * 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(
            $evaluatorFactory,
            $objectTypeDescriptor,
            $messageFactory,
            $objectTypeFromMessage,
            $statusFactory,
            $logger
        );
        $this->httpClientFactory = $httpClientFactory;
        $this->converterResolver = $converterResolver;
        $this->templateFactory = $templateFactory;
        $this->switchEvaluatorFactory = $switchEvaluatorFactory;
        $this->dataObjectFactory = $dataObjectFactory;
    }

    /**
     * @param MessageInterface $message
     * @return MessageInterface
     * @throws NoSuchEntityException
     * @throws NonRecoverableException
     * @throws Zend_Http_Client_Exception
     */
    public function consume(MessageInterface $message): MessageInterface
    {
        $this->message = $message;
        $client = $this->send($message);
        $body = $client->getBody();
        $this->getStatus()->setOutputData($body);
        if ($client->isSuccessful()) {
            $this->getStatus()->setCode(StatusInterface::SUCCESS);
        } else {
            $this->getStatus()->setCode(StatusInterface::ERROR);
        }

        return $this->messageFactory->create();
    }

    /**
     * @param MessageInterface $message
     * @return Zend_Http_Response
     * @throws NoSuchEntityException
     * @throws NonRecoverableException
     * @throws Zend_Http_Client_Exception
     */
    protected function send(MessageInterface $message)
    {
        $parameters = $this->getConfiguration()->getParameters();

        $uri = $parameters->getDataByPath(self::ENDPOINT_URL);
        $method = $parameters->getDataByPath(self::ENDPOINT_HTTP_METHOD);
        $contentType = $parameters->getDataByPath(self::ENDPOINT_CONTENT_TYPE);
        $timeout = $parameters->getDataByPath(self::ENDPOINT_TIMEOUT) ?: 30;
        $maxRedirects = $parameters->getDataByPath(self::ENDPOINT_MAX_REDIRECTS) ?: 1;

        $body = $this->getBody($message, $contentType);
        $object = $this->loadObject($message);

        /** @var \FiloBlu\Esb\Core\Expression\SwitchExpressionEvaluator $switchEvaluator */
        $switchEvaluator = $this->switchEvaluatorFactory->create();
        $uri = $switchEvaluator->getVariableExpander()->expand(
            $uri,
            $this->converterResolver->getFor($object)->convert($object)
        );

        $client = $this->httpClientFactory->create();

        $client->setUri($uri);
        $client->setRawData($body);
        $client->setConfig(
            [
                'maxredirects' => $maxRedirects,
                'timeout'      => $timeout
            ]
        );
        $client->setEncType($contentType);
        $client->setMethod($this->getHttpMethod($method));

        return $client->request();
    }

    /**
     * @param MessageInterface $message
     * @param string $contentType
     * @return string
     * @throws NoSuchEntityException
     * @throws NonRecoverableException
     * @throws RuntimeException
     */
    abstract protected function getBody(MessageInterface $message, string $contentType): string;

    /**
     * @param $method
     * @return string
     */
    protected function getHttpMethod($method)
    {
        if ($method === null) {
            return Zend_Http_Client::GET;
        }
        $method = strtoupper($method);
        switch ($method) {
            case Zend_Http_Client::GET:
            case Zend_Http_Client::POST:
            case Zend_Http_Client::HEAD:
            case Zend_Http_Client::PUT:
            case Zend_Http_Client::DELETE:
            case Zend_Http_Client::TRACE:
            case Zend_Http_Client::OPTIONS:
            case Zend_Http_Client::CONNECT:
            case Zend_Http_Client::MERGE:
            case Zend_Http_Client::PATCH:
                return $method;
        }

        return Zend_Http_Client::GET;
    }

    /**
     * @param $object
     * @return DataObject
     */
    protected function getDataForEvaluation($object): DataObject
    {
        /** @var DataObject $dataToEvaluate */
        $dataToEvaluate = $this->getDataObjectFactory()->create();

        if ($object === null) {
            return $dataToEvaluate;
        }

        $dataToEvaluate->setData('data', $object);
        $dataToEvaluate->setData('origdata', $object->getOrigData() ?: $this->getDataObjectFactory()->create());
        $this->dataForEvaluation = $dataToEvaluate;
        return $this->dataForEvaluation;
    }

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