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

use Shopware\Plugins\ViisonShippingCommon\Classes\PluginInfo;

class ViisonShippingCommonDispatchData extends \Enlight_Class
{

    /**
     * @param PluginInfo $pluginInfo
     * @param int $orderId,
     * @param int $languageID The language that should be used for the export document positions, if a translation is available
     * @return array
     */
    public function getDispatchOrderPositions($orderId, PluginInfo $pluginInfo, $languageID = null)
    {
        // Get the weight and quantity of all articles of this order. An item's weight can be null in two cases:
        // * If it's not defined (0.0 as it's a DECIMAL column or NULL)
        // * The actual article was deleted (all details are gone, therefore NULL)
        $dispatchOrderPositions = Shopware()->Db()->fetchAll(
            'SELECT od.id, IF(LENGTH(at.name) > 0, at.name, od.name) AS name, od.quantity AS quantity, od.price, od.articleordernumber, o.currency, IFNULL(ad.weight, 0) as weight
             FROM s_order_details od
             LEFT JOIN s_order o ON od.orderID = o.id
             LEFT JOIN s_articles_details ad ON od.articleordernumber = ad.ordernumber
             LEFT JOIN s_articles_translations at ON at.articleID =  ad.articleID AND at.languageID = ?
             WHERE od.orderID = ?
             AND od.quantity > 0
             AND od.esdarticle = 0
             AND od.modus = 0',
            array(
                $languageID,
                $orderId
            )
        );

        if ($pluginInfo->pluginSupportsExportDocuments()) {
            // Determine mapping between custom product order items and their respective options
            $customProductOptionMapping = $this->getCustomProductOptionMapping($orderId);

            foreach ($dispatchOrderPositions as &$item) {
                // Get information about all items of the order
                $exportData = Shopware()->Db()->fetchRow(
                    'SELECT aa.viison_customs_tariff_number as customsTariffNumber, c.countryiso AS countryOfOriginCode
                     FROM s_order_details od
                     LEFT JOIN s_articles_details ad on ad.ordernumber = od.articleordernumber
                     LEFT JOIN s_articles_attributes aa ON aa.articledetailsID = ad.id
                     LEFT JOIN s_core_countries c ON aa.viison_country_of_origin = c.id
                     WHERE od.id = ?',
                    array(
                        $item['id']
                    )
                );

                // Add the export related attributes to the dispatch order position data
                $item = array_merge($item, $exportData);

                // Add custom product surcharges
                if (array_key_exists($item['id'], $customProductOptionMapping)) {
                    $optionDetailIds = $customProductOptionMapping[$item['id']];
                    if (!empty($optionDetailIds)) {
                        $optionSurcharges = Shopware()->Db()->fetchOne('SELECT SUM(od.price * od.quantity) FROM s_order_details od WHERE od.id IN (' . join(',', $optionDetailIds) . ')');
                        $item['price'] += $optionSurcharges;
                    }
                }
            }
        }

        // Subtract percental vouchers from customs value (we assume that absolute vouchers are getting sold and are not allowed to be subtracted)
        $percentalVoucherAmount = Shopware()->Db()->fetchOne(
            'SELECT SUM(od.price) FROM s_order_details od
             LEFT JOIN s_emarketing_vouchers v ON v.ordercode = od.articleordernumber
             WHERE od.orderId = ?
             AND od.modus = 2
             AND v.percental = true',
            array(
                $orderId
            )
        );

        $remainingVoucherAmount = $percentalVoucherAmount;
        $remainingOrderPositions = $dispatchOrderPositions;

        if ($percentalVoucherAmount != 0) {
            foreach ($dispatchOrderPositions as &$orderPosition) {
                $remainingTotalPrice = array_reduce($remainingOrderPositions, function ($carry, $item) {
                    return $carry + $item['price'] * $item['quantity'];
                }, 0);

                if (round($remainingTotalPrice, 2) !== 0.0) {
                    $percentage = $orderPosition['price'] / $remainingTotalPrice;
                    $discount = round($percentage * $remainingVoucherAmount, 2);
                    $remainingVoucherAmount -= $discount;
                    $orderPosition['price'] += $discount; // Discount is negative
                    array_shift($remainingOrderPositions);
                }
            }
        }

        return $dispatchOrderPositions;
    }

