<?php
/**
 * @author Aitoc Team
 * @copyright Copyright (c) 2020 Aitoc (https://www.aitoc.com)
 * @package Aitoc_PreOrders
 */

/**
 * Copyright © 2018 Aitoc. All rights reserved.
 * Developed by Alex Yakimovich
 */

namespace Aitoc\PreOrders\Model;

use Aitoc\PreOrders\Api\NotificationManagementInterface;
use Aitoc\PreOrders\Api\OrderItemPreOrderSettingsRepositoryInterface;
use Aitoc\PreOrders\Api\Data\OrderItemPreOrderSettingsInterface;
use Aitoc\PreOrders\Api\PreOrderSubscribersRepositoryInterface;
use Aitoc\PreOrders\Api\Data\PreOrderSubscribersInterface;
use Aitoc\PreOrders\Api\Data\Source\SendingStatusInterface;
use Aitoc\PreOrders\Helper\Settings as SettingsHelper;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Api\FilterBuilder;
use Magento\Framework\App\Area;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\MailException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\Mail\TransportInterface;
use Magento\Sales\Model\Order\ItemRepository as OrderItemRepository;

/**
 * Class for managing pre-order email notifications.
 */
class NotificationManagement implements NotificationManagementInterface
{
    const BACK_IN_STOCK_NOTIFICATION_TYPE_ID = 1;
    const FOLLOW_UP_NOTIFICATION_TYPE_ID = 2;
    const RETURN_TO_STOCK_FAILURE_NOTIFICATION_TYPE_ID = 3;

    const RETURN_TO_STOCK_FAILURE_NOTIFICATION_EMAIL_TEMPLATE = 'preorder_return_to_stock_failure';

    /**
     * @var FilterBuilder
     */
    private $filterBuilder;

    /**
     * @var OrderItemPreOrderSettingsRepositoryInterface
     */
    private $orderItemPreOrderSettingsRepository;

    /**
     * @var OrderItemRepository
     */
    private $orderItemRepository;

    /**
     * @var ProductRepositoryInterface
     */
    private $productRepository;

    /**
     * @var SearchCriteriaBuilder
     */
    private $searchCriteriaBuilder;

    /**
     * @var PreOrderSubscribersRepositoryInterface
     */
    private $preOrderSubscribersRepository;

    /**
     * @var SettingsHelper
     */
    private $settingsHelper;

    /**
     * @var TransportBuilder
     */
    private $transportBuilder;

    /**
     * @param FilterBuilder $filterBuilder
     * @param OrderItemPreOrderSettingsRepositoryInterface $orderItemPreOrderSettingsRepository
     * @param OrderItemRepository $orderItemRepository
     * @param PreOrderSubscribersRepositoryInterface $preOrderSubscribersRepository
     * @param ProductRepositoryInterface $productRepository
     * @param SearchCriteriaBuilder $searchCriteriaBuilder
     * @param SettingsHelper $settingsHelper
     * @param TransportBuilder $transportBuilder
     */
    public function __construct(
        FilterBuilder $filterBuilder,
        OrderItemPreOrderSettingsRepositoryInterface $orderItemPreOrderSettingsRepository,
        OrderItemRepository $orderItemRepository,
        PreOrderSubscribersRepositoryInterface $preOrderSubscribersRepository,
        ProductRepositoryInterface $productRepository,
        SearchCriteriaBuilder $searchCriteriaBuilder,
        SettingsHelper $settingsHelper,
        TransportBuilder $transportBuilder
    ) {
        $this->filterBuilder = $filterBuilder;
        $this->orderItemPreOrderSettingsRepository = $orderItemPreOrderSettingsRepository;
        $this->orderItemRepository = $orderItemRepository;
        $this->preOrderSubscribersRepository = $preOrderSubscribersRepository;
        $this->productRepository = $productRepository;
        $this->searchCriteriaBuilder = $searchCriteriaBuilder;
        $this->settingsHelper = $settingsHelper;
        $this->transportBuilder = $transportBuilder;
    }

