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

use Doctrine\Common\Collections\ArrayCollection;
use Shopware\CustomModels\ViisonPickwareMobile\PickProfile\PickProfile;
use Shopware\Models\Document\Document as DocumentType;
use Shopware\Models\Media\Album as MediaAlbum;
use Shopware\Models\Price\Group as PriceGroup;
use Shopware\Models\User\User;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;
use Shopware\Plugins\ViisonPickwareCommon\Classes\Subscribers\PickwareAppConfig as PickwareCommonAppConfigSubscriber;
use Shopware\Plugins\ViisonPickwareCommon\Structs\CustomDocumentType;
use Shopware\Plugins\ViisonPickwareCommon\Structs\PageSize;
use Shopware\Plugins\ViisonPickwareERP\Components\PluginConfig as PickwareErpPluginConfigService;
use Shopware\Plugins\ViisonPickwareMobile\Classes\ShippingProvider\ShippingDocumentTypeHandling;

class PickwareMobileAppConfigSubscriber extends PickwareCommonAppConfigSubscriber
{
    use ShippingDocumentTypeHandling;

    const ACL_RESOURCE_NAME_PICKING = 'pickware_wms_picking';
    const ACL_RESOURCE_NAME_STOCKING = 'pickware_wms_stocking';

    /**
     * @see \Shopware\Plugins\ViisonPickwareCommon\Classes\Subscribers\PickwareAppConfig::updateConfig()
     */
    public function updateConfig($config, $appName)
    {
        switch ($appName) {
            case 'Picking':
                $config += $this->getPickingConfig();
                break;
            case 'Stocking':
                $config += $this->getStockingConfig();
                break;
        }

        $pluginConfigService = $this->get('viison_pickware_mobile.plugin_config');
        $config['productSortField'] = $pluginConfigService->getProductSortField();

        return $config;
    }

    /**
     * @see \Shopware\Plugins\ViisonPickwareCommon\Classes\Subscribers\PickwareAppConfig::collectPickwareAclRules()
     */
    public function collectPickwareAclRules($appName, User $user)
    {
        if (!in_array($appName, ['Picking', 'Stocking'])) {
            return [];
        }

        $resourceName = ($appName === 'Picking') ? self::ACL_RESOURCE_NAME_PICKING : self::ACL_RESOURCE_NAME_STOCKING;

        $acl = $this->get('acl');
        if (!$acl->has($resourceName)) {
            return [];
        }

        $userRole = $user->getRole();
        $aclRules = [];
        foreach ($acl->get($resourceName)->getPrivileges() as $privilege) {
            $aclRules[$privilege->getName()] = $this->get('acl')->isAllowed(
                $userRole,
                $resourceName,
                $privilege->getName()
            );
        }

        return $aclRules;
    }

    /**
     * @see \Shopware\Plugins\ViisonPickwareCommon\Classes\Subscribers\PickwareAppConfig::getRequiredACLPrivileges()
     */
    public function getRequiredACLPrivileges($appName)
    {
        if (!in_array($appName, ['Picking', 'Stocking'])) {
            return [];
        }

        $privileges = [
            'order' => [
                'read',
                'update',
            ],
        ];
        if ($appName === 'Stocking') {
            $privileges['article'] = [
                'read',
                'create',
            ];
            $privileges['mediamanager'] = [
                'read',
                'create',
                'upload',
                'delete',
            ];
            $privileges['variant'] = [
                'read',
                'update',
            ];
        }

        return $privileges;
    }

    /**
     * @inheritdoc
     */
    public function onCollectCustomDocumentTypes(\Enlight_Event_EventArgs $args)
    {
        if ($args->get('appName') !== 'Picking') {
            return null;
        }

        // Get the shipping document types of all installed shipping providers
        $documentTypes = [];
        $providers = $this->get('viison_pickware_mobile.shipping_provider_repository')->getProviders();
        foreach ($providers as $provider) {
            if (!method_exists($provider, 'getAvailableShippingDocumentTypes')) {
                continue;
            }
            // Convert the provider document types to instances of CustomDocumentType
            $providerDocumentTypes = $provider->getAvailableShippingDocumentTypes();
            foreach ($providerDocumentTypes as $documentType) {
                $documentTypes[] = new CustomDocumentType(
                    self::makeShippingDocumentTypeIdGloballyUnique($documentType->getId(), $provider),
                    $documentType->getName(),
                    new PageSize(
                        $documentType->getPageSizeName(),
                        $documentType->getPageSizeWidth(),
                        $documentType->getPageSizeHeight()
                    )
                );
            }
        }

        return new ArrayCollection($documentTypes);
    }

