<?php

namespace KupShop\OrderingBundle\Util\Order;

use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\OrderingBundle\Event\OrderItemEvent;
use KupShop\ReturnsBundle\Entity\ReturnEntity;
use KupShop\ReturnsBundle\Util\ReturnsUtil;
use Query\Operator;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\Attribute\Required;

class ReplacementUtil
{
    use \DatabaseCommunication;
    use OrderItemEditTrait;

    private $contextManager;

    /** @var OrderUtil */
    protected $orderUtil;

    /**
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;

    /**
     * @var ReturnsUtil
     */
    protected $returnsUtil;

    public function __construct(ContextManager $contextManager)
    {
        $this->contextManager = $contextManager;
    }

    /**
     * @required
     */
    public function setOrderUtil(OrderUtil $orderUtil)
    {
        $this->orderUtil = $orderUtil;
    }

    public function createReturnOrder($oldOrderID, array $items, $products = [], ?ReturnEntity $return = null): \Order
    {
        $order = $this->createOrder($oldOrderID, $return);

        if (findModule(\Modules::STORES) && $return) {
            $order->setData('id_store', $return->getCustomData()['id_store'] ?? 1);
        }

        if ($return && $return->getId()) {
            $order->setData('id_return', $return->getId());
        }

        $this->createReturnOrderItems($order, $items, $products, $return);

        $this->orderChangeStatus($oldOrderID, $order);

        return $order;
    }

    public function createReturnOrderItems(\Order $order, array $items, array $products, ?ReturnEntity $returnEntity = null): \Order
    {
        sqlGetConnection()->transactional(function () use ($order, $items, $products, $returnEntity) {
            $this->contextManager->activateOrder($order, function () use ($order, $items, $products, $returnEntity) {
                $callback = function (\Product $product, $pieces) use ($returnEntity) {
                    if (findModule(\Modules::RETURNS) && $returnEntity) {
                        $this->returnsUtil->logHistory($returnEntity->getId(), "Produkt {$product->title} - naskladněno {$pieces} Ks.");
                    }
                };

                foreach ($items as $key => $item) {
                    if ($key > 0 && $item['pieces'] > 0) {
                        $this->returnItem($order, $item, $callback);
                    } elseif ($key < 0) {
                        $this->editItem($order->id, $item);
                    }
                }

                foreach ($products as $key => $product) {
                    if ($key < 0 && !empty($item['pieces'])) {
                        $this->returnItem($order, $product, $callback);
                    }
                }

                $order->recalculate();
            });
        });

        return $order;
    }

    public function createOrder($orderID, ?ReturnEntity $return = null)
    {
        $order = $this->orderUtil->copyOrder($orderID, ['status_payed', 'status_dispatch', 'status_storno', 'source']);

        $flags = findModule('replacement', 'flags');
        if ($flags) {
            sqlQuery('UPDATE orders SET flags=ADD_TO_SET(:flags, flags) WHERE id=:id', ['flags' => $flags, 'id' => $order->id]);
        }

        if ($return && $idReplacementDeliveryType = $return->getIdReplacementDeliveryType()) {
            sqlQueryBuilder()
                ->update('orders')
                ->directValues(['id_delivery' => $idReplacementDeliveryType, 'delivery_type' => \DeliveryType::get($idReplacementDeliveryType)->name])
                ->where(Operator::equals(['id' => $order->id]))
                ->execute();
        }

        $order->status = -1;
        $this->orderUtil->addFlag($order, 'V');
        $order->source = OrderInfo::ORDER_SOURCE_RETURNS;
        sqlQuery('UPDATE orders SET source=:source WHERE id=:id',
            ['source' => OrderInfo::ORDER_SOURCE_RETURNS, 'id' => $order->id]);

        return $order;
    }

    public function returnItem(\Order $order, $item, ?callable $callback = null)
    {
        $dbcfg = \Settings::getDefault();

        if (!empty($item['id_product'])) {
            try {
                $note = json_decode($item['note'] ?? '', true);
                $returnItemNote = json_decode($item['data'] ?? '', true);
                $note['id_order_item'] = $item['id_item'];

                if (isset($note['item_type'])) {
                    $note['orig_item_type'] = $note['item_type'];
                }

                if (isset($returnItemNote['damageLevel'])) {
                    if ($damageDiscount = ($dbcfg->returns['damageLevels'][$returnItemNote['damageLevel']]['value'] ?? false)) {
                        $item['piece_price'] = toDecimal($item['piece_price'])->addDiscount(toDecimal($damageDiscount))->asFloat();
                        $note['damageLevelValue'] = $damageDiscount;
                    }
                }

                $note['item_type'] = OrderItemInfo::TYPE_RETURN;
                $order_item_id = $order->insertItem($item['id_product'], $item['id_variation'], -$item['pieces'], json_encode($note));

                $product = \Variation::createProductOrVariation($item['id_product'], $item['id_variation']);
                $product->createFromDB($product->id);
                $event = $this->eventDispatcher->dispatch(
                    new OrderItemEvent($product, empty($item['id_variation']) ? null : $item['id_variation'], null, $item['pieces'], ['order_item_id' => $order_item_id, 'order_item_data' => $item['return_data'] ?? null], $order),
                    OrderItemEvent::RETURN_ITEM
                );

                $updateFields = ['total_price' => $item['piece_price'] * -$item['pieces'], 'piece_price' => $item['piece_price'], 'tax' => $item['tax']];

                if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
                    $updateFields['price_buy'] = $item['price_buy'];
                }

                $updatedRows = $this->updateSQL('order_items', $updateFields, ['id' => $order_item_id]);

                if ($callback) {
                    $callback($product, $item['pieces']);
                }
                //                assert($updatedRows == 1, 'Replacement::returnItem - update price does not modified any order_item');
            } catch (\DomainException $e) {
                if (!$item['id_variation']) {
                    $order->insertNonItem($order->id, $item['piece_price'] * -$item['pieces'], $item['piece_price'], -$item['pieces'], $item['tax'], $item['descr'] ?? $item['title'] ?? '', $item['note'] ?? '');
                }
            }
        } else {
            $total_price = toDecimal($item['piece_price'])->mul(toDecimal($item['pieces']))->asFloat();
            $insertedItemID = $order->insertNonItem($order->id, -$total_price, $item['piece_price'], -$item['pieces'], $item['tax'], $item['title'] ?? $item['descr'] ?? '', $item['note'] ?? '');
            if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)
                && isset($item['price_buy'])
            ) {
                sqlQueryBuilder()->update('order_items')->set('price_buy', $item['price_buy'])
                    ->where(Operator::equals(['id' => $insertedItemID]))->execute();
            }
        }
    }

    public function orderChangeStatus($oldOrderID, \Order $order)
    {
        $old_order_obj = new \Order();
        $old_order_obj->createFromDB($oldOrderID);
        $old_order_obj->logHistory(translate('vratka', 'replacement', false, true)." <a href=\"javascript:nw('orders', {$order->id})\">{$order->order_no}</a>");

        $order->setData('replacement', $oldOrderID);
        $order->changeStatus(0, translate('vratkaOrd', 'replacement', false, true)." <a href=\"javascript:nw('orders', {$old_order_obj->id})\">{$old_order_obj->order_no}</a>", false);
    }

    /**
     * @required
     */
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * @required
     */
    public function setReturnsUtil(?ReturnsUtil $returnsUtil = null): void
    {
        $this->returnsUtil = $returnsUtil;
    }
}
