<?php

declare(strict_types=1);

namespace External\PompoBundle\Resources\script;

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use External\PompoBundle\DRS\Util\DRSApi;
use KupShop\AdminBundle\Util\Script\Script;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use Query\Operator;

class DRSUpdateUserCardsScript extends Script
{
    protected static $name = '[DRS] Update user cards by DRS';
    protected static $defaultParameters = [
        'dry-run' => true,
    ];

    protected function run(array $arguments)
    {
        $isDryRun = $arguments['dry-run'] ?? true;

        sqlGetConnection()->getConfiguration()->setSQLLogger();

        increaseMemoryLimit(4000);

        $start = time();

        $this->log('Generating cards cache from DRS...');

        $userCards = [];
        foreach ($this->getData() as $index => $item) {
            $userCards[$item['id']][] = $item;
        }

        $this->log('Processing shop update...');
        if ($isDryRun) {
            $this->log('Processing as dry-run');
        }

        $processIndex = 0;
        foreach ($userCards as $id => $cards) {
            $this->log('Index: '.$processIndex++);
            if (!($userId = $this->getUserIdByDRSId((string) $id))) {
                continue;
            }

            $currentCards = $this->getUserCards($userId);
            $cardsToDelete = $currentCards;

            if ($isDryRun) {
                $this->dryUserCardsProcess((string) $id, $userId, $cards, $currentCards);
                continue;
            }

            foreach ($cards as $card) {
                // pokud by nemela cislo karty, tak skipuju
                if (empty($card['number'])) {
                    continue;
                }

                // pokud karta uz existuje, tak nic nedelam a jen ji odeberu z karet ke smazani
                if (($cardId = array_search($card['number'], $currentCards)) !== false) {
                    unset($cardsToDelete[$cardId]);
                    continue;
                }

                if (($card['status'] ?? false) !== '1') {
                    continue;
                }

                try {
                    $this->createUserCard($userId, $card['number']);
                } catch (UniqueConstraintViolationException) {
                }
            }

            if (!empty($cardsToDelete)) {
                sqlQueryBuilder()
                    ->delete('user_cards')
                    ->andWhere(Operator::equals(['id_user' => $userId]))
                    ->andWhere(Operator::inIntArray(array_keys($cardsToDelete), 'id'))
                    ->execute();
            }
        }

        $this->log('----------');
        $this->log('Total time: '.(time() - $start));
    }

    private function dryUserCardsProcess(string $drsId, int $userId, array $drsCards, array $shopCards): void
    {
        $cardsToDelete = $shopCards;

        $logData = [];
        foreach ($drsCards as $card) {
            // pokud by nemela cislo karty, tak skipuju
            if (empty($card['number'])) {
                continue;
            }

            // pokud karta uz existuje, tak nic nedelam a jen ji odeberu z karet ke smazani
            if (($cardId = array_search($card['number'], $shopCards)) !== false) {
                unset($cardsToDelete[$cardId]);
                continue;
            }

            if (($card['status'] ?? false) !== '1') {
                continue;
            }

            $logData[] = [
                'drsId' => $drsId,
                'userId' => $userId,
                'action' => 'INSERT',
                'card' => $card['number'],
            ];
        }

        if (!empty($cardsToDelete)) {
            foreach ($cardsToDelete as $id => $card) {
                $logData[] = [
                    'drsId' => $drsId,
                    'userId' => $userId,
                    'action' => 'DELETE',
                    'card' => $card,
                ];
            }
        }

        if (empty($logData)) {
            $logData[] = [
                'drsId' => $drsId,
                'userId' => $userId,
                'action' => 'NO_CHANGE',
                'card' => '',
            ];
        }

        $this->createLogRecords($logData);
    }

    private function createLogRecords(array $logs): void
    {
        static $tableCheck;

        if (!$tableCheck) {
            sqlQuery('DROP TABLE IF EXISTS tmp_user_cards_logs');
            sqlQuery('CREATE TABLE IF NOT EXISTS `tmp_user_cards_logs` (
              `drsId` VARCHAR(50) NOT NULL,
              `userId` INT(11) NOT NULL,
              `action` VARCHAR(20) NOT NULL,
              `card` VARCHAR(100) NOT NULL
            );');

            $tableCheck = true;
        }

        if (empty($logs)) {
            return;
        }

        $qb = sqlQueryBuilder()
            ->insert('tmp_user_cards_logs');

        foreach ($logs as $log) {
            $qb->multiDirectValues($log);
        }

        $qb->execute();
    }

    private function getData(): iterable
    {
        return $this->getDRSApi()->getQueryBuilder()
            ->select('ContactNumber as id, IdentityValue as number, Status as status')
            ->from('eshop.CustomersCards')
            ->execute();
    }

    private function getDRSApi(): DRSApi
    {
        static $drsApi;

        return $drsApi ??= ServiceContainer::getService(DRSApi::class);
    }

    private function getUserIdByDRSId(string $drsId): ?int
    {
        $id = sqlQueryBuilder()
            ->select('id_user')
            ->from('drs_users')
            ->where(Operator::equals(['id_drs' => $drsId]))
            ->execute()->fetchOne();

        if (!$id) {
            return null;
        }

        return (int) $id;
    }

    private function getUserCards(int $userId): array
    {
        static $userCards;

        if (!$userCards) {
            $qb = sqlQueryBuilder()
                ->select('id, id_user, code')
                ->from('user_cards');

            foreach ($qb->execute() as $item) {
                $userCards[$item['id_user']][$item['id']] = $item['code'];
            }
        }

        return $userCards[$userId] ?? [];
    }

    private function createUserCard(int $userId, string $cardNumber, bool $deep = false): void
    {
        try {
            sqlQueryBuilder()
                ->insert('user_cards')
                ->directValues(
                    [
                        'id_user' => $userId,
                        'code' => $cardNumber,
                    ]
                )->execute();
        } catch (UniqueConstraintViolationException $e) {
            if ($deep) {
                throw $e;
            }

            sqlQueryBuilder()
                ->delete('user_cards')
                ->where(Operator::equals(['code' => $cardNumber]))
                ->execute();

            $this->createUserCard($userId, $cardNumber, true);
        }
    }
}

return DRSUpdateUserCardsScript::class;
