<?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\ViisonPickwareMobile\Subscribers\Api;

use Shopware\CustomModels\ViisonPickwareERP\Warehouse\BinLocation;
use Shopware\CustomModels\ViisonPickwareERP\Warehouse\Warehouse;
use Shopware\Models\Article\Article;
use Shopware\Models\Article\Detail as ArticleDetail;
use Shopware\Plugins\ViisonCommon\Classes\Subscribers\Base;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;
use Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\BinLocationStockChange;
use Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\RelocationStockChangeList;

class ArticlesSubscriber extends Base
{
    /**
     * @see \Shopware\Plugins\ViisonCommon\Classes\Subscribers\Base::getSubscribedEvents()
     */
    public static function getSubscribedEvents()
    {
        return [
            'Shopware_Controllers_Api_Articles::postAction::before' => 'onBeforePostAction',
            'Shopware_Controllers_Api_Articles::postAction::after' => 'onAfterPostAction',
        ];
    }

    /**
     * Checks the payload for an article number and if it is not set, generates one.
     * That is, the schema defined in the shop configuration is used to determine a new
     * article number as long as the current one is already used. Finally the created article
     * number is injected into the payload of the request.
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onBeforePostAction(\Enlight_Hook_HookArgs $args)
    {
        // Check the payload and whether an article number shall be created
        $params = $args->getSubject()->Request()->getPost();
        if (!is_array($params['mainDetail']) || !empty($params['mainDetail']['number']) || !$this->get('config')->backendAutoOrderNumber) {
            return;
        }

        // Find the next available article number
        $articleNumberPrefix = $this->get('config')->backendAutoOrderNumberPrefix;
        $numberRangeIncrementer = $this->get('shopware.number_range_incrementer');
        do {
            $articleNumber = $numberRangeIncrementer->increment('articleordernumber');
            $numberTaken = $this->get('db')->fetchOne(
                'SELECT id
                FROM s_articles_details
                WHERE ordernumber = ?',
                [
                    ($articleNumberPrefix . $articleNumber)
                ]
            );
        } while ($numberTaken);

        // Inject the retrieved number into the request payload
        $params['mainDetail']['number'] = $articleNumberPrefix . $articleNumber;
        ViisonCommonUtil::setRequestPostData($params);
    }

    /**
     * Checks the response for the ID of the newly created article and finds the main
     * detail/variant of that article, which is than injected into the response as
     * 'mainDetailId'. Finally, if the POSTed mainDetail data contains a valid
     * 'binLocationId', a new relocation is logged in the stock manager, to move the
     * article detail to that location.
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onAfterPostAction(\Enlight_Hook_HookArgs $args)
    {
        // Try to get the ID of the created article
        $responseData = $args->getSubject()->View()->data;
        if (!is_array($responseData) || $responseData['id'] === null) {
            return;
        }

        // Add the ID of the article's main detail to the response
        $article = $this->get('models')->find(Article::class, $responseData['id']);
        $responseData['mainDetailId'] = $article->getMainDetail()->getId();
        $args->getSubject()->View()->data = $responseData;

        $params = $args->getSubject()->Request()->getParams();
        if (isset($params['mainDetail']['binLocationId'])) {
            // Try to find the bin location and relocate the main detail to it, if necessary
            $binLocation = $this->get('models')->find(BinLocation::class, $params['mainDetail']['binLocationId']);
            if ($binLocation) {
                $this->relocateAllStock($article->getMainDetail(), $binLocation);
            }
        }
    }

    /**
     * @param ArticleDetail $articleDetail
     * @param BinLocation $destinationBinLocation
     */
    private function relocateAllStock(ArticleDetail $articleDetail, BinLocation $destinationBinLocation)
    {
        // Check whether the article detail is already mapped to the given bin location
        $binLocationMappings = $this->get('models')->getRepository(Warehouse::class)->findSortedWarehouseBinLocationMappings(
            $destinationBinLocation->getWarehouse(),
            $articleDetail
        );
        foreach ($binLocationMappings as $binLocationMapping) {
            if ($binLocationMapping->getBinLocation()->equals($destinationBinLocation)) {
                // Bin location already mapped
                return;
            }
        }

        // Check for any stock that can be relocated
        $destinationWarehouseStock = $this->get('pickware.erp.article_detail_stock_info_provider_service')->getStockInWarehouse(
            $articleDetail,
            $destinationBinLocation->getWarehouse()
        );
        if ($destinationWarehouseStock <= 0) {
            // No relocation possible
            return;
        }

        // Relocate the stock of all mapped bin locations to the given bin location
        $destinationStockChange = 0;
        $sourceStockChanges = [];
        foreach ($binLocationMappings as $binLocationMapping) {
            $destinationStockChange += $binLocationMapping->getStock();
            $sourceStockChanges[] = new BinLocationStockChange(
                $binLocationMapping->getBinLocation(),
                -1 * $binLocationMapping->getStock()
            );
        }
        $stockChanges = new RelocationStockChangeList(
            $sourceStockChanges,
            new BinLocationStockChange($destinationBinLocation, $destinationStockChange)
        );
        $this->get('pickware.erp.stock_ledger_service')->recordRelocatedStock(
            $articleDetail,
            $stockChanges,
            'Änderung des Lagerplatzes bei der Artikelerstellung über die REST API'
        );
    }
}
