<?php

namespace Myvetshop\Module\Clinique\Mapper\TarifSpecifique;

use Myvetshop\Module\Clinique\Adapter\Factory\SpecificPriceFactory;
use Myvetshop\Module\Clinique\Adapter\Repository\GroupReductionRepository;
use Myvetshop\Module\Clinique\Adapter\Repository\SpecificPriceRepository;
use Myvetshop\Module\Clinique\Entity\MyvetshopTarifSpecifique;

class TarifSpecifiqueToSpecificPriceMapper
{
    private \Db $db;

    private GroupReductionRepository $groupReductionRepository;

    private SpecificPriceFactory $specificPriceFactory;

    private SpecificPriceRepository $specificPriceRepository;

    public function __construct(
        \Db $db,
        GroupReductionRepository $groupReductionRepository,
        SpecificPriceFactory $specificPriceFactory,
        SpecificPriceRepository $specificPriceRepository
    ) {
        $this->db = $db;
        $this->groupReductionRepository = $groupReductionRepository;
        $this->specificPriceFactory = $specificPriceFactory;
        $this->specificPriceRepository = $specificPriceRepository;
    }

    /**
     * @param list<\Group> $groups
     *
     * @return array{delete: list<\SpecificPrice>, insert: list<\SpecificPrice>, update: list<\SpecificPrice>}
     */
    public function buildUpdateQueries(MyvetshopTarifSpecifique $tarif, array $groups): array
    {
        $groupReductions = $this->groupReductionRepository->getReductionForGroups($tarif->idProduct, $groups);

        $sortedSpecificPrices = $this->getSortedSpecificPrices($tarif, \array_keys($groupReductions));

        /** @var array{delete: list<\SpecificPrice>, insert: list<\SpecificPrice>, update: list<\SpecificPrice>} $queries */
        $queries = [
            'delete' => [],
            'insert' => [],
            'update' => [],
        ];

        foreach ($groups as $group) {
            $actualSpecificPrices = $sortedSpecificPrices[(int) $group->id] ?? [
                'fardelage' => null,
                'privilege' => null,
            ];
            $neededSpecificPrices = $this->toSpecificPrices(
                $tarif,
                $group,
                    $groupReductions[(int) $group->id] ?? 0
            );

            /** @var array{delete: list<\SpecificPrice>, insert: list<\SpecificPrice>, update: list<\SpecificPrice>} $queries */
            $queries = \array_merge_recursive(
                $queries,
                $this->prepareUpdateQueries($actualSpecificPrices, $neededSpecificPrices),
            );
        }

        return $queries;
    }

    /**
     * @param array{delete: list<\SpecificPrice>, insert: list<\SpecificPrice>, update: list<\SpecificPrice>} $queries
     */
    public function executeUpdateQueries(array $queries): void
    {
        // Delete query
        if (\count($queries['delete'])) {
            $this->db->execute(
                'DELETE FROM `' . _DB_PREFIX_ . 'specific_price` WHERE `id_specific_price` IN ('
                . \implode(
                    ',',
                    \array_map(
                        fn (\SpecificPrice $specificPrice) => (int) $specificPrice->id,
                        $queries['delete'],
                    )
                )
                . ')'
            );
        }

        // Insert query
        if (\count($queries['insert'])) {
            $this->db->execute(
                'INSERT INTO `' . _DB_PREFIX_ . 'specific_price`'
                . ' (`id_specific_price_rule`, `id_cart`, `id_product`, `id_product_attribute`, `id_shop`,'
                . ' `id_shop_group`, `id_currency`, `id_country`, `id_customer`, `id_group`, `from_quantity`,'
                . ' `reduction`, `reduction_tax`, `reduction_type`, `price`, `from`, `to`) VALUES '
                . \implode(
                    ',',
                    \array_map(
                        fn (\SpecificPrice $specificPrice) => '(0, 0, ' . (int) $specificPrice->id_product . ', '
                            . (int) $specificPrice->id_product_attribute . ', 0, 0, 0, 0, 0, '
                            . (int) $specificPrice->id_group . ', ' . (int) $specificPrice->from_quantity
                            . ', 0, 1, "amount", ' . (float) $specificPrice->price
                            . ', "0000-00-00 00:00:00", "0000-00-00 00:00:00")',
                        $queries['insert'],
                    )
                )
            );
        }

        // Update queries
        if (\count($queries['update'])) {
            foreach ($queries['update'] as $specificPrice) {
                $this->db->execute(
                    'UPDATE `' . _DB_PREFIX_ . 'specific_price` SET price = ' . (float) $specificPrice->price
                    . ' WHERE `id_specific_price` = ' . (int) $specificPrice->id);
            }
        }
    }

