<?php

namespace FiloBlu\Refilo\Model\Consumer;

use FiloBlu\Core\Framework\Model\Mail\MailerInterface;
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\Core\Expression\EvaluatorInterfaceFactory;
use FiloBlu\Esb\Core\Expression\SwitchExpressionEvaluatorFactory;
use FiloBlu\Esb\Core\Expression\VariableExpanderInterface;
use FiloBlu\Esb\Core\Extractor\ObjectTypeFromMessage;
use FiloBlu\Esb\Framework\Consumer\AbstractConsumer;
use FiloBlu\Esb\Framework\Template\TemplateInterface;
use FiloBlu\Esb\Framework\Template\TemplateInterfaceFactory;
use Magento\Framework\App\Area;
use Magento\Framework\App\AreaInterface;
use Magento\Framework\App\AreaList;
use Magento\Framework\App\State;
use Magento\Framework\Phrase;
use Magento\Framework\Phrase\Renderer\Placeholder;
use Magento\Store\Model\App\Emulation;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;
use RuntimeException;

use function base64_decode;
use function file_put_contents;
use function is_string;

/**
 * {
 *   "mail": {
 *          "from" : "from@example.com",
 *          "reply_to" : "replyto1@example.com",
 *          "to" : [ "to1@example.com", "to2@example.com" ],
 *          "cc" : ["cc1@example.com","cc2@example.com" ],
 *          "bcc" : ["bcc1@example.com", "bcc2@example.com"],
 *          "subject" : "subject",
 *          "template" : 32,
 *          "template_variables" : [],
 *          "template_options" : [],
 *          "template_translate" : true,
 *          "translate_template_variables" : [],
 *          "attachments" : [ "" ]
 *          "attachment_root" : "/var/www/html/var/common/forager/";
 *   }
 * }
 *
 * Class ContactConsumer
 * @package FiloBlu\Refilo\Model\Consumer
 */
class ContactConsumer extends AbstractConsumer
{
    /**
     * @var string
     */
    public const MAIL = 'mail';
    /**
     * @var string
     */
    public const VARIABLES = 'variables';
    /**
     * @var string
     */
    public const VARIABLES_TO_TRANSLATE = 'mail/translate_template_variables';
    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;
    /**
     * @var AreaList
     */
    protected $areaList;
    /**
     * @var State
     */
    protected $state;
    /**
     * @var MailerInterface
     */
    private $mailer;
    /**
     * @var \FiloBlu\Core\Framework\Model\Mail\MessageInterfaceFactory
     */
    private $mailMessageFactory;
    /**
     * @var SwitchExpressionEvaluatorFactory
     */
    private $switchEvaluatorFactory;
    /**
     * @var TemplateInterfaceFactory
     */
    private $templateFactory;
    /**
     * @var VariableExpanderInterface
     */
    private $variableExpander;
    /**
     * @var \Magento\Store\Model\App\Emulation
     */
    private $emulation;

    /**
     * ContactConsumer constructor.
     * @param EvaluatorInterfaceFactory $evaluatorFactory
     * @param ObjectTypeDescriptorInterface $objectTypeDescriptor
     * @param MessageInterfaceFactory $messageFactory
     * @param ObjectTypeFromMessage $objectTypeFromMessage
     * @param StatusInterfaceFactory $statusFactory
     * @param TemplateInterfaceFactory $templateFactory
     * @param SwitchExpressionEvaluatorFactory $switchEvaluatorFactory
     * @param MailerInterface $mailer
     * @param VariableExpanderInterface $variableExpander
     * @param \FiloBlu\Core\Framework\Model\Mail\MessageInterfaceFactory $mailMessageFactory
     * @param \Magento\Store\Model\App\Emulation $emulation
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param LoggerInterface $logger
     * @param \Magento\Framework\App\AreaList $areaList
     * @param \Magento\Framework\App\State $state
     */
    public function __construct(
        EvaluatorInterfaceFactory $evaluatorFactory,
        ObjectTypeDescriptorInterface $objectTypeDescriptor,
        MessageInterfaceFactory $messageFactory,
        ObjectTypeFromMessage $objectTypeFromMessage,
        StatusInterfaceFactory $statusFactory,
        TemplateInterfaceFactory $templateFactory,
        SwitchExpressionEvaluatorFactory $switchEvaluatorFactory,
        MailerInterface $mailer,
        VariableExpanderInterface $variableExpander,
        \FiloBlu\Core\Framework\Model\Mail\MessageInterfaceFactory $mailMessageFactory,
        Emulation $emulation,
        StoreManagerInterface $storeManager,
        LoggerInterface $logger,
        AreaList $areaList,
        State $state
    ) {
        parent::__construct(
            $evaluatorFactory,
            $objectTypeDescriptor,
            $messageFactory,
            $objectTypeFromMessage,
            $statusFactory,
            $logger
        );
        $this->mailer = $mailer;
        $this->mailMessageFactory = $mailMessageFactory;
        $this->evaluatorFactory = $evaluatorFactory;
        $this->switchEvaluatorFactory = $switchEvaluatorFactory;
        $this->templateFactory = $templateFactory;
        $this->variableExpander = $variableExpander;
        $this->emulation = $emulation;
        $this->storeManager = $storeManager;
        $this->areaList = $areaList;
        $this->state = $state;
    }

