<?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\Models\Order\Detail;
use Shopware\Models\Order\Order;
use Shopware\Plugins\ViisonCommon\Classes\Util\OrderDetailUtil;
use \Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;
use Shopware\Plugins\ViisonCommon\Classes\Subscribers\Base;
use Shopware\Plugins\ViisonSetArticles\Components\PluginConfigService;

class OrderSubscriber extends Base
{
    /**
     * @inheritdoc
     */
    public static function getSubscribedEvents()
    {
        return [
            'Shopware_Controllers_Backend_Order::getList::after' => 'onAfterGetList',
            'Shopware_Controllers_Backend_OrderState_Notify' => [
                // Set priority high so it is executed at last, so that "removed" sub article quantities are not
                // overwritten afterwards
                'onOrderStateNotify',
                999,
            ],
        ];
    }

    /**
     * Gets all orders contained in the return value and adds custom values such as 'viisonSetarticleOrderid' to each of
     * their positions.
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onAfterGetList(\Enlight_Hook_HookArgs $args)
    {
        // Check the result of the original method
        $return = $args->getReturn();
        if (!$return['success']) {
            return;
        }

        // Update the returned order details
        foreach ($return['data'] as &$order) {
            foreach ($order['details'] as &$item) {
                $orderDetail = $this->get('models')->find('Shopware\\Models\\Order\\Detail', $item['id']);
                $item['viisonSetarticleOrderid'] = ($orderDetail->getAttribute()) ? $orderDetail->getAttribute()->getViisonSetarticleOrderid() : 0;
            }
        }

        // Update the result
        $args->setReturn($return);
    }

    /**
     * This method is used when an order status mail is created. ViisonPickwareMobile (or the documentation of this plugin)
     * introduces a whitelist code snippet for mail templates that allows us to hide certain positions or alter their
     * quantity.
     *
     * This method removes sub articles from the mail by removing (not adding) them from (to) the whitelist, if the
     * plugin configuration is set that way.
     *
     * @param \Enlight_Event_EventArgs $args
     */
    public function onOrderStateNotify(\Enlight_Event_EventArgs $args)
    {
        // Hide nothing if sub articles should be displayed
        /** @var PluginConfigService $pluginConfigService */
        $pluginConfigService = $this->get('viison_set_articles.plugin_config');
        if ($pluginConfigService->getDisplaySubArticlesOnDocuments() === PluginConfigService::ALWAYS_SHOW_SUB_ARTICLES_ON_DOCUMENTS) {
            return;
        }
        $order = $this->get('models')->find('Shopware\\Models\\Order\\Order', $args->get('id'));
        if (!$order) {
            return;
        }

        // Get the current event context values or create new ones
        $values = ($args->getValues()) ? $args->getValues() : [];

        // Create or use existing whitelist
        $orderItemsWhitelist = isset($values['orderItemsWhitelist']) ? $values['orderItemsWhitelist'] : [];
        $isPickedOrder = $this->isPickedOrder($order);

        // If the whitelist is empty and this is a picked order the status was likely changed from the backend
        // because of this we need to fill the whitelist with picked items here.
        if (empty($orderItemsWhitelist) && $isPickedOrder) {
            foreach ($order->getDetails() as $detail) {
                if ($detail->getAttribute()->getPickwarePickedQuantity()) {
                    $orderItemsWhitelist[$detail->getId()] = $detail->getQuantity();
                }
            }
        }

        foreach ($order->getDetails() as $detail) {
            $isNotPartOfSetArticle = $detail->getAttribute() && !$detail->getAttribute()->getViisonSetarticleOrderid();
            $isMainSetArticle = $detail->getAttribute()
                && $detail->getAttribute()->getViisonSetarticleOrderid()
                && $detail->getAttribute()->getViisonSetarticleOrderid() === $detail->getId();

            if ($isMainSetArticle && !$orderItemsWhitelist[$detail->getId()]) {
                $this->substituteSubArticlesWithSetArticleIfSetArticleCompletelyPicked($detail, $orderItemsWhitelist);
            }

            if (!$isNotPartOfSetArticle && !$isMainSetArticle) {
                // Hide sub articles because they should always be hidden
                if ($pluginConfigService->getDisplaySubArticlesOnDocuments() === PluginConfigService::NEVER_SHOW_SUB_ARTICLES_ON_DOCUMENTS) {
                    $orderItemsWhitelist[$detail->getId()] = -1;
                }

                // Hide sub articles because this is no partial delivery
                if ($pluginConfigService->getDisplaySubArticlesOnDocuments() === PluginConfigService::ONLY_SHOW_SUB_ARTICLES_ON_PARTIAL_DELIVERY_NOTES
                    && $this->isCompletelyPickedOrder($order)) {
                    $orderItemsWhitelist[$detail->getId()] = -1;
                }
            } elseif (!$isPickedOrder) {
                // Add all non sub articles when this is no wms order
                $orderItemsWhitelist[$detail->getId()] = $detail->getQuantity();
            }
        }

        // Write the whitelist back into the mail values
        $values['orderItemsWhitelist'] = $orderItemsWhitelist;
        $args->setValues($values);
    }