    /**
     * @param MyvetshopTarifSpecifique $tarif
     * @param list<int> $groupIds
     *
     * @return array<int, array{fardelage: ?\SpecificPrice, privilege: ?\SpecificPrice}> $sortedSpecificPrices
     */
    public function getSortedSpecificPrices(MyvetshopTarifSpecifique $tarif, array $groupIds): array
    {
        $specificPrices = $this->specificPriceRepository->findByTarifSpecifiqueAndGroups(
            $tarif,
            $groupIds
        );

        /** @var array<int, array{fardelage: ?\SpecificPrice, privilege: ?\SpecificPrice}> $sortedSpecificPrices */
        $sortedSpecificPrices = \array_reduce(
            $specificPrices,
            function (array $carry, \SpecificPrice $specificPrice) {
                if (!isset($carry[(int) $specificPrice->id_group])) {
                    $carry[(int) $specificPrice->id_group] = [
                        'fardelage' => null,
                        'privilege' => null,
                    ];
                }

                switch ((int) $specificPrice->from_quantity) {
                    case 1:
                        $key = 'privilege';
                        break;

                    case 2:
                        $key = 'fardelage';
                        break;

                    default:
                        $key = 'unknown';
                }

                $carry[(int) $specificPrice->id_group][$key] = $specificPrice;

                return $carry;
            },
            []
        );

        return $sortedSpecificPrices;
    }

    /**
     * @param array{fardelage: ?\SpecificPrice, privilege: ?\SpecificPrice} $actualSpecificPrices
     * @param array{fardelage: ?\SpecificPrice, privilege: ?\SpecificPrice} $neededSpecificPrices
     *
     * @return array{delete: list<\SpecificPrice>, insert: list<\SpecificPrice>, update: list<\SpecificPrice>}
     */
    public function prepareUpdateQueries(array $actualSpecificPrices, array $neededSpecificPrices): array
    {
        $ret = [
            'delete' => [],
            'insert' => [],
            'update' => [],
        ];

        if ($actualSpecificPrices['privilege'] && !$neededSpecificPrices['privilege']) {
            $ret['delete'][] = $actualSpecificPrices['privilege'];
        } elseif (!$actualSpecificPrices['privilege'] && $neededSpecificPrices['privilege']) {
            $ret['insert'][] = $neededSpecificPrices['privilege'];
        } elseif (
            $actualSpecificPrices['privilege']
            && $neededSpecificPrices['privilege']
            && $actualSpecificPrices['privilege']->price !== $neededSpecificPrices['privilege']->price
        ) {
            // Mise à jour du prix
            $actualSpecificPrices['privilege']->price = $neededSpecificPrices['privilege']->price;
            $ret['update'][] = $actualSpecificPrices['privilege'];
        }

        if ($actualSpecificPrices['fardelage'] && !$neededSpecificPrices['fardelage']) {
            $ret['delete'][] = $actualSpecificPrices['fardelage'];
        } elseif (!$actualSpecificPrices['fardelage'] && $neededSpecificPrices['fardelage']) {
            $ret['insert'][] = $neededSpecificPrices['fardelage'];
        } elseif (
            $actualSpecificPrices['fardelage']
            && $neededSpecificPrices['fardelage']
            && $actualSpecificPrices['fardelage']->price !== $neededSpecificPrices['fardelage']->price
        ) {
            // Mise à jour du prix
            $actualSpecificPrices['fardelage']->price = $neededSpecificPrices['fardelage']->price;
            $ret['update'][] = $actualSpecificPrices['fardelage'];
        }

        return $ret;
    }

    /**
     * @return array{fardelage: ?\SpecificPrice, privilege: ?\SpecificPrice}
     */
    public function toSpecificPrices(MyvetshopTarifSpecifique $tarif, \Group $group, float $groupReduction): array
    {
        $ret = [
            'fardelage' => null,
            'privilege' => null,
        ];

        if ($tarif->tarifPrivilege) {
            $ret['privilege'] = $this->specificPriceFactory->create(
                $tarif->idProduct,
                $tarif->idProductAttribute ?? 0,
                1,
                (float) $tarif->tarifPrivilege,
                (int) $group->id,
                $groupReduction
            );
        }

        if ($tarif->tarifFardelage) {
            $ret['fardelage'] = $this->specificPriceFactory->create(
                $tarif->idProduct,
                $tarif->idProductAttribute ?? 0,
                2,
                (float) $tarif->tarifFardelage,
                (int) $group->id,
                $groupReduction
            );
        }

        return $ret;
    }
}
