<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonSetArticles\Subscribers\Backend;

use Shopware\Plugins\ViisonCommon\Classes\Subscribers\Base;

/**
 * Manipulates set articles around the notification cron job that creates notification mails about articles that are out
 * of stock.
 */
class NotificationSubscriber extends Base
{
    /**
     * "Remembered" notifications that should be reactivated later
     *
     * @var array
     */
    private $savedNotificationIds = [];

    /**
     * @inheritdoc
     */
    public static function getSubscribedEvents()
    {
        return [
            'Shopware_CronJob_Notification' => [
                [
                    'onNotificationBefore',
                    -999,
                ],
                [
                    'onNotificationAfter',
                    999,
                ],
            ],
        ];
    }

    /**
     * Executed before Notification CronJob.
     *
     * Mark (and remembers) all notifications of set articles as "sent", so the CronJob doesn't "see" them. This way no
     * notification is sent falsely (since set articles instock in the db is not correct). Check the notifications
     * manually and sent emails if necessary. Reactivate unsent set article notifications after the CronJob, so it can be
     * processed later on.
     *
     * @param \Shopware_Components_Cron_CronJob $job
     */
    public function onNotificationBefore(\Shopware_Components_Cron_CronJob $job)
    {
        $this->savedNotificationIds = [];
        // Find all set article notifications (without any further filtering)
        $builder = $this->get('models')->createQueryBuilder();
        $builder
            ->select(
                'Notification as notification',
                'Notification.mail as customerEmail',
                'Notification.articleNumber as articleNumber',
                'ArticleDetail.id',
                'Article.notification as sendNotification',
                'Article.id as articleID'
            )
            ->from('Shopware\\Models\\Article\\Notification', 'Notification')
            ->leftJoin('Shopware\\Models\\Article\\Detail', 'ArticleDetail', 'WITH', 'ArticleDetail.number = Notification.articleNumber')
            ->leftJoin('ArticleDetail.attribute', 'ArticleDetailAttribute')
            ->leftJoin('ArticleDetail.article', 'Article')
            ->where(
                $builder->expr()->eq(
                    'ArticleDetailAttribute.viisonSetarticleActive',
                    1
                )
            )
            ->andWhere(
                $builder->expr()->eq(
                    'Notification.send',
                    0
                )
            );
        $setArticleNotifications = $builder->getQuery()->getResult();

        /**
         * Handle set article notifications, sent mails when set articles are available, or save them for later.
         *
         * Remark: this copies the send mechanism from Notification plugins onRunCronJob
         */
        $setArticleDetailIds = array_map(static function ($setArticleNotification) {
            return $setArticleNotification['id'];
        }, $setArticleNotifications);
        $setArticleAvailabilities = $this->get('models')
            ->getRepository('Shopware\\CustomModels\\ViisonSetArticles\\SetArticle')
            ->getCombinedSetArticleDetailsBatchData($setArticleDetailIds);
        foreach ($setArticleNotifications as $setArticleNotification) {
            $setArticleAvailability = $setArticleAvailabilities[$setArticleNotification['id']];

            /**
             * Remark: even if the article "is available" (e.g. instock < 0, but laststock = 0), no email is sent,
             * because instock is checked every time. This is different to the frontend. (but copied from the
             * Notification plugin)
             */
            if ($setArticleAvailability &&
                $setArticleNotification['sendNotification'] == true &&
                $setArticleAvailability['available'] &&
                $setArticleAvailability['instock'] > 0
            ) {
                // Send notification email
                $context = [
                    'sArticleLink' => $setArticleNotification['notification']->getShopLink() .
                        '?sViewport=detail&sArticle=' . $setArticleNotification['articleID'],
                    'sOrdernumber' => $setArticleNotification['articleNumber'],
                    'sData' => $job['data'],
                ];

                $mail = Shopware()->TemplateMail()->createMail('sARTICLEAVAILABLE', $context);
                $mail->addTo($setArticleNotification['customerEmail']);
                $mail->send();
            } else {
                // Remember notification ID to reactivate them after the Shopware CronJob
                $this->savedNotificationIds[] = $setArticleNotification['notification']->getId();
            }

            // In every case: mark notification as "sent" to bypass Shopwares CronJob
            // Remark: the cron job does not remove notifications, it just sets them to "sent"
            $setArticleNotification['notification']->setSend(1);
            $this->get('models')->flush($setArticleNotification['notification']);
        }

        // Flush changes once
        $this->get('models')->flush();
    }

    /**
     * Executed after Notification CronJob. Reactivate set article notifications that have not been sent.
     *
     * @param \Shopware_Components_Cron_CronJob $job
     */
    public function onNotificationAfter(\Shopware_Components_Cron_CronJob $job)
    {
        foreach ($this->savedNotificationIds as $savedNotificationId) {
            $unsentNotification = $this->get('models')->find('Shopware\\Models\\Article\\Notification', $savedNotificationId);
            if ($unsentNotification) {
                $unsentNotification->setSend(0);
                $this->get('models')->flush($unsentNotification);
            }
        }
    }
}
