<?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 FiloBlu\Esb\Model\Response;
use FiloBlu\Esb\Model\ResponseFactory;
use Magento\Framework\DataObject;
use Magento\Framework\DataObjectFactory;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\HTTP\ClientInterfaceFactory;
use Magento\Framework\Webapi\ServiceOutputProcessor;
use Psr\Log\LoggerInterface;
use RuntimeException;

/**
 *
 * {
 *     "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 \Magento\Framework\HTTP\ClientInterfaceFactory */

    protected $httpClientFactory;

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

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

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

    /** @var DataObjectFactory */
    protected $dataObjectFactory;
    /**
     * @var \FiloBlu\Esb\Model\ResponseFactory
     */
    private $responseFactory;

    /**
     * @param \Magento\Framework\HTTP\ClientInterfaceFactory $httpClientFactory
     * @param \FiloBlu\Esb\Core\Expression\EvaluatorInterfaceFactory $evaluatorFactory
     * @param \FiloBlu\Esb\Core\Expression\SwitchExpressionEvaluatorFactory $switchEvaluatorFactory
     * @param \FiloBlu\Esb\Api\Data\ObjectTypeDescriptorInterface $objectTypeDescriptor
     * @param \FiloBlu\Esb\Api\Data\MessageInterfaceFactory $messageFactory
     * @param \FiloBlu\Esb\Core\Extractor\ObjectTypeFromMessage $objectTypeFromMessage
     * @param \FiloBlu\Esb\Converter\ConverterResolverInterface $converterResolver
     * @param \FiloBlu\Esb\Api\Data\StatusInterfaceFactory $statusFactory
     * @param \FiloBlu\Esb\Framework\Template\TemplateInterfaceFactory $templateFactory
     * @param \Magento\Framework\DataObjectFactory $dataObjectFactory
     * @param \FiloBlu\Esb\Model\ResponseFactory $responseFactory
     * @param \Psr\Log\LoggerInterface $logger
     */
    public function __construct(
        ClientInterfaceFactory $httpClientFactory,
        EvaluatorInterfaceFactory $evaluatorFactory,
        SwitchExpressionEvaluatorFactory $switchEvaluatorFactory,
        ObjectTypeDescriptorInterface $objectTypeDescriptor,
        MessageInterfaceFactory $messageFactory,
        ObjectTypeFromMessage $objectTypeFromMessage,
        ConverterResolverInterface $converterResolver,
        StatusInterfaceFactory $statusFactory,
        TemplateInterfaceFactory $templateFactory,
        DataObjectFactory $dataObjectFactory,
        ResponseFactory $responseFactory,
        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;
        $this->responseFactory = $responseFactory;
    }

    /**
     * @param \FiloBlu\Esb\Api\Data\MessageInterface $message
     * @return \FiloBlu\Esb\Api\Data\MessageInterface
     * @throws \FiloBlu\Esb\Core\Exception\NonRecoverableException
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function consume(MessageInterface $message): MessageInterface
    {
        $this->message = $message;

        $response = $this->send($message);

        if ($this->getConfiguration()
                 ->getSaveRequest()) {
            $this->getStatus()
                 ->setInputData($response->getRawRequest())
            ;
        }

        if ($this->getConfiguration()
                 ->getSaveResponse()) {
            $this->getStatus()
                 ->setOutputData($response->getRawResponse())
            ;
        }

        if ($response->isSuccessful()) {
            $this->getStatus()
                 ->setCode(StatusInterface::SUCCESS)
            ;
        } else {
            $this->getStatus()
                 ->setCode(StatusInterface::ERROR)
            ;
        }

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

    /**
     * @param MessageInterface $message
     * @return \FiloBlu\Esb\Model\Response
     * @throws \FiloBlu\Esb\Core\Exception\NonRecoverableException
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    protected function send(MessageInterface $message): Response
    {
        $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;

        $requestBody = $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)
                               )
        ;

        /** @var \Magento\Framework\HTTP\ClientInterface $client */
        $client = $this->httpClientFactory->create();
        $client->setTimeout($timeout);

        if ($contentType) {
            $client->addHeader('Content-Type', $contentType);
        }

        switch ($this->getHttpMethod($method)) {
            case 'POST':
                $client->post($uri, $requestBody);
                break;
            case 'DELETE':
            case 'PUT':
                $client->setOption(CURLOPT_CUSTOMREQUEST, $method);
                $client->post($uri, $requestBody);
                break;
            case 'GET':
                $client->get($uri);
            default:
        }


        $errors = null;
        $responseType = floor(($client->getStatus() / 100));
        if ($responseType != 2 && $responseType != 1) {
            $errors = [sprintf('Unable to perform request: returned status code %s', $client->getStatus())];
        }

        return $this->responseFactory->create([
            'rawResponse' => $client->getBody(),
            'rawRequest'  => $requestBody,
            'errors'      => $errors
        ]);

        // $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)
    {
        return strtoupper(trim($method ?? '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;
    }
}
