<?php

declare(strict_types=1);

namespace KupShop\AdminBundle\Util;

use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\StringUtil;
use KupShop\RewriteBundle\Util\Rewrite;
use Query\Operator;

class MergeProductsToVariationsUtil
{
    private $productUtils;

    private $rewrite;

    public function __construct(ProductUtils $productUtils, ?Rewrite $rewrite)
    {
        $this->productUtils = $productUtils;
        $this->rewrite = $rewrite;
    }

    public function mergeProducts(int $productId, int $labelId, array $products, array $config = []): void
    {
        sqlGetConnection()->transactional(function () use ($productId, $labelId, $products, $config) {
            $descriptions = [];

            $qb = sqlQueryBuilder()
                ->select('p.id, pv.id as id_variation')
                ->from('products', 'p')
                ->leftJoin('p', 'products_variations', 'pv', 'p.id = pv.id_product')
                ->leftJoin('pv', 'products_variations_combination', 'pvc', 'pv.id = pvc.id_variation')
                ->andWhere(Operator::inIntArray(array_keys($products), 'p.id'));

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

            $deleteProducts = [];
            foreach ($qb->execute() as $item) {
                $oldProductId = (int) $item['id'];
                $oldVariationId = $item['id_variation'] ? (int) $item['id_variation'] : null;
                $labelValueId = (int) $products[$oldProductId];

                // potrebuju, aby se to stalo jen 1x - kvuli variantam
                if (!($deleteProducts[$oldProductId] ?? false)) {
                    // zkopirovat od data produktu
                    $this->productUtils->copyProductData($oldProductId, $productId, [
                        'parameters' => $config['merge_parameters'] ?? false,
                        'sections' => true,
                        'products_collections' => true,
                        'products_related' => true,
                        'attachments' => true,
                        'articles' => true,
                        'translations' => false,
                        'sets' => true,
                        'gifts' => true,
                        'links' => true,
                        'charges' => true,
                        'descr_plus' => true,
                    ]);

                    // vytvorit rewrite
                    $this->createProductRewrite($oldProductId, $productId);
                }

                // vytvorime variantu
                $variationId = $this->createProductVariation(
                    $productId,
                    array_replace(
                        $this->getVariationParts($oldVariationId),
                        [
                            $labelId => $this->getLabelValueByValueId($labelValueId),
                        ]
                    )
                );

                if (!$oldVariationId) {
                    // kopirovat produktova data na variantu
                    $this->productUtils->copyProductDataToVariation($oldProductId, $variationId);
                } else {
                    // kopirovat data od varianty k variante
                    $this->productUtils->copyVariationData($oldVariationId, $variationId);

                    // aktualizuju kod variant - musim nejdriv u te stare nasetovat null, aby to nepadlo na duplicite
                    if (findModule(\Modules::PRODUCTS_VARIATIONS, \Modules::SUB_CODE)) {
                        sqlQueryBuilder()
                            ->update('products_variations')
                            ->directValues(['code' => null])
                            ->where(Operator::equals(['id' => $oldVariationId]))
                            ->execute();

                        sqlQueryBuilder()
                            ->update('products_variations')
                            ->directValues(['code' => $item['variationCode'] ?? null])
                            ->where(Operator::equals(['id' => $variationId]))
                            ->execute();
                    }
                }

                // popisky
                if ($config['merge_descriptions'] ?? false) {
                    $description = sqlQueryBuilder()
                        ->select('long_descr')
                        ->from('products')
                        ->where(Operator::equals(['id' => $oldProductId]))
                        ->execute()->fetchColumn();

                    if ($description) {
                        $variationTitle = sqlQueryBuilder()
                            ->select('title')
                            ->from('products_variations')
                            ->where(Operator::equals(['id' => $variationId]))
                            ->execute()->fetchColumn();

                        $descriptions[] = '<p><strong>'.$variationTitle.'</strong></p>'.$description;
                    }

                    $this->productUtils->moveProductBlocksToNewProduct($oldProductId, $productId);
                }

                // pokud nechceme slucovat fotky, tak je smazeme jeste pred tim, nez aktualizujeme IDcka relaci
                if (!($config['merge_photos'] ?? false) && ($oldProductId != $productId)) {
                    sqlQueryBuilder()
                        ->delete('photos_products_relation')
                        ->where(Operator::equals(['id_product' => $oldProductId]))
                        ->execute();
                }

                // updatujeme IDcka stareho produktu na nove ID produktu a varianty - tim se prenesou napr. fotky, ceniky
                $this->updateProductRelationsToVariation($oldProductId, $oldVariationId, $productId, $variationId);

                $deleteProducts[$oldProductId] = $oldProductId;
            }

            // smazeme produkt, ktery jsme sloucili
            sqlQueryBuilder()
                ->delete('products')
                ->where(Operator::inIntArray($deleteProducts, 'id'))
                ->andWhere('id != :id_product')
                ->setParameter('id_product', $productId)
                ->execute();

            // aktualizujeme popisek
            if (!empty($descriptions)) {
                sqlQueryBuilder()
                    ->update('products')
                    ->set('long_descr', 'CONCAT(long_descr, :description)')
                    ->setParameter('description', implode('', $descriptions))
                    ->where(Operator::equals(['id' => $productId]))
                    ->execute();
            }

            // prepocitame skladovost
            \Variations::recalcInStore();

            // aktualizovat cenu produktu podle variant
            $this->updateProductPriceByVariations($productId);
        });
    }

