<?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.

use Shopware\Components\Api\Exception as ApiException;
use Shopware\Components\Api\Manager as ResourceManager;
use Shopware\CustomModels\ViisonPickwareERP\Warehouse\BinLocation;
use Shopware\CustomModels\ViisonPickwareERP\Warehouse\Warehouse;
use Shopware\Models\Article\Detail as ArticleDetail;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;
use Shopware\Plugins\ViisonPickwareCommon\Classes\Util as PickwareUtil;
use Shopware\Plugins\ViisonPickwareERP\Components\BarcodeLabel\Article\ArticleBarcodeLabelType;
use Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\BinLocationStockChange;
use Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\RelocationStockChangeList;
use Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockLedgerValidationException;

/**
 * This controller adds a new method to the REST API variants resource for creating
 * stock entries in the database for the given variant.
 */
class Shopware_Controllers_Api_ViisonPickwareMobileVariants extends Shopware_Controllers_Api_Rest
{
    /**
     * Tries to find the requested variant and relocates its stock based on the
     * POSTed bin locations.
     *
     * POST /api/variants/{id}/relocations
     */
    public function postRelocationsAction()
    {
        // Check the privileges
        ResourceManager::getResource('variant')->checkPrivilege('update');

        // Check the payload for the required values
        $variant = $this->findVariantFromRequestParams();
        $sourceBinLocation = $this->findBinLocationFromRequestParams('sourceBinLocationId');
        $destinationBinLocation = $this->findBinLocationFromRequestParams('destinationBinLocationId');
        $quantity = $this->Request()->getParam('quantity');
        $comment = $this->Request()->getParam('comment');

        // Validate the quantity
        if ($quantity === null) {
            throw new ApiException\ParameterMissingException('quantity');
        } elseif (intval($quantity) < 0) {
            throw new ApiException\CustomValidationException('Parameter "quantity" must not be negative.');
        }

        // Relocate as much stock as requested
        try {
            $stockChanges = new RelocationStockChangeList(
                [new BinLocationStockChange($sourceBinLocation, (-1 * $quantity))],
                new BinLocationStockChange($destinationBinLocation, $quantity)
            );
            $this->get('pickware.erp.stock_ledger_service')->recordRelocatedStock($variant, $stockChanges, $comment);
        } catch (InvalidArgumentException $exception) {
            throw new ApiException\CustomValidationException($exception->getMessage());
        } catch (StockLedgerValidationException $exception) {
            throw new ApiException\CustomValidationException($exception->getMessage());
        }

        // Check whether the destination bin location should be selected as the new default location
        if ($this->Request()->getParam('selectDestinationBinLocationAsDefault') === true) {
            $this->get('pickware.erp.stock_ledger_service')->selectDefaultBinLocation(
                $variant,
                $destinationBinLocation->getWarehouse(),
                $destinationBinLocation
            );
        }

        // Prepare the response data
        $binLocationMappings = PickwareUtil::getVariantBinLocationMappingArrays([$variant->getId()]);
        $responseData = [
            'binLocationMappings' => ($binLocationMappings[$variant->getId()]) ?: [],
        ];

        $this->Response()->setHttpResponseCode(201);
        $this->View()->assign([
            'success' => true,
            'data' => $responseData,
        ]);
    }

