<?php

declare(strict_types=1);

namespace KupShop\OrderingBundle\OrderList;

use KupShop\CatalogBundle\ProductList\ProductList;
use KupShop\DropshipBundle\Util\DropshipmentUtil;
use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\OrderingBundle\Entity\Order\OrderItem;
use KupShop\OrderingBundle\Util\Order\OrderInfo;
use KupShop\UnitsBundle\Utils\PiecesRounder;
use Query\Operator;
use Query\Product;

class MultiFetch
{
    public function __construct(
        protected readonly ContextManager $contextManager,
        protected readonly ProductList $productList,
        protected readonly OrderInfo $orderInfo,
        protected readonly ?PiecesRounder $piecesRounder,
        protected readonly ?DropshipmentUtil $dropshipmentUtil,
    ) {
    }

    public function fetchDropshipmentInfo(OrderCollection $orders): void
    {
        if (!findModule(\Modules::DROPSHIP)) {
            return;
        }

        $qb = sqlQueryBuilder()
            ->select('id_order, id_dropshipment, id_external, data')
            ->from('order_dropshipment')
            ->where(Operator::inIntArray($orders->getOrderIds(), 'id_order'));

        foreach ($qb->execute() as $item) {
            if (!isset($orders[$item['id_order']])) {
                continue;
            }

            $orders[$item['id_order']]->dropshipmentInfo = [
                'dropshipment' => $this->dropshipmentUtil->getDropshipment($item['id_dropshipment']),
                'external' => [
                    'id' => $item['id_external'],
                    'data' => json_decode($item['data'] ?: '', true) ?: [],
                ],
            ];
        }
    }

    public function fetchProductList(OrderCollection $orders): void
    {
        if ($orders->_productListFetched ?? false) {
            return;
        }

        $orders->_productListFetched = true;

        $productList = $this->getProductList();

        $productList->setVariationsAsResult(true);

        $qb = sqlQueryBuilder()
            ->select('id_order, id_product, id_variation')
            ->from('order_items')
            ->where(Operator::inIntArray($orders->getOrderIds(), 'id_order'))
            ->andWhere('id_product IS NOT NULL');

        $products = [];
        $ordersProducts = [];
        foreach ($qb->execute() as $item) {
            if (empty($item['id_product'])) {
                continue;
            }

            $products[$item['id_product']] = $products[$item['id_product']] ?? null;
            if ($item['id_variation']) {
                $products[$item['id_product']][] = $item['id_variation'];
            }

            $productListKey = $item['id_product'];
            if ($item['id_variation']) {
                $productListKey .= '/'.$item['id_variation'];
            }

            $ordersProducts[$item['id_order']][] = $productListKey;
        }

        // nemam co fetchovat
        if (empty($products)) {
            return;
        }

        $productList->andSpec(function () use ($products) {
            return Product::productsAndVariationsIds($products);
        });

        $products = $productList->getProducts();
        /** @var \Order $order */
        foreach ($orders as $order) {
            foreach ($ordersProducts[$order->id] ?? [] as $key) {
                if ($products[$key] ?? false) {
                    $order->productList->set($key, $products[$key]);
                }
            }
        }
    }

