<?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\PickingOrderFilter\OrderItemRelevance;

use Enlight_Components_Db_Adapter_Pdo_Mysql;
use Shopware\Plugins\ViisonCommon\Classes\Util\Util as ViisonCommonUtil;
use Shopware\Plugins\ViisonPickwareMobile\Components\PickingOrderFilter\PickingOrderFilterJoinQueryComponentFactory;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\BooleanCompositionQueryComponent;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\PlainSqlQueryComponent;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\QueryBuilder;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\QueryBuilderJoinResolver;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\QueryComponent;
use Shopware\Plugins\ViisonPickwareMobile\Components\QueryBuilder\QueryComponentFactory;

class OrderItemRelevanceService
{
    // Query condition aliases
    const CONDITION_ALIAS_ITEM_HAS_ASSOCIATED_ARTICLE = 'itemHasAssociatedArticle';
    const CONDITION_ALIAS_ITEM_HAS_NO_NEGATIVE_PRICE = 'itemHasNoNegativePrice';
    const CONDITION_ALIAS_ITEM_IS_NO_ESD = 'itemIsNoEsd';
    const CONDITION_ALIAS_ITEM_NOT_MARKED_AS_IRRELEVANT_FOR_PICKING = 'itemIsNotMarkedAsIrrelevantForPicking';
    const CONDITION_ALIAS_ITEM_DROP_SHIPPING_NOT_ENABLED = 'itemDropShippingNotEnabled';

    /**
     * @var Enlight_Components_Db_Adapter_Pdo_Mysql
     */
    protected $database;

    /**
     * @param Enlight_Components_Db_Adapter_Pdo_Mysql $database
     */
    public function __construct($database)
    {
        $this->database = $database;
    }

    /**
     * Computes a result containing the IDs of all order details that are part of any order whose ID is contained in the
     * given $orderIds and fulfills all criteria for order details that are relevant for the Picking app.
     *
     * See {@link self::createPickingAppRelevanceConditions()} for the evaluated criteria.
     *
     * @param array $orderIds
     * @return OrderItemRelevanceResult
     */
    public function computeRelevanceForPickingApp(array $orderIds)
    {
        return $this->computeRelevanceWithConditions(
            array_values($this->createPickingAppRelevanceConditions()),
            $orderIds
        );
    }

    /**
     * Returns an array containing the conditions that determine the relevance of an order item for Picking app:
     *  - itemHasAssociatedArticle
     *  - itemHasNoNegativePrice
     *  - itemIsNoEsd
     *  - itemIsNnotMarkedAsIrrelevantForPicking
     *  - itemDropShippingNotEnabled (if ViisonDropShipping is active)
     *
     * @return array
     */
    public function createPickingAppRelevanceConditions()
    {
        $conditions = [
            self::CONDITION_ALIAS_ITEM_HAS_ASSOCIATED_ARTICLE => $this->createHasAssociatedArticleCondition(),
            self::CONDITION_ALIAS_ITEM_HAS_NO_NEGATIVE_PRICE => $this->createHasNoNegativePriceCondition(),
            self::CONDITION_ALIAS_ITEM_IS_NO_ESD => $this->createIsNoEsdCondition(),
            self::CONDITION_ALIAS_ITEM_NOT_MARKED_AS_IRRELEVANT_FOR_PICKING => $this->createNotMarkedAsIrrelevantForPickingCondition(),
        ];
        $dropShippingNotEnabledCondition = $this->createDropShippingNotEnabledConditionIfAvailable();
        if ($dropShippingNotEnabledCondition) {
            $conditions[self::CONDITION_ALIAS_ITEM_DROP_SHIPPING_NOT_ENABLED] = $dropShippingNotEnabledCondition;
        }

        return $conditions;
    }

    /**
     * Computes a result containing the IDs of all order details that are part of any order whose ID is contained in the
     * given $orderIds and fulfills all criteria for order details that are relevant for the Stocking app.
     *
     * See {@link self::createStockingAppRelevanceConditions()} for the evaluated criteria.
     *
     * @param array $orderIds
     * @return OrderItemRelevanceResult
     */
    public function computeRelevanceForStockingApp(array $orderIds)
    {
        return $this->computeRelevanceWithConditions(
            array_values($this->createStockingAppRelevanceConditions()),
            $orderIds
        );
    }