    /**
     * Tries to find the requested variant and adds a new stock entry for it
     * using the Pickware stock manager.
     *
     * POST /api/variants/{id}/stock
     */
    public function postStockAction()
    {
        // Check the privileges
        ResourceManager::getResource('variant')->checkPrivilege('update');

        // Get and check the payload for the required values
        $variant = $this->findVariantFromRequestParams();
        $binLocation = $this->findBinLocationFromRequestParams('binLocationId');
        $quantity = $this->Request()->getParam('quantity');
        $price = $this->Request()->getParam('price');
        $comment = $this->Request()->getParam('comment');
        if ($quantity === null) {
            throw new ApiException\ParameterMissingException('quantity');
        }
        if ($quantity === 0) {
            throw new ApiException\CustomValidationException('Parameter "quantity" must not be zero.');
        }

        // Check whether the bin location should be selected as the new default location
        $selectBinLocationAsDefault = $this->Request()->getParam('selectBinLocationAsDefault');
        if ($selectBinLocationAsDefault === true && $quantity <= 0) {
            throw new ApiException\CustomValidationException(
                'Cannot select a source bin location as the default bin location when logging outgoing stock.'
            );
        }

        // Create a new stock entry for the variant and set the posted values
        $stockChanges = $this->get('pickware.erp.stock_change_list_factory_service')->createSingleBinLocationStockChangeList(
            $binLocation,
            $quantity
        );
        if ($quantity > 0) {
            $this->get('pickware.erp.stock_ledger_service')->recordIncomingStock($variant, $stockChanges, $price, $comment);
            if ($selectBinLocationAsDefault) {
                $this->get('pickware.erp.stock_ledger_service')->selectDefaultBinLocation(
                    $variant,
                    $binLocation->getWarehouse(),
                    $binLocation
                );
            }
        } elseif ($quantity < 0) {
            $this->get('pickware.erp.stock_ledger_service')->recordOutgoingStock($variant, $stockChanges, $comment);
        }

        // Check if the variant shall be added to the barcode label list
        if ($this->Request()->getParam('createBarcodeLabels', false) !== false) {
            $articleBarcodeLabelType = $this->get('pickware.erp.barcode_label_service')->getBarcodeLabelTypeByName(
                ArticleBarcodeLabelType::IDENTIFIER
            );
            $articleBarcodeLabelType->enqueueForPrinting($variant->getNumber(), $quantity);
        }

        // Prepare the response data
        $binLocationMappings = PickwareUtil::getVariantBinLocationMappingArrays([$variant->getId()]);
        $responseData = [
            'binLocationMappings' => ($binLocationMappings[$variant->getId()]) ?: [],
            'purchasePrice' => $variant->getPurchasePrice(),
        ];

        $this->Response()->setHttpResponseCode(201);
        $this->View()->assign([
            'success' => true,
            'data' => $responseData,
        ]);
    }

    /**
     * Tries to find the requested variant and logs a stocktake for its stock based on the
     * POSTed bin location.
     *
     * POST /api/variants/{id}/stocktakes
     */
    public function postStocktakesAction()
    {
        // Check the privileges
        ResourceManager::getResource('variant')->checkPrivilege('update');

        // Get and check the payload for the required values
        $variant = $this->findVariantFromRequestParams();
        $binLocation = $this->findBinLocationFromRequestParams('binLocationId');
        $stock = $this->Request()->getParam('stock');
        $comment = $this->Request()->getParam('comment');
        if ($stock === null) {
            throw new ApiException\ParameterMissingException('stock');
        }

        // Create a new stocktake entry for the variant
        $this->get('pickware.erp.stock_ledger_service')->recordStocktake($variant, $binLocation, $stock, $comment);

        // Prepare the response data
        $binLocationMappings = PickwareUtil::getVariantBinLocationMappingArrays([$variant->getId()]);
        $responseData = [
            'binLocationMappings' => ($binLocationMappings[$variant->getId()]) ?: [],
        ];

        $this->Response()->setHttpResponseCode(201);
        $this->View()->assign([
            'success' => true,
            'data' => $responseData,
        ]);
    }

    /**
     * @param string $paramKey
     * @return ArticleDetail
     */
    private function findVariantFromRequestParams()
    {
        $variantId = $this->Request()->getParam('id');
        $variant = $this->get('models')->find(ArticleDetail::class, $variantId);
        if (!$variant) {
            throw new ApiException\NotFoundException(
                sprintf('Variant with ID %d not found', $variantId)
            );
        }

        return $variant;
    }

    /**
     * @param string $paramKey
     * @return BinLocation
     */
    private function findBinLocationFromRequestParams($paramKey)
    {
        $binLocationId = $this->Request()->getParam($paramKey, 0);
        $binLocation = $this->get('models')->find(BinLocation::class, $binLocationId);
        if (!$binLocation) {
            throw new ApiException\NotFoundException(
                sprintf('Bin location with ID %d not found', $binLocationId)
            );
        }

        return $binLocation;
    }
}
