<?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\Components;

use \Exception;
use \InvalidArgumentException;
use Shopware\Components\DependencyInjection\Container;
use Shopware\Components\Model\ModelManager;
use Shopware\CustomModels\ViisonPickwareERP\Warehouse\BinLocation;
use Shopware\CustomModels\ViisonPickwareMobile\PickedQuantity\PickedQuantity;
use Shopware\CustomModels\ViisonPickwareMobile\PickedQuantity\StockLedgerEntryMapping;
use Shopware\Models\Article\Detail as ArticleDetail;
use Shopware\Models\Order\Detail as OrderDetail;
use Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\StockChangeListFactory;
use Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockItemUpdater;
use Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockLedgerService;
use Shopware_Components_Snippet_Manager;

class PickedQuantityUpdaterService
{
    /**
     * @var ModelManager
     */
    protected $entityManager;

    /**
     * @var Shopware_Components_Snippet_Manager
     */
    protected $snippetManager;

    /**
     * @var StockLedgerService
     */
    protected $stockLedgerService;

    /**
     * @var StockChangeListFactory
     */
    protected $stockChangeListFactory;

    /**
     * @var StockItemUpdater
     */
    protected $stockItemUpdate;

    /**
     * @param ModelManager $entityManager
     * @param Shopware_Components_Snippet_Manager $snippetManager
     * @param StockLedgerService $stockLedgerService
     * @param StockChangeListFactory $stockChangeListFactory
     * @param StockItemUpdater $stockItemUpdate
     */
    public function __construct(
        ModelManager $entityManager,
        Shopware_Components_Snippet_Manager $snippetManager,
        StockLedgerService $stockLedgerService,
        StockChangeListFactory $stockChangeListFactory,
        StockItemUpdater $stockItemUpdate
    ) {
        $this->entityManager = $entityManager;
        $this->snippetManager = $snippetManager;
        $this->stockLedgerService = $stockLedgerService;
        $this->stockChangeListFactory = $stockChangeListFactory;
        $this->stockItemUpdate = $stockItemUpdate;
    }

    /**
     * First checks for any existing picked quantity for the given $orderDetail and $binLocation and either updates its
     * quantity or, if no entity exists yet, creates a new entity with the given $quantity. In any case and a new 'sale'
     * stock entry for the given $quantity and $binLocation is logged. If the optional $stockEntryItemData is provided,
     * it is used to create stock entry items that are associated with the created stock entry. Finally the cached total
     * picked quantity is updated and the created/updated picked quantity entity is returned.
     *
     * @param OrderDetail $orderDetail
     * @param BinLocation $binLocation
     * @param int $quantity
     * @param array[] $stockEntryItemData (optional)
     * @return PickedQuantity
     * @throws Exception
     */
    public function increasePickedQuantity(
        OrderDetail $orderDetail,
        BinLocation $binLocation,
        $quantity,
        array $stockEntryItemData = []
    ) {
        if ($quantity <= 0) {
            throw new InvalidArgumentException('Parameter "quantity" must be grater than zero when increasing picked quantity.');
        }

        // Try to find the article detail
        $articleDetail = $this->entityManager->getRepository(ArticleDetail::class)->findOneBy([
            'number' => $orderDetail->getArticleNumber(),
        ]);
        if (!$articleDetail) {
            throw new Exception(
                sprintf('Article detail with number "%s" not found', $orderDetail->getArticleNumber())
            );
        }

        // Try to find an existing picked quantitiy for the same order detail and bin location
        $pickedQuantity = $this->entityManager->getRepository(PickedQuantity::class)->findOneBy([
            'orderDetail' => $orderDetail,
            'binLocation' => $binLocation,
        ]);
        if ($pickedQuantity) {
            $pickedQuantity->setQuantity($pickedQuantity->getQuantity() + $quantity);
        } else {
            // Create new picked quantity entity
            $pickedQuantity = new PickedQuantity($orderDetail, $binLocation, $quantity);
            $this->entityManager->persist($pickedQuantity);
        }
        $this->entityManager->flush($pickedQuantity);

        // Log stock changes
        $stockChanges = $this->stockChangeListFactory->createSingleBinLocationStockChangeList(
            $binLocation,
            -1 * $quantity
        );
        $stockEntries = $this->stockLedgerService->recordSoldStock($articleDetail, $orderDetail, $stockChanges);

        // Add the stock entry items to the stock entries and associate them with the picked quantity
        $newStockEntryMappings = [];
        foreach ($stockEntries as $stockEntry) {
            // Assign item property data
            $stockEntryItemDataSlice = array_splice($stockEntryItemData, 0, abs($stockEntry->getChangeAmount()));
            if (count($stockEntryItemDataSlice) > 0) {
                $this->stockItemUpdate->addStockItemsWithProperties($stockEntry, $stockEntryItemDataSlice);
            }

            // Map stock entry to picked quantity
            $stockEntryMapping = new StockLedgerEntryMapping($pickedQuantity, $stockEntry);
            $this->entityManager->persist($stockEntryMapping);
            $newStockEntryMappings[] = $stockEntryMapping;
        }
        $this->entityManager->flush($newStockEntryMappings);

        // Update the cached picked quantity
        $attribute = $orderDetail->getAttribute();
        $attribute->setPickwarePickedQuantity($attribute->getPickwarePickedQuantity() + $quantity);
        $this->entityManager->flush($attribute);

        return $pickedQuantity;
    }