    /**
     * Returns an array containing the conditions that determine the relevance of an order item for Stocking app:
     *  - itemHasAssociatedArticle
     *  - itemHasNoNegativePrice
     *  - itemIsNoEsd
     *
     * @return array
     */
    public function createStockingAppRelevanceConditions()
    {
        return [
            self::CONDITION_ALIAS_ITEM_HAS_ASSOCIATED_ARTICLE => $this->createHasAssociatedArticleCondition(),
            self::CONDITION_ALIAS_ITEM_HAS_NO_NEGATIVE_PRICE => $this->createHasNoNegativePriceCondition(),
            self::CONDITION_ALIAS_ITEM_IS_NO_ESD => $this->createIsNoEsdCondition(),
        ];
    }

    /**
     * Computes a result containing the IDs of all order details that fulfill the passed $conditionComponents. If an
     * array is passed as the optional second argument, the result is limited to order details that are part of any of
     * the orders whose ID is contained in $orderIds.
     *
     * @param QueryComponent[] $conditionComponents
     * @param int[]|null $orderIds (optional)
     * @return OrderItemRelevanceResult
     */
    public function computeRelevanceWithConditions(array $conditionComponents, array $orderIds = null)
    {
        if (is_array($orderIds)) {
            // Add a condition component that limits the result to the items of the passed order IDs
            $conditionComponents[] = QueryComponentFactory::createTableFieldInValuesComparison('s_order', 'id', $orderIds);
        }

        // Build the relevance query
        $queryBuilder = new QueryBuilder();
        $queryBuilder
            ->select(QueryComponentFactory::createTableFieldSelectWithAlias('s_order_details', 'id', 'orderItemId'))
            ->from('s_order_details')
            ->where(BooleanCompositionQueryComponent::createConjunction(...$conditionComponents))
            ->groupBy(QueryComponentFactory::createGroupByTableField('s_order_details', 'id'));
        QueryBuilderJoinResolver::addMissingJoinsToQueryBuilder(
            $queryBuilder,
            PickingOrderFilterJoinQueryComponentFactory::createSimpleJoinComponents()
        );

        // Execute query in the database
        $result = $this->database->fetchCol($queryBuilder->getSql());

        return new OrderItemRelevanceResult($result);
    }

    /**
     * Returns a component asserting that the order detail is associated with a real, existing article. That is, it
     * filters 'pseudo' items like 'minimum invoice amount surcharge' as well as items whose article number does not
     * match the number of any currently existing article.
     *
     * @return PlainSqlQueryComponent
     */
    public function createHasAssociatedArticleCondition()
    {
        return new PlainSqlQueryComponent('`s_articles_details`.`id` IS NOT NULL', ['s_articles_details']);
    }

    /**
     * Returns a component asserting that the order detail has no negative price, to filter discounts, coupons etc.
     *
     * @return PlainSqlQueryComponent
     */
    public function createHasNoNegativePriceCondition()
    {
        return new PlainSqlQueryComponent('`s_order_details`.`price` >= 0', ['s_order_details']);
    }

    /**
     * Returns a component asserting that the order detail is not an ESD.
     *
     * @return PlainSqlQueryComponent
     */
    public function createIsNoEsdCondition()
    {
        return new PlainSqlQueryComponent('`s_order_details`.`esdarticle` != 1', ['s_order_details']);
    }

    /**
     * Returns a component asserting that the order detail's associated article detail is not manually marked as 'not
     * relevant for picking'.
     *
     * @return PlainSqlQueryComponent
     */
    public function createNotMarkedAsIrrelevantForPickingCondition()
    {
        return new PlainSqlQueryComponent(
            '`s_articles_attributes`.`pickware_not_relevant_for_picking` != 1',
            ['s_articles_attributes']
        );
    }

    /**
     * Returns a component asserting that the order detail's associated article detail is not confgured to be processed
     * via drop shipping using the ViisonDropShipping plugin. Returns null, if the ViisonDropShipping plugin is
     * not active.
     *
     * @return PlainSqlQueryComponent|null
     */
    public function createDropShippingNotEnabledConditionIfAvailable()
    {
        if (!ViisonCommonUtil::isPluginInstalledAndActive(null, 'ViisonDropShipping')) {
            return null;
        }

        return new PlainSqlQueryComponent(
            '(`s_articles_attributes`.`viison_drop_shipper_mail` IS NULL OR TRIM(`s_articles_attributes`.`viison_drop_shipper_mail`) = \'\')',
            ['s_articles_attributes']
        );
    }
}