    /**
     * @param array $exportDocumentItems
     * @param int $maxExportDocumentItems
     * @param string $groupedItemNameTemplate
     * @return array
     */
    public function condenseExportDocumentItems(
        array $exportDocumentItems,
        $maxExportDocumentItems,
        $groupedItemNameTemplate = '%d more positions, please see invoice'
    ) {
        if (count($exportDocumentItems) <= $maxExportDocumentItems) {
            return $exportDocumentItems;
        }

        // Sort the items by total price in descending order
        usort($exportDocumentItems, function ($a, $b) {
            return $b['price'] * $b['quantity'] - $a['price'] * $b['quantity'];
        });

        // Save all except the most expensive ones
        $restOfPositions = array_slice($exportDocumentItems, $maxExportDocumentItems - 1);

        // Show only the most expensive items on the document
        $exportDocumentItems = array_slice($exportDocumentItems, 0, $maxExportDocumentItems - 1);

        $totalWeight = array_reduce($restOfPositions, function ($totalWeight, $item) {
            return $totalWeight + $item['quantity'] * $item['weight'];
        }, 0);
        $totalPrice = array_reduce($restOfPositions, function ($totalPrice, $item) {
            return $totalPrice + $item['quantity'] * $item['price'];
        }, 0);

        $exportDocumentItems[] = [
            'name' => sprintf($groupedItemNameTemplate, count($restOfPositions)),
            'countryOfOriginCode' => $restOfPositions[0]['countryOfOriginCode'],
            'currency' => $restOfPositions[0]['currency'],
            'customsTariffNumber' => $restOfPositions[0]['customsTariffNumber'],
            'quantity' => 1,
            'price' => $totalPrice,
            'weight' => $totalWeight,
            'articleordernumber' => 'Remaining positions',
        ];

        return $exportDocumentItems;
    }

    /**
     * Returns an array that assigns custom product order details their respective custom product option order detail ids.
     * E.g. if order detail id 1 has an option with order detail id 2, the function will return array(1 => array(2))
     *
     * This method relies on the order of the ids of the order detail entities, e.g. the following will be interpreted as
     * the options 2 and 3 belonging to product 1 and option 5 belonging to product 4:
     *
     * 1: product
     * 2: option
     * 3: option
     * 4: product
     * 5: option
     *
     * We need to use this because the custom products plugin fails to make the relationship between
     * the options and the custom product order items that they belong to transparent.
     *
     * @param int $orderId
     * @return array
     */
    private function getCustomProductOptionMapping($orderId)
    {
        // Check if the SwagCustomizing plugin is installed
        if (!property_exists('Shopware\Models\Attribute\OrderDetail', 'swagCustomizing')) {
            return array();
        }

        $orderDetails = Shopware()->Container()->get('models')->createQueryBuilder()
            ->select(array('detail', 'attribute'))
            ->from('Shopware\Models\Order\Detail', 'detail')
            ->innerJoin('detail.attribute', 'attribute')
            ->where('detail.orderId = :orderId')
            ->orderBy('detail.id')
            ->setParameter('orderId', $orderId)
            ->getQuery()->getArrayResult();

        $mapping = array();

        $currentProductOrderDetailId = null;

        // Iterate Order\Detail result and inspect custom product attribute data
        foreach ($orderDetails as $orderDetail) {
            // Unserialize the swagCustomizing property
            $unserializedAttributes = unserialize($orderDetail['attribute']['swagCustomizing']);

            if (!is_array($unserializedAttributes)) {
                continue;
            }

            if (array_key_exists('groupId', $unserializedAttributes)) {
                $currentProductOrderDetailId = $orderDetail['id'];
                $mapping[$currentProductOrderDetailId] = array();
            }
            if (array_key_exists('optionId', $unserializedAttributes)) {
                if (is_null($currentProductOrderDetailId)) {
                    throw new \Exception(sprintf('Encountered customs product option detail id %d without an associated product order item', $orderDetail['id']));
                }

                $mapping[$currentProductOrderDetailId][] = $orderDetail['id'];
            }
        }

        return $mapping;
    }
}