    /**
     * @param MessageInterface $message
     * @return MessageInterface
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function consume(MessageInterface $message): MessageInterface
    {
        if (!$this->isConditionSatisfied($message->getPayload())) {
            $this->getStatus()->setCode(StatusInterface::SKIPPED)->setOutputData('Condition was not satisfied');
            return $this->messageFactory->create();
        }

        $this->mailer->send($this->getMailMessage($message));

        $this->getStatus()->setCode(StatusInterface::SUCCESS);
        return $this->messageFactory->create();
    }

    /**
     * @param MessageInterface $message
     * @return mixed
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getMailMessage(MessageInterface $message)
    {
        $payload = $message->getPayload();

        $parameters = $this->getConfiguration()->getParameters();

        $templateVariables = $parameters->getDataByPath(self::VARIABLES) ?? [];
        $templateBody = $parameters->getDataByPath(self::MAIL) ?? [];

        $templateVariablesToTranslate = $parameters->getDataByPath(self::VARIABLES_TO_TRANSLATE) ?? [];
        $switchEvaluator = $this->switchEvaluatorFactory->create();

        foreach ($templateVariables as $name => $value) {
            $name = $this->variableExpander->expand($name, $payload);

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

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

        // Apply translations

        foreach ($templateVariablesToTranslate as $name) {
            if (!isset($templateVariables[$name], $templateVariables['store'])) {
                continue;
            }

            $storeId = $this->storeManager->getStore($templateVariables['store'])->getId();
            $this->emulation->startEnvironmentEmulation($storeId, Area::AREA_FRONTEND, true);
            $renderer = Phrase::getRenderer();
            if ($renderer instanceof Placeholder) {
                $this->areaList->getArea($this->state->getAreaCode())->load(AreaInterface::PART_TRANSLATE);
            }
            $templateVariables[$name] = (string)__($templateVariables[$name]);
            $this->emulation->stopEnvironmentEmulation();
        }

        /** @var TemplateInterface $template */
        $template = $this->templateFactory->create();

        $mailRendered = $template->setVariables($templateVariables)->setTemplate($templateBody)->render();

        $attachmentRoot = $mailRendered->getDataByPath('attachment_root') ?? '/tmp';
        $rawAttachments = $mailRendered->getDataByPath('attachments') ?? [];
        $attachments = [];
        foreach ($rawAttachments as $attachment) {
            $attachments[] = $this->getAttachment($attachment, $attachmentRoot);
        }

        /** @var \FiloBlu\Core\Framework\Model\Mail\MessageInterface $emailMessage */
        $emailMessage = $this->mailMessageFactory->create();

        return $emailMessage
            ->setFrom($mailRendered->getDataByPath('from'))
            ->setTo($mailRendered->getDataByPath('to') ?? [])
            ->setReplyTo($mailRendered->getDataByPath('reply_to'))
            ->setCc($mailRendered->getDataByPath('cc') ?? [])
            ->setBcc($mailRendered->getDataByPath('bcc') ?? [])
            ->setTemplate($mailRendered->getDataByPath('template'))
            ->setTemplateOptions($mailRendered->getDataByPath('template_options') ?? [])
            ->setTemplateVariables($mailRendered->getDataByPath('template_variables') ?? [])
            ->enableTranslation((bool)$mailRendered->getDataByPath('template_translate'))
            ->setAttachments($attachments);
    }

    /**
     * @param $attachment
     * @param string $attachmentRoot
     * @return string
     */
    public function getAttachment($attachment, string $attachmentRoot): string
    {
        if (is_string($attachment)) {
            return "$attachmentRoot/" . basename($attachment);
        }

        if (!isset($attachment['name'], $attachment['mime'], $attachment['base64_data'])) {
            throw new RuntimeException('Invalid attachment payload');
        }

        // basename() prevents path traversal from an attacker-controlled payload name.
        $fileName = "$attachmentRoot/" . basename((string)$attachment['name']);

        $data = base64_decode($attachment['base64_data']);

        if ($data === false) {
            throw new RuntimeException("Unable to decode file: '$fileName'");
        }

        if (file_put_contents($fileName, $data) === false) {
            throw new RuntimeException("Unable to save file: '$fileName'");
        }

        return $fileName;
    }

    /**
     * @return MailerInterface
     */
    public function getMailer()
    {
        return $this->mailer;
    }

    public function getName(): string
    {
        return 'Refilo Form Contact Consumer';
    }

    public function getUniqueName(): string
    {
        return 'FiloBlu_Refilo::form_contact_consumer';
    }
}