    /**
     * @param int $productId
     * @return void
     * @throws LocalizedException
     */
    public function sendBackInStockNotificationsByProductId($productId)
    {
        $notificationInfo = $this->getDataForSending(self::BACK_IN_STOCK_NOTIFICATION_TYPE_ID, $productId);
        $template = $this->settingsHelper->getBackInStockNotificationEmailTemplate();
        $sender = [
            'name' => $this->settingsHelper->getBackInStockNotificationSenderName(),
            'email' => $this->settingsHelper->getBackInStockNotificationSenderEmail()
        ];
        foreach ($notificationInfo as $item) {
            $emailInfo = $item['email_info'];
            $email = $emailInfo['email'];
            $name = $emailInfo['first_name'] . ' ' . $emailInfo['last_name'];
            $templateParams = [
                'customerName' => $name,
                'incrementId' => $emailInfo['increment_id'],
                'productName' => $emailInfo['product_name']
            ];
            $this->sendMessage($email, $name, $sender, $template, $templateParams);
            $settingsModel = $item['order_item_pre_order_settings'];
            $this->markAsSent($settingsModel);
        }
    }

    /**
     * @param int $productId
     * @return void
     * @throws LocalizedException
     * @throws MailException
     */
    public function sendFollowUpNotificationsByProductId($productId)
    {
        $notificationInfo = $this->getDataForSending(self::FOLLOW_UP_NOTIFICATION_TYPE_ID, $productId);
        $template = $this->settingsHelper->getFollowUpNotificationEmailTemplate();
        $sender = [
            'name' => $this->settingsHelper->getFollowUpNotificationSenderName(),
            'email' => $this->settingsHelper->getFollowUpNotificationSenderEmail()
        ];
        foreach ($notificationInfo as $item) {
            /** @var PreOrderSubscribersInterface $subscriber */
            $subscriber = $item['subscriber'];
            try {
                $email = $subscriber->getCustomerEmail();
                $name = $subscriber->getCustomerName();
                $templateParams = [
                    'customerName' => $name,
                    'productName' => $item['product_name']
                ];
                $this->sendMessage($email, $name, $sender, $template, $templateParams);
                $this->markAsSent($subscriber);
            } catch (LocalizedException $e) {
                $this->markAsError($subscriber, $e->getMessage());
            }
        }
    }

    /**
     * @param int $productId
     * @return void
     * @throws LocalizedException
     * @throws MailException
     */
    public function sendReturnToStockFailureNotification($productId)
    {
        //TODO: error logger
        $templateParams = $this->getDataForSending(self::RETURN_TO_STOCK_FAILURE_NOTIFICATION_TYPE_ID, $productId);
        $template = self::RETURN_TO_STOCK_FAILURE_NOTIFICATION_EMAIL_TEMPLATE;
        $sender = 'general';
        $email = $this->settingsHelper->getChangeToInStockByDateEmailSetting();
        $name = $email;
        $this->sendMessage($email, $name, $sender, $template, $templateParams);
    }

    /**
     * @param string $notificationType
     * @param int $productId
     * @return array
     * @throws LocalizedException
     */
    private function getDataForSending($notificationType, $productId)
    {
        $data = [];
        switch ($notificationType) {
            case self::BACK_IN_STOCK_NOTIFICATION_TYPE_ID:
                $data = $this->getDataForBackInStockNotification($productId);
                break;

            case self::FOLLOW_UP_NOTIFICATION_TYPE_ID:
                $subscribers = $this->getPendingSubscribersByProductId($productId);
                foreach ($subscribers as $subscriber) {
                    $tmpData = [];
                    $tmpData['subscriber'] = $subscriber;
                    $product = $this->productRepository->getById($subscriber->getProductId());
                    $tmpData['product_name'] = $product->getName();

                    $data[] = $tmpData;
                }
                break;

            case self::RETURN_TO_STOCK_FAILURE_NOTIFICATION_TYPE_ID:
                $product = $this->productRepository->getById($productId);
                $data = [
                    'productName' => $product->getName(),
                    'productSKU' => $product->getSku()
                ];
                break;
        }
        return $data;
    }