    /**
     * Substitute subarticles of a completely picked setarticle with the setarticle and remove subarticles
     * from the whitelist. In this context a setarticle is said to be completely picked if and only if the
     * whole ordered quantity is completely picked and not only a part of the ordered quantity. Does nothing
     * if the setarticle is not completely picked.
     * This function mimics the behavior of the substitute in the DocumentManipulationService
     * https://github.com/pickware/ShopwareSetArticles/blob/2092a6ea88c7229b524f0d74e269969f864830df/Components/DocumentManipulationService.php#L70
     *
     * @param Detail $detail
     * @param $orderItemsWhitelist
     * @return void
     */
    private function substituteSubArticlesWithSetArticleIfSetArticleCompletelyPicked(Detail $detail, &$orderItemsWhitelist)
    {
        $articleDetail = OrderDetailUtil::getArticleDetailForOrderDetail($detail);
        if (!$articleDetail) {
            return;
        }

        $subArticles = Shopware()->Container()->get('models')->getRepository('Shopware\\CustomModels\\ViisonSetArticles\\SetArticle')->findBy([
            'setId' => $articleDetail->getId(),
        ]);

        $toBeRemovedOrderDetailIds = [];
        $completed = PHP_INT_MAX;

        foreach ($subArticles as $subArticle) {
            $needed = $subArticle->getQuantity();

            $orderDetailIds = array_keys($orderItemsWhitelist);
            $orderDetails = $this->get('models')->getRepository('Shopware\\Models\\Order\\Detail')->findBy([
                'id' => $orderDetailIds,
            ]);

            $matchingOrderDetail = array_filter(
                $orderDetails,
                function ($orderDetail) use ($subArticle) {
                    $articleDetail = OrderDetailUtil::getArticleDetailForOrderDetail($orderDetail);

                    if ($articleDetail && $articleDetail->getId() === $subArticle->getArticleDetail()->getId()) {
                        return $orderDetail;
                    }
                }
            );
            if (!$matchingOrderDetail) {
                return;
            }

            $array = array_values($matchingOrderDetail);
            $orderDetailId = array_shift($array)->getId();
            $completeMainArticles = $orderItemsWhitelist[$orderDetailId] / $needed;
            $completed = min($completeMainArticles, $completed);

            if ($completed === 0) {
                return;
            }

            $toBeRemovedOrderDetailIds[] = $orderDetailId;
        }

        // If all Setarticles are picked substitute sub articles for main article
        if ($completed === $detail->getQuantity()) {
            foreach ($toBeRemovedOrderDetailIds as $toBeRemovedOrderDetailId) {
                unset($orderItemsWhitelist[$toBeRemovedOrderDetailId]);
            }
            $orderItemsWhitelist[$detail->getId()] = $completed;
        }
    }

    /**
     * Determines whether or not the order has any picked items
     *
     * @param Order $order
     * @return bool
     */
    private function isPickedOrder(Order $order)
    {
        if (!ViisonCommonUtil::isPluginInstalledAndActive('Core', 'ViisonPickwareMobile')) {
            return false;
        }

        foreach ($order->getDetails() as $detail) {
            $pickedQuantities = $detail->getAttribute() && $detail->getAttribute()->getPickwarePickedQuantity();
            if ($pickedQuantities) {
                return true;
            }
        }

        return false;
    }

    /**
     * Determines whether or not all the items of the order are picked
     *
     * @param Order $order
     * @return bool
     */
    private function isCompletelyPickedOrder(Order $order)
    {
        if (!ViisonCommonUtil::isPluginInstalledAndActive('Core', 'ViisonPickwareMobile')) {
            return false;
        }

        foreach ($order->getDetails() as $detail) {
            $pickedQuantities = $detail->getAttribute() && $detail->getAttribute()->getPickwarePickedQuantity();
            if (!$pickedQuantities) {
                return false;
            }
        }

        return true;
    }
}
