<?php

declare(strict_types=1);

namespace External\HannahBundle\SAP\Synchronizer;

use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use External\HannahBundle\Enum\ProductAvailability;
use External\HannahBundle\SAP\Exception\SAPException;
use External\HannahBundle\SAP\MappingType;
use External\HannahBundle\Util\Product\ProductAvailabilityUpdater;
use KupShop\StoresBundle\Utils\StoresInStore;
use Query\Operator;
use Symfony\Contracts\Service\Attribute\Required;

class StockSynchronizer extends BaseSynchronizer
{
    protected static $type = 'disponibility';
    protected $logging = false;

    #[Required]
    public StoresInStore $storesInStore;

    #[Required]
    public ProductAvailabilityUpdater $productAvailabilityUpdater;

    protected function processItem(array $item): void
    {
        if (empty($item['Matnr'])) {
            return;
        }

        if (!($mapping = $this->sapUtil->getItemMapping($item['Matnr']))) {
            // nemam produkt
            return;
        }

        [$productId, $variationId] = $mapping;

        $storeId = $this->getStoreId($item);

        if (!($sapDateUpdated = \DateTime::createFromFormat('YmdHis', (string) $item['Lastupd']))) {
            $sapDateUpdated = null;
        }

        try {
            $this->updateStoreItem(
                $storeId,
                $productId,
                $variationId,
                (float) $item['Dispo'],
                $sapDateUpdated
            );
        } catch (ForeignKeyConstraintViolationException $e) {
        }
    }

    protected function getHandledFields(): array
    {
        return [
            'Items' => 'item',
        ];
    }

    private function getStoreId(array $item): int
    {
        static $storeIdCache = [];

        $shippingPoint = $item['ShippingPoint'] ?? null;
        if (!$shippingPoint) {
            throw new SAPException('Shipping point is empty');
        }

        if ($storeIdCache[$shippingPoint] ?? false) {
            return $storeIdCache[$shippingPoint];
        }

        $storeId = sqlGetConnection()->transactional(function () use ($shippingPoint) {
            return $this->sapUtil->getMapping(MappingType::STORES, $shippingPoint);
        });

        if (!$storeId) {
            $storeId = sqlGetConnection()->transactional(function () use ($shippingPoint) {
                sqlQueryBuilder()
                    ->insert('stores')
                    ->directValues(
                        [
                            'name' => 'Shipping point '.$shippingPoint,
                            'data' => json_encode(['shippingPoint' => $shippingPoint]),
                        ]
                    )->execute();

                return (int) sqlInsertId();
            });

            $this->sapUtil->createMapping(MappingType::STORES, $shippingPoint, $storeId);
        }

        return $storeIdCache[$shippingPoint] = $storeId;
    }

