<?php

declare(strict_types=1);

namespace External\HannahBundle\SAP\Synchronizer;

use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use External\HannahBundle\SAP\MappingType;
use External\HannahBundle\Util\Configuration;
use External\HannahBundle\Util\FTP\SFTPClient;
use KupShop\SellerBundle\Utils\SellerUtil;
use Query\Operator;
use Symfony\Contracts\Service\Attribute\Required;

class POSOrderSynchronizer extends BaseSynchronizer
{
    protected static $type = 'pos_order';

    #[Required]
    public SellerUtil $sellerUtil;

    #[Required]
    public SFTPClient $sftp;

    private ?string $posFilename = null;

    public function setPosFilename(?string $posFilename): void
    {
        $this->posFilename = $posFilename;
    }

    public function process(array $data): void
    {
        foreach ($this->getItems() as $item) {
            $this->processItem($item);
            if ($this->processItemCallback) {
                call_user_func($this->processItemCallback, (array) $item);
            }
        }
    }

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

        $saleId = sqlQueryBuilder()
            ->select('id')
            ->from('sales')
            ->where(Operator::equals(['code' => $item['BillId']]))
            ->sendToMaster()
            ->execute()->fetchOne();

        // prodejka v e-shopu uz existuje
        if ($saleId) {
            $this->withRetryStrategy(function () use ($saleId, $item) {
                sqlGetConnection()->transactional(function () use ($saleId, $item) {
                    sqlQueryBuilder()
                        ->delete('sales_items')
                        ->where(Operator::equals(['id_sale' => $saleId]))
                        ->execute();

                    $this->updateSaleHeader($saleId, $item);
                    $this->updateSaleItems($saleId, $item);

                    return true;
                });
            });

            return;
        }

        $sale = [
            'id_user' => $this->getUserId($item),
            'id_seller' => $this->getSellerId($item),
            'id_delivery_type' => $this->getDeliveryTypeId($item),
            'code' => $item['BillId'],
            'date_created' => $item['Crdate'].' '.$item['Crtime'],
            'data' => json_encode([
                'sapInvoiceNumber' => $item['SapInvoice'] ?? null,
                'sapCustomerId' => $item['CustomerId'] ?? null,
                'sapStoreId' => $item['Store'],
            ]),
        ];