    /**
     * @param $productId
     * @return array
     * @throws LocalizedException
     * @throws InputException
     * @throws NoSuchEntityException
     */
    private function getDataForBackInStockNotification($productId)
    {
        $data = [];
        $this->searchCriteriaBuilder
            ->addFilter(OrderItemPreOrderSettingsInterface::IS_PRE_ORDER, '1')
            ->addFilter(OrderItemPreOrderSettingsInterface::IS_NOTIFIED, '0');
        $searchCriteria = $this->searchCriteriaBuilder->create();
        $orderItemPreOrderSettingsList = $this->orderItemPreOrderSettingsRepository->getList($searchCriteria);

        foreach ($orderItemPreOrderSettingsList->getItems() as $orderItemPreOrderSettings) {
            $orderItemId = $orderItemPreOrderSettings->getOrderItemId();
            /** @var \Magento\Sales\Model\Order\Item $orderItem */
            $orderItem = $this->orderItemRepository->get($orderItemId);
            // TODO: filtering on database level
            if ($orderItem->getProductId() != $productId) {
                continue;
            }
            $order = $orderItem->getOrder();
            $data[] = [
                'email_info' => [
                    'email' => $order->getCustomerEmail(),
                    'first_name' => $order->getCustomerFirstName(),
                    'last_name' => $order->getCustomerLastName(),
                    'increment_id' => $order->getIncrementId(),
                    'product_name' => $orderItem->getProduct()->getName()
                ],
                'order_item_pre_order_settings' => $orderItemPreOrderSettings
            ];
        }

        return $data;
    }

    /**
     * @param int $productId
     * @return PreOrderSubscribersInterface[]
     * @throws LocalizedException
     */
    private function getPendingSubscribersByProductId($productId)
    {
        $this->searchCriteriaBuilder
            ->addFilter(PreOrderSubscribersInterface::PRODUCT_ID, $productId)
            ->addFilter(
                PreOrderSubscribersInterface::SENDING_STATUS,
                SendingStatusInterface::TYPE_PENDING
            );
        $searchCriteria = $this->searchCriteriaBuilder->create();

        $subscribersList = $this->preOrderSubscribersRepository->getList($searchCriteria);

        return $subscribersList->getItems();
    }

    /**
     * @param string $email
     * @param string $name
     * @param array|string $sender
     * @param string $templateId
     * @param array $params
     * @throws MailException
     */
    private function sendMessage($email, $name, $sender,  $templateId, $params)
    {
        /** TODO: real storeId */
        $storeId = 1;
        /** @var TransportInterface $transport */
        $transport = $this->transportBuilder->setTemplateIdentifier(
            $templateId
        )->setTemplateOptions(
            ['area' => Area::AREA_FRONTEND, 'store' => $storeId]
        )->setTemplateVars(
            $params
        )->setFrom(
            $sender
        )->addTo(
            $email,
            $name
        )->getTransport();
        $transport->sendMessage();
    }

    /**
     * @param mixed $model
     * @throws LocalizedException
     */
    private function markAsSent($model)
    {
        switch (true) {
            case ($model instanceof OrderItemPreOrderSettingsInterface):
                $model->setIsNotified(true);
                $this->orderItemPreOrderSettingsRepository->save($model);
                break;

            case ($model instanceof PreOrderSubscribersInterface):
                $model->setSendingStatus(SendingStatusInterface::TYPE_SENT);
                $this->preOrderSubscribersRepository->save($model);
                break;
        }
    }

    /**
     * @param mixed $model
     * @param string $message
     * @throws LocalizedException
     */
    private function markAsError($model, $message = '')
    {
        switch (true) {
            case ($model instanceof PreOrderSubscribersInterface):
                $model->setSendingStatus(SendingStatusInterface::TYPE_ERROR)
                    ->setMessage($message);
                $this->preOrderSubscribersRepository->save($model);
                break;
        }
    }
}
