<?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 Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Shopware\CustomModels\ViisonPickwareERP\Warehouse\Warehouse;
use Shopware\CustomModels\ViisonPickwareMobile\PickProfile\PickProfile;
use Shopware\CustomModels\ViisonPickwareMobile\PickProfile\PickProfilePrioritizedDispatchMethod;
use Shopware\Plugins\ViisonCommon\Controllers\ViisonCommonBaseController;
use Shopware\Plugins\ViisonPickwareMobile\Components\PickingOrderFilter\OrderFilterConditionQueryComponent\AttributeFieldQueryComponentArrayDecodingPreprocessor;
use Shopware\Plugins\ViisonPickwareMobile\Components\PickingOrderFilter\StockBasedFilterConfiguration;
use Shopware\Plugins\ViisonPickwareMobile\Components\PickProfile\PickProfileOrderFilterOptionFactoryService;

class Shopware_Controllers_Backend_ViisonPickwareMobilePickProfiles extends ViisonCommonBaseController
{
    /**
     * @inheritdoc
     */
    public function getViewParams()
    {
        return [
            'attributeFilterOptionPrefix' => AttributeFieldQueryComponentArrayDecodingPreprocessor::ATTRIBUTE_FIELD_PREFIX,
            'stockBasedOrderFilterModes' => StockBasedFilterConfiguration::VALID_FILTER_MODES,
            'availableFilterConditions' => $this->get('viison_pickware_mobile.pick_profile_order_filter_option_factory_service')->createQueryComponentOptions(),
            'operators' => PickProfileOrderFilterOptionFactoryService::getOperators(),
        ];
    }

    /**
     * Responds a filtered, sorted and paginated list of pick profiles.
     */
    public function getPickProfileListAction()
    {
        $limit = $this->Request()->getParam('limit', 1000);
        $offset = $this->Request()->getParam('start', 0);
        $sort = $this->Request()->getParam('sort', []);
        $filter = $this->Request()->getParam('filter', []);

        // Update pefixes of sort fields, because joining mutliple tables disables the alias for 'addOrderBy'
        foreach ($sort as &$sortField) {
            if (mb_strpos($sortField['property'], 'pickProfile.') === false) {
                $sortField['property'] = 'pickProfile.' . $sortField['property'];
            }
        }

        // Build the main query
        $builder = $this->get('models')->createQueryBuilder();
        $builder
            ->setAlias('pickProfile')
            ->select(
                'pickProfile',
                'prioritizedDispatchMethods',
                'prioritizedPaymentMethods',
                'stockBasedOrderFilterExemptDispatchMethods'
            )
            ->from(PickProfile::class, 'pickProfile')
            ->leftJoin('pickProfile.prioritizedDispatchMethods', 'prioritizedDispatchMethods')
            ->leftJoin('pickProfile.prioritizedPaymentMethods', 'prioritizedPaymentMethods')
            ->leftJoin('pickProfile.stockBasedOrderFilterExemptDispatchMethods', 'stockBasedOrderFilterExemptDispatchMethods')
            ->addFilter($filter)
            ->addOrderBy($sort)
            ->setFirstResult($offset)
            ->setMaxResults($limit);

        // Check for a search query
        $searchQuery = $this->Request()->getParam('query', []);
        if (!empty($searchQuery)) {
            $builder->andWhere('pickProfile.name LIKE :searchQuery');
            $builder->setParameter('searchQuery', ('%' . $searchQuery . '%'));
        }

        // Create the query and execute it to get the paginated results
        $query = $builder->getQuery();
        $query->setHydrationMode(Query::HYDRATE_ARRAY);
        $paginator = new Paginator($query);
        $total = $paginator->count();
        $result = $paginator->getIterator()->getArrayCopy();

        // Modify the result
        $result = array_map(
            function ($pickProfile) {
                // Decode the order filter query components
                $pickProfile['orderFilterQueryConditions'] = PickProfile::decodeOrderFilterQueryConditions(
                    $pickProfile['encodedOrderFilterQueryConditions']
                );
                unset($pickProfile['encodedOrderFilterQueryConditions']);

                // Keep only the Ids of associated dispatch and payment methods
                $pickProfile['prioritizedDispatchMethods'] = array_map(
                    function ($data) {
                        return $data['dispatchMethodId'];
                    },
                    $pickProfile['prioritizedDispatchMethods']
                );
                $pickProfile['prioritizedPaymentMethods'] = array_map(
                    function ($data) {
                        return $data['paymentMethodId'];
                    },
                    $pickProfile['prioritizedPaymentMethods']
                );
                $pickProfile['stockBasedOrderFilterExemptDispatchMethods'] = array_map(
                    function ($data) {
                        return $data['dispatchMethodId'];
                    },
                    $pickProfile['stockBasedOrderFilterExemptDispatchMethods']
                );

                return $pickProfile;
            },
            $result
        );

        $this->View()->assign([
            'success' => true,
            'data' => $result,
            'total' => $total,
        ]);
    }

    /**
     * Creates one or more pick profiles using the POSTed data and responds the data of those pick profiles,
     * if successful.
     */
    public function createPickProfilesAction()
    {
        $entityManager = $this->get('models');
        $pickProfilesService = $this->get('viison_pickware_mobile.pick_profiles_service');

        // Load data
        $data = $this->Request()->getParam('data');
        if (!is_array($data)) {
            $this->View()->success = false;

            return;
        }

        $createdPickProfiles = [];
        foreach ($data as $pickProfileValues) {
            $pickProfile = $pickProfilesService->createPickProfile($pickProfileValues['name']);
            $this->updatePickProfileWithValues($pickProfile, $pickProfileValues);
            $createdPickProfiles[] = $pickProfile;
        }

        // Save changes
        $entityManager->flush($createdPickProfiles);

        // Set a filter parameter using the IDs of the created pick profiles and add their data to the response
        $this->Request()->setParam('filter', [$this->createFilterForPickProfiles($createdPickProfiles)]);
        $this->getPickProfileListAction();
    }

