<?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\ViisonCommon\Classes\Subscribers;

use Shopware\Models\Article\Detail as ArticleDetail;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util;

/**
 * Class FrontendArticleCartEnabler
 *
 * If an article has inStock <= 0 and lastStock active it will be shown as not available in the frontend.
 *
 * In some plugins (e.g. SetArticles or DropShipping) we want to display articles as available in the frontend even
 * though they are not. We achieve this by manipulating the view variables that are passed to the frontend. In some
 * cases however Shopware calculates availability in controller functions while working with data directly from the
 * database. Hence we cannot replace our availability values there.
 *
 * This results in messages like "This article is not available" shown in the cart, even though we displayed the article
 * as available in the article detail view. The information is conflicting.
 *
 * This Subscriber wraps around some of these checks by disabling (and later enabling) the lastStock flag of articles
 * that the plugin declares as available in the frontend to suppress these messages.
 */
// phpcs:ignore VIISON.Classes.AbstractClassName
abstract class FrontendArticleCartEnabler extends AbstractBaseSubscriber
{

    /**
     * Remember article numbers of articles where laststock was temporary disabled, so it can be enabled again.
     *
     * @var string[]
     */
    private $disabledLastStockArticleOrderNumbers = [];

    /**
     * @inheritdoc
     */
    public static function getSubscribedEvents()
    {
        return [
            'sBasket::sAddArticle::before' => 'onBeforeSAddArticle',
            'sBasket::sAddArticle::after' => 'onAfterSAddArticle',
            'Shopware_Controllers_Frontend_Checkout::getAvailableStock::before' => 'onBeforeGetAvailableStock',
            'Shopware_Controllers_Frontend_Checkout::getAvailableStock::after' => 'onAfterGetAvailableStock',
        ];
    }

    /**
     * This function defines which articles should always be available in the frontend (e.g. a specific attribute and
     * config setting is set). Returns false for normal (irrelevant) or unavailable articles.
     *
     * @param string $orderNumber
     * @return bool
     */
    abstract protected function doAvailabilityCheck($orderNumber);

    /**
     * Manipulate (disable) lastStock before the article is added to the card.
     * Remark: The SQL in sBasket->getArticleForAddArticle() checks the Detail's laststock flag (in SW5.4).
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onBeforeSAddArticle(\Enlight_Hook_HookArgs $args)
    {
        $this->disableLastStockIfNecessary($args->getId());
    }

    /**
     * Undo (enable) lastStock manipulation after the article was added to the card.
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onAfterSAddArticle(\Enlight_Hook_HookArgs $args)
    {
        $this->enableLastStockIfNecessary($args->getId());
    }

    /**
     * Manipulate (disable) lastStock before inStock values are checked in the checkout view.
     * Remark: The SQL in Checkout->getAvailableStock() checks the Article's lastStock flag.
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onBeforeGetAvailableStock(\Enlight_Hook_HookArgs $args)
    {
        $this->disableLastStockIfNecessary($args->get('ordernumber'));
    }

    /**
     * Undo (enable) lastStock manipulation after checkout view.
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onAfterGetAvailableStock(\Enlight_Hook_HookArgs $args)
    {
        $this->enableLastStockIfNecessary($args->get('ordernumber'));
    }

    /**
     * Undo (enable) lastStock manipulation, if lastStock was disabled by this Subscriber before.
     *
     * @param string $orderNumber
     */
    protected function enableLastStockIfNecessary($orderNumber)
    {
        if (!$this->disabledLastStockArticleOrderNumbers[$orderNumber]) {
            return;
        }

        $articleDetail = $this->get('models')->getRepository('Shopware\\Models\\Article\\Detail')->findOneBy([
            'number' => $orderNumber,
        ]);
        if ($articleDetail) {
            $this->setLastStock($articleDetail, true);
        }
    }

    /**
     * Manipulate (disable) lastStock of a given article if lastStock is enabled and the plugin declares the article to
     * be available. Manipulates the Article or ArticleDetail depending on the current Shopware version.
     *
     * @param string $orderNumber
     */
    protected function disableLastStockIfNecessary($orderNumber)
    {
        if (!$this->doAvailabilityCheck($orderNumber)) {
            return;
        }

        $articleDetail = $this->get('models')->getRepository('Shopware\\Models\\Article\\Detail')->findOneBy([
            'number' => $orderNumber,
        ]);
        if (!$articleDetail || !$articleDetail->getArticle()) {
            return;
        }

        // Shopware 5.6 deprecated the `article->getLastStock()` method and it will be removed in Shopware 5.7.
        // Because of this we have to use the articleDetail starting with Shopware 5.6.
        if (Util::assertMinimumShopwareVersion('5.6.0')) {
            $lastStockActive = $articleDetail->getLastStock();
        } else {
            $supportsDetailLastStock = Util::assertMinimumShopwareVersion('5.4.0');
            $article = $articleDetail->getArticle();
            $lastStockActive = $article->getLastStock() || ($supportsDetailLastStock && $articleDetail->getLastStock());
        }
        if ($lastStockActive) {
            $this->disabledLastStockArticleOrderNumbers[$articleDetail->getNumber()] = true;
            $this->setLastStock($articleDetail, false);
        }
    }

    /**
     * Sets lastStock of given ArticleDetail to the given value. Manipulates the Detail in Shopware 5.4+, manipulates
     * the Article in any case.
     *
     * @param ArticleDetail $articleDetail
     * @param bool $lastStockActive
     */
    protected function setLastStock(ArticleDetail $articleDetail, $lastStockActive)
    {
        if (Util::assertMinimumShopwareVersion('5.4.0')) {
            $articleDetail->setLastStock($lastStockActive);
            $this->get('models')->flush($articleDetail);
        }

        $articleDetail->getArticle()->setLastStock($lastStockActive);
        $this->get('models')->flush($articleDetail->getArticle());
    }
}