    /**
     * Loads all available shipping providers and all of their products and adds
     * them to a list. Finally, that list and the document generation and printing
     * modes are added to an array and returned.
     *
     * @return array
     */
    private function getPickingConfig()
    {
        // Load all products from all shipping providers
        $providers = $this->get('viison_pickware_mobile.shipping_provider_repository')->getProviders();
        $shippingProviders = [];
        foreach ($providers as $provider) {
            // Get the provider's products and convert them
            $products = array_map(function ($product) {
                return [
                    'id' => $product->getIdentifier(),
                    'name' => $product->getName(),
                    'packageDimensionsRequired' => $product->packageDimensionsRequired(),
                    'options' => $product->getAvailableOptions(),
                    'defaultPackageDimensions' => (method_exists($product, 'getDefaultPackageDimensions')) ? $product->getDefaultPackageDimensions() : null,
                ];
            }, $provider->shippingProducts());
            if (count($products) === 0) {
                continue;
            }

            // Add the provider and its products to the list
            $shippingProviders[] = [
                'provider' => $provider->getIdentifier(),
                'name' => $provider->getName(),
                'products' => $products,
            ];
        }

        // Get pick profiles
        $pickProfiles = $this->get('models')->getRepository(PickProfile::class)->findAll();
        $pickProfiles = array_map(
            function (PickProfile $pickProfile) {
                return [
                    'id' => $pickProfile->getId(),
                    'name' => $pickProfile->getName(),
                ];
            },
            $pickProfiles
        );

        // Return the document generation and printing mode as well as the shipping providers
        return [
            'documentGeneration' => $this->getPluginConfig()->get('pickingAppDocumentGenerationMode', 0),
            'documentGenerationExtraDocumentsWithInvoice' => $this->getExtraDocumentTypes('pickingAppDocumentGenerationExtraDocumentsWithInvoice'),
            'documentGenerationExtraDocumentsWithDeliveryNote' => $this->getExtraDocumentTypes('pickingAppDocumentGenerationExtraDocumentsWithDeliveryNote'),
            'documentPrinting' => $this->getPluginConfig()->get('pickingAppDocumentPrintingMode', 0),
            'documentPrintingSendInvoiceViaEmail' => $this->getPluginConfig()->get('pickingAppSendInvoiceViaEmail', false),
            'documentPrintingInvoiceCopies' => $this->getPluginConfig()->get('pickingAppDocumentPrintingInvoiceCopies', 1),
            'createShippingLabelUponCompletion' => $this->getPluginConfig()->get('pickingAppCreateShippingLabelUponCompletion', false),
            'shippingProviders' => $shippingProviders,
            'pickProfiles' => $pickProfiles,
        ];
    }

    /**
     * @return array
     */
    private function getStockingConfig()
    {
        // Parse the inventory stock entry comments from the config of the main ViisonPickwareERP plugin
        $comments = $this->get('plugins')->Core()->ViisonPickwareERP()->Config()->get('stockEntryComments', '');
        $comments = ViisonCommonUtil::safeExplode(',', $comments);
        $comments = array_map(function ($comment) {
            return trim(ViisonCommonUtil::trimN($comment, '"'));
        }, $comments);

        // Check for auto generated article numbers
        $suggestArticleNumber = $this->get('config')->backendAutoOrderNumber;
        if ($suggestArticleNumber) {
            // Prefetch the next generated article number
            $articleNumber = $this->get('db')->fetchOne(
                'SELECT number
                FROM s_order_number
                WHERE name = \'articleordernumber\''
            );
            $articleNumber = intval($articleNumber) + 1;
        }

        // Find the 'reshipments' and 'supplier orders' media albums
        $returnShipmentsAlbum = $this->get('models')->getRepository(MediaAlbum::class)->findOneBy([
            'name' => 'Pickware Retouren',
        ]);
        $supplierOrdersAlbum = $this->get('models')->getRepository(MediaAlbum::class)->findOneBy([
            'name' => 'Pickware Lieferantenbestellungen',
        ]);

        // Fetch some rudimentary information about the available price groups (id, name) that are relevant for the
        // article creation in the stocking app. No further information about discounts and customer groups is provided.
        $priceGroups = $this->get('models')->getRepository(PriceGroup::class)->findAll();
        $priceGroups = array_map(function (PriceGroup $priceGroup) {
            return [
                'id' => $priceGroup->getId(),
                'name' => $priceGroup->getName(),
            ];
        }, $priceGroups);

        /** @var PickwareErpPluginConfigService $pluginConfigService */
        $pickwareErpPluginConfigService = $this->get('pickware.erp.plugin_config_service');

        return [
            'stockEntryComments' => $comments,
            'suggestArticleNumber' => $suggestArticleNumber,
            'nextArticleNumber' => $articleNumber,
            'articleNumberPrefix' => $this->get('config')->backendAutoOrderNumberPrefix,
            'reshipmentsMediaAlbumId' => ($returnShipmentsAlbum) ? $returnShipmentsAlbum->getId() : null,
            'supplierOrdersMediaAlbumId' => ($supplierOrdersAlbum) ? $supplierOrdersAlbum->getId() : null,
            'purchasePriceMode' => $this->get('plugins')->get('Core')->get('ViisonPickwareERP')->Config()->get('purchasePriceMode'),
            'priceGroups' => $priceGroups,
            'showCurrentStockWhenStocktaking' => $pickwareErpPluginConfigService->getShowCurrentStockWhenStocktaking(),
        ];
    }

    /**
     * Loads the plugin config value with the given $configFieldName, parses its value by either safely exploding it
     * or fetching it as an array and tries to find the document types for the configured IDs. If the IDs of the default
     * document types 'invoice' or 'delivery note' are contained in the config element, they are not added to the parsed
     * types. Finally an array containing the types' number range names is returned.
     *
     * @param string $configFieldName
     * @return string[]
     */
    private function getExtraDocumentTypes($configFieldName)
    {
        $extraDocumentIds = $this->getPluginConfig()->get($configFieldName, '');
        if (is_string($extraDocumentIds)) {
            // Old, string based config element
            $extraDocumentIds = ViisonCommonUtil::safeExplode(',', $extraDocumentIds);
        } elseif ($extraDocumentIds instanceof \Enlight_Config) {
            // New, combobox config element
            $extraDocumentIds = $this->getPluginConfig()->toArray()[$configFieldName];
        } else {
            // Invalid type
            return [];
        }

        // Filter out 'invoice' and 'delivery note' as well as all types that don't exist
        $extraDocumentTypes = array_filter(array_map(function ($documentId) {
            $documentType = $this->get('models')->find(DocumentType::class, $documentId);

            return ($documentType && !in_array($documentId, [1, 2])) ? $documentId : null;
        }, $extraDocumentIds));

        return array_unique(array_values($extraDocumentTypes));
    }
}