    public function fetchItems(OrderCollection $orders): void
    {
        $qb = sqlQueryBuilder()
            ->select('oi.id, oi.id_order, oi.id_product, oi.id_variation, oi.pieces, oi.pieces_reserved, oi.piece_price, 
                    oi.total_price, oi.descr, oi.tax as vat, COALESCE(pv.ean, p.ean) ean, pv.title variation_title, oi.note, p.guarantee, 
                    COALESCE(pv.in_store, p.in_store) in_store, p.campaign, pr.name as producer, pr.id as id_producer, oi.date, oi.discount, s.name as section_name, s.id as section_id')
            ->from('order_items', 'oi')
            ->leftJoin('oi', 'products', 'p', 'p.id=oi.id_product')
            ->leftJoin('p', 'producers', 'pr', 'p.producer=pr.id')
            ->leftJoin('oi', 'products_variations', 'pv', 'pv.id=oi.id_variation')
            ->leftJoin('oi', 'sections', 's', 's.id = (
                           SELECT sec.id
                           FROM sections sec
                           LEFT JOIN products_in_sections ps ON sec.id = ps.id_section
                           WHERE ps.id_product = p.id
                           AND sec.priority = (SELECT MAX(s2.priority)
                                FROM sections s2
                                LEFT JOIN products_in_sections ps2 ON s2.id = ps2.id_section
                                WHERE ps2.id_product = p.id
                                )
                           LIMIT 1
                        )')
            ->where(Operator::inIntArray($orders->getOrderIds(), 'oi.id_order'))
            ->groupBy('oi.id')
            ->orderBySql((new \Order())->getItemsOrdering());

        if (findModule(\Modules::PRODUCTS_VARIATIONS, \Modules::SUB_CODE)) {
            $qb->addSelect('COALESCE(pv.code, p.code) code');
        } else {
            $qb->addSelect('p.code code');
        }

        if (findModule(\Modules::STOCK_IN)) {
            $qb->addSelect('(SELECT group_concat(code SEPARATOR " ") FROM '.getTableName('products_of_suppliers').' pos WHERE pos.id_product = oi.id_product AND (pos.id_variation = oi.id_variation OR (pos.id_variation IS NULL AND oi.id_variation IS NULL))) as suppliers_codes');
        }

        if (findModule(\Modules::PRODUCTS, \Modules::SUB_NOTE)) {
            $qb->addSelect('COALESCE(pv.note, p.note) note_');
        }

        if (findModule(\Modules::PRODUCTS, \Modules::SUB_WEIGHT)) {
            $qb->addSelect('COALESCE(pv.weight, p.weight) weight');
        }

        if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
            $qb->addSelect('oi.price_buy');
        }

        $dataItems = [];
        $dataTotalsWithoutVat = [];
        $dataTotalsWithoutVatNoRounding = [];
        $dataVats = [];

        // prepare data
        foreach ($qb->execute() as $item) {
            /** @var \Order $order */
            if (!($order = $orders[$item['id_order']] ?? false)) {
                throw new \RuntimeException(
                    sprintf('Order %s was not found. It looks like corrupted order item', $item['id_order'])
                );
            }

            $this->contextManager->activateContexts([CurrencyContext::class => $order->getCurrency()], function () use ($order, $item, &$dataItems, &$dataTotalsWithoutVat, &$dataTotalsWithoutVatNoRounding, &$dataVats) {
                $item['ean'] = formatEAN($item['ean']);
                $item['piece_price'] = formatPrice($item['piece_price'], $item['vat']);
                $item['total_price'] = formatPrice($item['total_price'], $item['vat']);
                $item = array_merge($item, $item['piece_price']);

                $item['discount'] = toDecimal($item['discount']);

                if (!isset($vats[$item['vat']]['total_price_without_vat'])) {
                    $vats[$item['vat']]['total_price_without_vat'] = toDecimal(0);
                    $vats[$item['vat']]['total_price_with_vat'] = toDecimal(0);
                }

                $vats[$item['vat']]['total_price_without_vat'] = $vats[$item['vat']]['total_price_without_vat']->add(
                    $item['total_price']['value_without_vat']->value(2)
                );
                $vats[$item['vat']]['total_price_with_vat'] = $vats[$item['vat']]['total_price_with_vat']->add(
                    $item['total_price']['value_with_vat']->value(2)
                );

                // vats
                if (!isset($dataVats[$item['id_order']][$item['vat']]['total_price_without_vat'])) {
                    $dataVats[$item['id_order']][$item['vat']]['total_price_without_vat'] = toDecimal(0);
                    $dataVats[$item['id_order']][$item['vat']]['total_price_with_vat'] = toDecimal(0);
                }

                $dataVats[$item['id_order']][$item['vat']]['total_price_without_vat'] = $dataVats[$item['id_order']][$item['vat']]['total_price_without_vat']->add($item['total_price']['value_without_vat']->value(2));
                $dataVats[$item['id_order']][$item['vat']]['total_price_with_vat'] = $dataVats[$item['id_order']][$item['vat']]['total_price_with_vat']->add($item['total_price']['value_with_vat']->value(2));

                // totals without vat
                if (!isset($dataTotalsWithoutVat[$item['id_order']])) {
                    $dataTotalsWithoutVat[$item['id_order']] = toDecimal(0);
                    $dataTotalsWithoutVatNoRounding[$item['id_order']] = toDecimal(0);
                }

                $dataTotalsWithoutVatNoRounding[$item['id_order']] = $dataTotalsWithoutVatNoRounding[$item['id_order']]->add($item['total_price']['value_without_vat']);
                $dataTotalsWithoutVat[$item['id_order']] = $dataTotalsWithoutVat[$item['id_order']]->add($item['total_price']['value_without_vat']->value(2));

                if ($item['id_product']) {
                    $productListKey = $item['id_product'];
                    if ($item['id_variation']) {
                        $productListKey .= '/'.$item['id_variation'];
                    }

                    $item['product'] = $order->productList[$productListKey] ?? null;
                }

                if (findModule(\Modules::PRODUCTS, \Modules::SUB_UNITS_FLOAT)) {
                    if ($item['id_product']) {
                        $item['pieces'] = $this->piecesRounder->roundPieces($item['product'], $item['pieces']);
                    } else {
                        $item['pieces'] = floatval($item['pieces']);
                    }
                }

                if (empty($item['note'])) {
                    $item['note'] = [];
                } else {
                    $item['note'] = json_decode($item['note'], true);
                }

                $tmpItem = $item;

                unset($item['id_order']);

                $dataItems[$tmpItem['id_order']][$tmpItem['id']] = new OrderItem($item);
            });
        }

        // prepare vats
        $dataVats = $this->prepareOrdersVats($dataVats);

        // set data to orders
        /** @var \Order $order */
        foreach ($orders as $order) {
            $this->contextManager->activateContexts([CurrencyContext::class => $order->getCurrency()], function () use ($order, $dataItems, $dataTotalsWithoutVat, $dataTotalsWithoutVatNoRounding, $dataVats) {
                // add items
                if ($dataItems[$order->id] ?? false) {
                    $order->items = $dataItems[$order->id];
                }

                // vats
                if ($dataVats[$order->id] ?? false) {
                    $order->vats = $dataVats[$order->id];
                }

                // create total_price_array
                if ($dataTotalsWithoutVat[$order->id] ?? false) {
                    $order->total_price_array = formatPrice($order->total_price);
                    $order->total_price_array['value_without_vat_no_rounding'] = $dataTotalsWithoutVatNoRounding[$order->id];
                    $order->total_price_array['value_without_vat'] = $dataTotalsWithoutVat[$order->id];
                    $order->total_price_array['value_vat'] = $order->total_price_array['value_with_vat']->sub(
                        $order->total_price_array['value_without_vat']
                    );
                }
            });
        }
    }