        $this->withRetryStrategy(function () use ($item, $sale) {
            sqlGetConnection()->transactional(function () use ($item, $sale) {
                sqlQueryBuilder()
                    ->insert('sales')
                    ->directValues($sale)
                    ->execute();

                $saleId = (int) sqlInsertId();

                $this->updateSaleItems($saleId, $item);
            });

            return true;
        });
    }

    protected function getSaleHeaderData(array $item): array
    {
        return [
            'id_user' => $this->getUserId($item),
            'id_seller' => $this->getSellerId($item),
            'id_delivery_type' => $this->getDeliveryTypeId($item),
            'code' => $item['BillId'],
            'date_created' => $item['Crdate'].' '.$item['Crtime'],
            'data' => json_encode([
                'sapInvoiceNumber' => $item['SapInvoice'] ?? null,
                'sapCustomerId' => $item['CustomerId'] ?? null,
                'sapStoreId' => $item['Store'],
            ]),
        ];
    }

    protected function updateSaleHeader(int $saleId, array $item): void
    {
        sqlQueryBuilder()
            ->update('sales')
            ->directValues($this->getSaleHeaderData($item))
            ->where(Operator::equals(['id' => $saleId]))
            ->execute();
    }

    protected function updateSaleItems(int $saleId, array $item): void
    {
        $totalWithVat = \DecimalConstants::zero();
        $totalWithoutVat = \DecimalConstants::zero();

        foreach ($item['Items'] ?? [] as $saleItem) {
            $productId = null;
            $variationId = null;
            if ($mapping = $this->sapUtil->getItemMapping($saleItem['Material'])) {
                [$productId, $variationId] = $mapping;
            }

            $totalPriceWithoutVat = toDecimal($saleItem['Netto']);
            $totalPriceWithVat = toDecimal($saleItem['Netto'] + $saleItem['Tax']);
            if (!$totalPriceWithVat->isZero()) {
                $tax = $totalPriceWithVat->div($totalPriceWithoutVat)->sub(\DecimalConstants::one())->mul(\DecimalConstants::hundred())->round(2);
            } else {
                $tax = \DecimalConstants::zero();
            }

            $pieces = toDecimal($saleItem['Amount']);

            $totalWithoutVat = $totalWithoutVat->add($totalPriceWithoutVat);
            $totalWithVat = $totalWithVat->add($totalPriceWithVat);

            sqlQueryBuilder()
                ->insert('sales_items')
                ->directValues(
                    [
                        'id_sale' => $saleId,
                        'id_product' => $productId,
                        'id_variation' => $variationId,
                        'pieces' => $pieces,
                        'piece_price' => $totalPriceWithoutVat->div($pieces),
                        'total_price' => $totalPriceWithoutVat,
                        'tax' => $tax,
                        'name' => $saleItem['Arktx'],
                    ]
                )->execute();
        }

        sqlQueryBuilder()
            ->update('sales')
            ->directValues(['total_price' => $totalWithVat, 'total_price_without_vat' => $totalWithoutVat])
            ->where(Operator::equals(['id' => $saleId]))
            ->execute();
    }

    protected function getDeliveryTypeId(array $item): ?int
    {
        static $deliveryTypeId;

        if ($deliveryTypeId === null) {
            $deliveryId = sqlQueryBuilder()
                ->select('id')
                ->from('delivery_type_delivery')
                ->where(Operator::equals(['class' => 'OdberNaProdejne']))
                ->execute()->fetchOne();

            $paymentId = sqlQueryBuilder()
                ->select('id')
                ->from('delivery_type_payment')
                ->where(Operator::equals(['class' => 'Hotovost']))
                ->execute()->fetchOne();

            if ($deliveryId && $paymentId) {
                $tmpDeliveryId = sqlQueryBuilder()
                    ->select('id')
                    ->from('delivery_type')
                    ->where(Operator::equals(['id_delivery' => $deliveryId, 'id_payment' => $paymentId]))
                    ->execute()->fetchOne();

                $deliveryTypeId = $tmpDeliveryId ?: false;
            }
        }

        return $deliveryTypeId ?: null;
    }

    protected function getUserId(array $item): ?int
    {
        if (empty($item['CustomerId'])) {
            return null;
        }

        if (!($userId = $this->sapUtil->getMapping(MappingType::USERS, $item['CustomerId']))) {
            if ($this->isStoreUser($item)) {
                $userId = $this->createSellerUser($item);
            }
        }

        return $userId;
    }

    protected function getSellerId(array $item): ?int
    {
        if (!empty($item['Store'])) {
            if ($storeId = $this->sapUtil->getMapping(MappingType::STORES, $item['Store'])) {
                $tmpSellerId = sqlQueryBuilder()
                    ->select('id')
                    ->from('sellers')
                    ->where(Operator::equals(['id_store' => $storeId]))
                    ->execute()->fetchOne();

                if ($tmpSellerId) {
                    return $tmpSellerId;
                }
            }
        }

        return null;
    }

    protected function createSellerUser(array $item): ?int
    {
        if ($sellerId = $this->getSellerId($item)) {
            if ($seller = $this->sellerUtil->getSeller($sellerId)) {
                $userEmailExists = sqlQueryBuilder()
                    ->select('id')
                    ->from('users')
                    ->where(Operator::equals(['email' => $seller['email']]))
                    ->execute()->fetchOne();

                if ($userEmailExists) {
                    $seller['email'] = null;
                }

                if (empty($seller['email'])) {
                    $seller['email'] = "prodejna_{$sellerId}@rockpoint.cz";
                }

                return sqlGetConnection()->transactional(function () use ($item, $seller) {
                    sqlQueryBuilder()
                        ->insert('users')
                        ->directValues(
                            [
                                'email' => $seller['email'],
                                'firm' => $seller['title'],
                                'street' => implode(' ', array_filter([$seller['street'], $seller['number']])),
                                'city' => $seller['city'],
                                'zip' => $seller['psc'],
                                'phone' => $seller['phone'],
                                'figure' => 'N',
                            ]
                        )->execute();

                    $userId = (int) sqlInsertId();

                    $this->sapUtil->createMapping(MappingType::USERS, $item['CustomerId'], $userId);

                    return $userId;
                });
            }
        }

        return null;
    }

    protected function isStoreUser(array $item): bool
    {
        $sapStoreId = ltrim($item['CustomerId'], '0');

        return $item['Store'] === $sapStoreId;
    }

    protected function getItems(): iterable
    {
        ini_set('memory_limit', '4096M');

        foreach ($this->getPOSJsonFiles() as $filename) {
            // pokud je nastaveny posFilename, tak chci importovat pouze ten jeden konkretni file
            if ($this->posFilename && $this->posFilename !== $filename) {
                continue;
            }

            $tmpFile = $this->sftp->getRemoteJSONFile($filename);

            $data = json_decode(
                file_get_contents($tmpFile),
                true
            );

            foreach ($data as $item) {
                yield $item;
            }

            if (!isDevelopment()) {
                $this->sftp->renameRemoteFile($filename, $filename.'.DONE');
            }

            unlink($tmpFile);
        }
    }

    protected function getHandledFields(): array
    {
        return [];
    }

    private function withRetryStrategy(callable $fn, int $maxTries = 3): mixed
    {
        $try = 0;

        do {
            $try++;

            try {
                return $fn();
            } catch (DeadlockException|LockWaitTimeoutException) {
                sleep(1);
            }
        } while ($try < $maxTries);

        return false;
    }

    /**
     * Metoda pro získání JSON souborů s datama prodejek.
     */
    public function getPOSJsonFiles(): iterable
    {
        $directory = $this->sftp->getJSONDirectory($this->sftp->isTestEnv());

        $files = $this->sftp->getDirectory($directory);

        $result = [];

        // procházím všechny soubory na FTP
        foreach ($files as $file) {
            // zajimaji me pouze postdata soubory
            if (!preg_match('/^(\S+_)?posdata_(.+).json$/', $file)) {
                continue;
            }

            $parts = explode('_', $file);

            // pokud je to soubor, ktery uz obsahuje prefix, tak ho jen ulozim do pole s vysledkama
            if (in_array($parts[0], $this->getFilePrefixes())) {
                $result[$parts[0]][] = $file;
                continue;
            }

            // pokud je to soubor bez prefixu, tak z neho vytvorim soubor s prefixama, aby to byl soubor per e-shop a byl jsm schopnej to na kazdym shopu snadno zpracovat
            $content = $this->sftp->getSFTP()->get($directory.$file);
            foreach ($this->getFilePrefixes() as $prefix) {
                $this->sftp->getSFTP()->put("{$directory}{$prefix}_{$file}", $content);
                $result[$prefix][] = "{$prefix}_{$file}";
            }

            if (!isDevelopment()) {
                $this->sftp->renameRemoteFile($file, $file.'.DONE');
            }
        }

        return $result[$this->getFilePrefixes()[$this->configuration->getShopId()]] ?? [];
    }

    private function getFilePrefixes(): array
    {
        return [
            Configuration::SHOP_ROCKPOINT => 'RPO',
            Configuration::SHOP_HANNAH => 'HAN',
            Configuration::SHOP_RAFIKI => 'RAF',
            Configuration::SHOP_KEEN => 'KEN',
        ];
    }
}