    /**
     * @param OrderDetail $orderDetail
     * @param bool $revertStockChanges (optional) Default to false
     * @param bool $moveStockToOriginalLocation Indicates if the stock should be moved back to it's original location.
     */
    public function clearPickedQuantities(
        OrderDetail $orderDetail,
        $revertStockChanges = false,
        $moveStockToOriginalLocation = false
    ) {
        // Update the cached picked quantity
        $attribute = $orderDetail->getAttribute();
        $attribute->setPickwarePickedQuantity(0);
        $this->entityManager->flush($attribute);

        // Check for any picked quantities
        /** @var PickedQuantity[] $pickedQuantities */
        $pickedQuantities = $this->entityManager->getRepository(PickedQuantity::class)->findBy([
            'orderDetail' => $orderDetail,
        ]);
        if (count($pickedQuantities) === 0) {
            return;
        }

        $namespace = $this->snippetManager->getNamespace('viison_pickware_mobile/picked_quantity_updater');
        $comment = $namespace->get('clear_picked_quantities/stock_entry_comment');

        // Revert stock changes of the picked quantities and remove the picked quantity entities
        foreach ($pickedQuantities as $pickedQuantity) {
            if ($revertStockChanges) {
                // Log 'manual' stock entries for each associated 'sale' entry to the respective default bin location
                // in the same warehouse using the respective article detail, quantity and purchase price of the
                // original entry (without updating inStock because the picked quantity remains sold)
                /** @var StockLedgerEntryMapping $stockEntryMapping */
                foreach ($pickedQuantity->getStockLedgerEntryMappings() as $stockEntryMapping) {
                    $saleStockEntry = $stockEntryMapping->getStockLedgerEntry();
                    $warehouse = $pickedQuantity->getBinLocation()->getWarehouse();
                    $binLocation = ($moveStockToOriginalLocation) ? $pickedQuantity->getBinLocation() : $warehouse->getNullBinLocation();
                    $stockChange = $this->stockChangeListFactory->createSingleBinLocationStockChangeList(
                        $binLocation,
                        -1 * $saleStockEntry->getChangeAmount()
                    );

                    $stockEntries = $this->stockLedgerService->recordSoldStockCorrection(
                        $saleStockEntry->getArticleDetail(),
                        $orderDetail,
                        $stockChange,
                        $comment
                    );

                    // Add the order detail to the created stock entries for reference
                    foreach ($stockEntries as $stockEntry) {
                        $stockEntry->setOrderDetail($orderDetail);
                    }
                    $this->entityManager->flush($stockEntries);

                    // Remove all stock entry items from the original stock entry
                    foreach ($saleStockEntry->getStockItems() as $stockEntryItem) {
                        $this->entityManager->remove($stockEntryItem);
                    }
                    $this->entityManager->flush($saleStockEntry->getStockItems()->toArray());
                    $saleStockEntry->getStockItems()->clear();
                }
            }

            // Remove picked quantity
            $this->entityManager->remove($pickedQuantity);
            $this->entityManager->flush($pickedQuantity);
        }
    }
}