    public function fetchHistory(OrderCollection $orders): void
    {
        $qb = sqlQueryBuilder()->select('*')
            ->from('orders_history')
            ->where(Operator::inIntArray($orders->getOrderIds(), 'id_order'))
            ->orderBy('date', 'ASC');

        $ordersHistory = $qb->execute()->fetchAllAssociative();
        $ordersHistory = array_map(function ($v) {
            return $v + ['status_text' => $this->orderInfo->getOrderStatus($v['id_status'])];
        }, $ordersHistory);

        /** @var \Order $order */
        foreach ($orders as $order) {
            $orderId = $order->id;
            $order->history = array_filter($ordersHistory, fn ($entry) => $entry['id_order'] == $orderId);
        }
    }

    public function fetchPackages(OrderCollection $orders): void
    {
        $balikobotData = [];

        if (findModule(\Modules::BALIKONOS)) {
            $qb = sqlQueryBuilder()
                ->select('id_order, data')
                ->from('balikonos')
                ->where(Operator::inIntArray($orders->getOrderIds(), 'id_order'));

            foreach ($qb->execute() as $item) {
                $data = json_decode($item['data'] ?: '', true) ?: [];
                foreach ($data['response'] ?? [] as $response) {
                    if ($packageId = $response['carrier_id'] ?? null) {
                        $balikobotData[$item['id_order']][$packageId] = [
                            'package_id' => $packageId,
                            'track_url' => $response['track_url'] ?? null,
                        ];
                    }
                }
            }
        }

        foreach ($orders as $order) {
            $order->packages = [];

            $delivery = $order->getDeliveryType()?->getDelivery();

            if (!empty($balikobotData[$order->id])) {
                foreach ($balikobotData[$order->id] as $package) {
                    $order->packages[$package['package_id']] = [
                        'package_id' => $package['package_id'],
                        'track_url' => $package['track_url'] ?: $delivery?->getTrackAndTraceLink($package['package_id'], $order),
                    ];
                }

                continue;
            }

            foreach (explode(',', $order->package_id ?? '') as $packageId) {
                if (empty($packageId)) {
                    continue;
                }

                $order->packages[$packageId] = [
                    'package_id' => $packageId,
                    'track_url' => $order->getDeliveryType()?->getDelivery()->getTrackAndTraceLink($packageId, $order),
                ];
            }
        }
    }

    private function prepareOrdersVats(array $vats)
    {
        $possibleVats = [];
        foreach ($vats as $orderId => $values) {
            $possibleVats = array_merge($possibleVats, array_keys($values));
        }

        $possibleVats = array_unique($possibleVats);

        $vatsQb = sqlQueryBuilder()
                ->select('descr, vat')
                ->from('vats')
                ->where(Operator::inIntArray($possibleVats, 'vat'))
                ->orderBy('vat', 'DESC');

        $shopVats = sqlFetchAll($vatsQb->execute(), 'vat');

        foreach ($vats as $orderId => $values) {
            foreach ($values as $vatRate => $vat) {
                if ($shopVats[$vatRate] ?? false) {
                    $vat['descr'] = $shopVats[$vatRate]['descr'] ?? '';
                    $vat['tax'] = formatPrice($vat['total_price_without_vat'], $shopVats[$vatRate]['vat'] ?? 0);
                }

                if (!isset($vat['tax'])) {
                    $vat['tax'] = formatPrice($vat['total_price_without_vat'], $vatRate);
                }

                $vats[$orderId][$vatRate] = $vat;
            }
        }

        return $vats;
    }

    private function getProductList(): ProductList
    {
        return clone $this->productList;
    }
}