    /**
     * Updates one or more existing pick profiles using the POSTed data and responds the updated data of those pick
     * profiles, if successful.
     */
    public function updatePickProfilesAction()
    {
        $entityManager = $this->get('models');

        // Load data
        $data = $this->Request()->getParam('data');
        if (!is_array($data)) {
            $this->View()->success = false;

            return;
        }

        $updatedPickProfiles = [];
        foreach ($data as $pickProfileValues) {
            // Try to find the pick profile
            $pickProfile = $entityManager->find(PickProfile::class, $pickProfileValues['id']);
            if (!$pickProfile) {
                continue;
            }

            $this->updatePickProfileWithValues($pickProfile, $pickProfileValues);
            $updatedPickProfiles[] = $pickProfile;
        }

        // Save changes
        $entityManager->flush($updatedPickProfiles);

        // Set a filter parameter using the IDs of the updated pick profiles and add their data to the response
        $this->Request()->setParam('filter', [$this->createFilterForPickProfiles($updatedPickProfiles)]);
        $this->getPickProfileListAction();
    }

    /**
     * Deletes one or more existing pick profiles that are provided in the POSTed data.
     */
    public function deletePickProfilesAction()
    {
        $entityManager = $this->get('models');

        // Load data
        $data = $this->Request()->getParam('data');
        if (!is_array($data)) {
            $this->View()->success = false;

            return;
        }

        $deletedPickProfiles = [];
        foreach ($data as $pickProfileValues) {
            // Try to find the pick profile
            $pickProfile = $entityManager->find(PickProfile::class, $pickProfileValues['id']);
            if (!$pickProfile) {
                continue;
            }

            $entityManager->remove($pickProfile);
            $deletedPickProfiles[] = $pickProfile;
        }

        // Save changes
        $entityManager->flush($deletedPickProfiles);

        $this->View()->success = true;
    }

    /**
     * Executes the picking order filter using the configuration of the POSTed data and responds with both the time it
     * took to run the filter and the current number of results in the default warehouse.
     */
    public function testOrderFilterAction()
    {
        // Prepare the order filter configuration using the request parameters
        $stockBasedFilterConfig = new StockBasedFilterConfiguration(
            $this->Request()->getParam('stockBasedOrderFilterMode', StockBasedFilterConfiguration::FILTER_MODE_OFF),
            $this->Request()->getParam('stockBasedOrderFilterExemptDispatchMethodIds', [])
        );
        $earliestPreOrderDate = PickProfile::createEarliestReleaseDateForPreOrderedItems(
            $this->Request()->getParam('advanceDaysForPreOrderedItems', 0)
        );
        $orderFilterQueryConditions = $this->Request()->getParam('orderFilterQueryConditions');
        if ($orderFilterQueryConditions) {
            $customCondition = $this->get('viison_pickware_mobile.order_filter_condition_query_component_factory_service')->createWithArrayEncodedQueryComponent(
                $orderFilterQueryConditions
            );
        }

        // Measure the computation time of the order filter
        $startTime = microtime(true);
        $idsOfOrdersPassingFilter = $this->get('viison_pickware_mobile.picking_order_filter_service')->getIdsOfOrdersPassingFilter(
            $this->get('models')->getRepository(Warehouse::class)->getDefaultWarehouse(),
            $stockBasedFilterConfig,
            $earliestPreOrderDate,
            $customCondition
        );
        $filterExecutionTime = microtime(true) - $startTime;

        $this->View()->assign([
            'success' => true,
            'data' => [
                'filterExecutionTime' => $filterExecutionTime,
                'filterResultCount' => count($idsOfOrdersPassingFilter),
            ],
        ]);
    }

    /**
     * Saves all pick profile properties contained in the passed `$values` in the passed `$pickProfile`.
     *
     * @param PickProfile $pickProfile
     * @param array $values
     */
    protected function updatePickProfileWithValues(PickProfile $pickProfile, array $values)
    {
        $pickProfilesService = $this->get('viison_pickware_mobile.pick_profiles_service');

        // Update all associated dispatch and payment methods
        $pickProfilesService->updatePrioritizedDispatchMethods(
            $pickProfile,
            $values['prioritizedDispatchMethods']
        );
        unset($values['prioritizedDispatchMethods']);
        $pickProfilesService->updatePrioritizedPaymentMethods(
            $pickProfile,
            $values['prioritizedPaymentMethods']
        );
        unset($values['prioritizedPaymentMethods']);
        $pickProfilesService->updateStockBasedOrderFilterExemptDispatchMethods(
            $pickProfile,
            $values['stockBasedOrderFilterExemptDispatchMethods']
        );
        unset($values['stockBasedOrderFilterExemptDispatchMethods']);

        // Update all other pick profile properties
        $pickProfile->fromArray($values);
    }

    /**
     * @param PickProfile[] $pickProfiles
     * @return array
     */
    protected function createFilterForPickProfiles(array $pickProfiles)
    {
        return [
            'property' => 'id',
            'expression' => 'IN',
            'value' => array_map(function (PickProfile $pickProfile) {
                return $pickProfile->getId();
            }, $pickProfiles),
        ];
    }
}