    public function updateStoreItem(int $storeId, int $productId, ?int $variationId, float $quantity, ?\DateTime $sapDateUpdated = null): void
    {
        if (empty($productId)) {
            return;
        }

        $isRestocked = false;

        if ($storeItem = sqlQueryBuilder()
            ->select('id, quantity, sap_date_updated')
            ->from('stores_items')
            ->andWhere(Operator::equalsNullable(
                [
                    'id_product' => $productId,
                    'id_variation' => $variationId,
                    'id_store' => $storeId,
                ]))
            ->sendToMaster()
            ->execute()
            ->fetchAssociative()) {
            if ($sapDateUpdated && ($storeItemSapDateUpdated = \DateTime::createFromFormat('Y-m-d H:i:s', $storeItem['sap_date_updated']))) {
                // pokud mam ulozeny spravny casovy razitko ze sapu, ale nesedi mi sklad, tak je invalidStore a budu muset udelat update
                $isInvalidStore = $sapDateUpdated == $storeItemSapDateUpdated && $quantity != $storeItem['quantity'];
                // pokud mam v databazi ($storeItemSapDateUpdated) ulozenej novejsi zaznam, nez mi aktualne prisel, tak nebudu provat update, protoze
                // je to neaktualni skladovost, ktera me nezajima
                // zaroven nesmi byt invalidStore, protoze pokud je, tak ten update proste provedu, at se to fixne
                if ($sapDateUpdated <= $storeItemSapDateUpdated && !$isInvalidStore) {
                    // date_updated aktualizuju jen pri plne synchronizaci, protoze jen tehdy me zajima. Jinym zpusobem ho nevyuzivam
                    if ($this->isFullFileImport()) {
                        // date_updated budu aktualizovat jen pokud se jedna o full update
                        sqlQueryBuilder()
                            ->update('stores_items')
                            ->set('date_updated', 'NOW()')
                            ->where(Operator::equals(['id' => $storeItem['id']]))
                            ->execute();
                    }

                    return;
                }
            }

            if ($quantity <= 0) {
                sqlQueryBuilder()
                    ->delete('stores_items')
                    ->where(Operator::equals(['id' => $storeItem['id']]))
                    ->execute();

                $this->sapUtil->recalculateStores([$productId]);

                // produkt se vyprodal, takze zmenime jeho dostupnost
                $this->productAvailabilityUpdater->updateProductAvailability(
                    ProductAvailability::UNAVAILABLE,
                    $productId,
                    $variationId
                );

                return;
            }

            // pokud doslo k znovu naskladneni, tak si to oznacim
            if ($storeItem['quantity'] <= 0) {
                $isRestocked = true;
            }

            $updateQb = sqlQueryBuilder()
                ->update('stores_items')
                ->where(Operator::equals(['id' => $storeItem['id']]));

            $updateData = [
                'quantity' => $quantity,
                'sap_date_updated' => $sapDateUpdated ? $sapDateUpdated->format('Y-m-d H:i:s') : (new \DateTime())->format('Y-m-d H:i:s'),
            ];

            // pokud se opravdu zmenila quantity, tak nastavim sap_updated na 1
            if ($quantity != $storeItem['quantity']) {
                $updateData['sap_updated'] = 1;
            }

            // date_updated aktualizuju jen pri plne synchronizaci, protoze jen tehdy me zajima. Jinym zpusobem ho nevyuzivam
            // nebo se opravdu zmenila skladovost
            if ($this->isFullFileImport() || $quantity != $storeItem['quantity']) {
                $updateQb->set('date_updated', 'NOW()');
            }

            // nasetuju data a executnu update
            $updateQb
                ->directValues($updateData)
                ->execute();
        } elseif ($quantity > 0) {
            sqlQueryBuilder()
                ->insert('stores_items')
                ->setValue('date_updated', 'NOW()')
                ->directValues(
                    [
                        'id_store' => $storeId,
                        'id_product' => $productId,
                        'id_variation' => $variationId,
                        'quantity' => $quantity,
                        'sap_date_updated' => $sapDateUpdated ? $sapDateUpdated->format('Y-m-d H:i:s') : (new \DateTime())->format('Y-m-d H:i:s'),
                        'sap_updated' => 1,
                    ]
                )
                ->execute();

            $isRestocked = true;
        }

        if ($isRestocked) {
            // produkt byl naskladnen, takze provedu update dostupnosti
            $this->productAvailabilityUpdater->updateProductAvailability(
                ProductAvailability::IN_STORE,
                $productId,
                $variationId
            );
        }
    }

    protected function postprocess(): void
    {
        // pokud je to full import a je to file import (import z JSON souboru), tak na konci importu spustim vynulovani
        // zaznamu, ktere nebyly aktualizovany
        if ($this->isFullFileImport()) {
            // nastavim 0 vsem polozkam, ktere nebyly aktualizovant vice jak 12 hodin
            sqlQueryBuilder()
                ->update('stores_items')
                ->directValues(['quantity' => 0])
                ->where('TIMESTAMPDIFF(HOUR, date_updated, NOW()) > 12')
                ->execute();
        }
    }

    private function isFullFileImport(): bool
    {
        return $this->context['importType'] === 'FULL' && ($this->context['isFileImport'] ?? false);
    }
}