    private function createProductVariation(int $productId, array $parts): int
    {
        foreach ($parts as $labelId => $_) {
            // ujistit se, ze ma produkt prirazenou jmenovku
            sqlQuery('INSERT IGNORE INTO products_variations_choices_categorization (id_product, id_label) VALUES (:id_product, :id_label);',
                [
                    'id_product' => $productId,
                    'id_label' => $labelId,
                ]
            );
        }

        return (int) \Variations::createProductVariation($productId, $parts);
    }

    private function createProductRewrite(int $oldProductId, int $newProductId): void
    {
        $oldProductTitle = sqlQueryBuilder()
            ->select('title')
            ->from('products')
            ->where(Operator::equals(['id' => $oldProductId]))
            ->execute()->fetchColumn();

        if ($this->rewrite) {
            $this->rewrite->addRewrite(
                path('products', ['id' => $oldProductId, 'name' => StringUtil::slugify($oldProductTitle)]),
                Rewrite::TYPE_PRODUCT,
                $newProductId
            );
        }
    }

    private function updateProductPriceByVariations(int $productId): void
    {
        sqlQuery('UPDATE products p
                    JOIN products_variations pv on p.id = pv.id_product
                    SET p.price = (
                            SELECT COALESCE(MIN(IF(pv2.price=0, null, pv2.price)), p.price)
                            FROM products_variations pv2
                            WHERE pv2.id_product = p.id
                        )
                    WHERE p.id = :id;', ['id' => $productId]
        );
    }

    private function updateProductRelationsToVariation(int $oldProductId, ?int $oldVariationId, int $newProductId, int $newVariationId): void
    {
        $this->productUtils->updateTablesWithCallback(function ($table, $productIdField = 'id_product', $variationIdField = 'id_variation') use ($oldProductId, $oldVariationId, $newProductId, $newVariationId) {
            sqlQueryBuilder()
                ->update($table)
                ->directValues(
                    [
                        $productIdField => $newProductId,
                        $variationIdField => $newVariationId,
                    ]
                )
                ->where(Operator::equalsNullable([$productIdField => $oldProductId, $variationIdField => $oldVariationId]))
                ->execute();
        });
    }

    private function getVariationParts(?int $variationId): array
    {
        if (!$variationId) {
            return [];
        }

        return Mapping::mapKeys(sqlQueryBuilder()
            ->select('id_label, id_value')
            ->from('products_variations_combination')
            ->where(Operator::equals(['id_variation' => $variationId]))
            ->execute()->fetchAll(),
            function ($k, $v) {
                return [(int) $v['id_label'], $this->getLabelValueByValueId((int) $v['id_value'])];
            });
    }

    private function getLabelValueByValueId(int $valueId): string
    {
        $result = sqlQueryBuilder()
            ->select('value')
            ->from('products_variations_choices_values')
            ->where(Operator::equals(['id' => $valueId]))
            ->execute()->fetchColumn();

        if (!$result) {
            throw new \RuntimeException('Label value not found!');
        }

        return $result;
    }
}
