<?php

declare(strict_types=1);

namespace KupShop\BankAutoPaymentBundle\PaymentSources;

use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\BankAutoPaymentBundle\Entity\BankTransaction;
use KupShop\I18nBundle\Util\PriceConverter;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Database\QueryHint;
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
use KupShop\KupShopBundle\Util\StringUtil;
use KupShop\KupShopBundle\Util\System\CurlUtil;
use Query\Operator;
use Symfony\Contracts\Service\Attribute\Required;

abstract class AbstractPaymentSource
{
    protected static string $name;
    protected static string $logTag;

    protected CurlUtil $curlUtil;
    protected SentryLogger $sentryLogger;
    protected array $config = [];
    protected array $allowedPaymentTypes = [];

    abstract public function checkPayments(?\DateTime $forceDate = null): bool;

    abstract protected function loadConfig();

    public static function getName(): string
    {
        return static::$name;
    }

    public static function getLogTag(): string
    {
        return static::$logTag;
    }

    protected function checkPayment(BankTransaction $transaction): void
    {
        $type = $transaction->getType() ?? false;
        $price = $transaction->getPrice() ?? false;
        $transactionCurrency = $transaction->getCurrency() ?? false;
        $transactionId = $transaction->getId() ?? false;
        $transactionComment = $transaction->getComment() ?? false;
        $vs = $transaction->getVariableSymbol() ?? false;

        $transactionAccount = $transaction->getAccount();

        $orderIdentifiers = $transaction->getOrderIdentifier();

        $transactionData = [
            'type' => $type,
            'orderIdentifiers' => $orderIdentifiers,
            'price' => $price,
            'transactionCurrency' => $transactionCurrency,
            'transactionAccount' => $transactionAccount,
        ];

        // TMP: Log all payments - #15184
        ServiceContainer::getService('logger')->notice('checkPayment', [...$transactionData, 'transactionId' => $transactionId]);

        if (empty($orderIdentifiers) || !$type || !$transactionCurrency || $price === false
            || (!empty($this->allowedPaymentTypes) && !in_array($type, $this->allowedPaymentTypes))) {
            if ($price && ($price > 0)) {
                addActivityLog(ActivityLog::SEVERITY_WARNING,
                    ActivityLog::TYPE_SYNC,
                    sprintf($this::getName().': '.translate('orderNotFound', 'autoPayment', false, true), $transactionId, $vs),
                    $transactionData,
                    [$this::getLogTag()]);
            }

            return;
        }

        if (!$orderID = $this->findPaymentOrder($transactionId, $orderIdentifiers, $transactionData)) {
            return;
        }

        $order = new \Order();
        $order->createFromDB($orderID);

        if ($order->getCurrency() != $transactionCurrency) {
            $currencyContext = Contexts::get(CurrencyContext::class);
            if (array_key_exists($transactionCurrency, $currencyContext->getSupported())) {
                $priceConverter = ServiceContainer::getService(PriceConverter::class);
                $price = $priceConverter->convert($transactionCurrency, $order->getCurrency(), $price)->asFloat();
            } else {
                $data = ['transactionCurrency' => $transactionCurrency];
                addActivityLog(ActivityLog::SEVERITY_ERROR,
                    ActivityLog::TYPE_SYNC,
                    sprintf($this::getName().': '.translate('paymentInvalidCurrency', 'autoPayment', false, true), $order->order_no, $transactionCurrency),
                    $data + ActivityLog::addObjectData([$order->id => $order->order_no], 'orders'),
                    [$this::getLogTag()]);

                return;
            }
        }

        // "číslo protiúčtu"
        $accountNo = $transaction->getAccountNumber() ?? '';

        // "kód banky"
        $bankCode = $transaction->getBankCode();
        $accountNo .= !empty($bankCode) ? "/{$bankCode}" : '';

        $paymentId = $this->createPayment($orderID, $price, $transactionId, $accountNo);

        if ($paymentId) {
            // v updatePayments() se nastavuje date_handle a následně se neodeslal email sendPaymentReceipt
            $dateHandleBeforeUpdate = $order->date_handle;
            $order->updatePayments();

            $transactionComment = StringUtil::unicode_trim($transactionComment);
            $logComment = 'Modul '.$this::getName().' - '.sprintf(translate('orderPaidComment', 'autoPayment', false, true), $transactionId)
                .((empty($transactionComment)) ? '' : ' '.sprintf(translate('paymentComment', 'autoPayment', false, true),
                    $transactionComment));

            if ($order->status_payed == 1 && is_numeric($this->config['newState']) && empty($dateHandleBeforeUpdate)) {
                $order->changeStatus($this->config['newState'], $logComment, false);
            } else {
                $order->logHistory($logComment);
            }

            $activityMessage = 'orderPaidActivityFail';

            if ($order->status_payed == 1) {
                $doNotSendEmail = $this->config['doNotSendEmail'] ?? false;
                if (empty($dateHandleBeforeUpdate) && empty($doNotSendEmail)) {
                    $order->sendPaymentReceipt($paymentId);
                }
                $activityMessage = 'orderPaidActivitySuccess';
            }

            $data = ['paymentId' => $paymentId];
            addActivityLog(($activityMessage == 'orderPaidActivityFail') ? ActivityLog::SEVERITY_ERROR : ActivityLog::SEVERITY_SUCCESS,
                ActivityLog::TYPE_SYNC,
                $this::getName().': '.sprintf(translate($activityMessage, 'autoPayment', false, true), $order->order_no),
                $data + ActivityLog::addObjectData([$order->id => $order->order_no], 'orders'),
                [$this::getLogTag()]);
        }
    }

    public function findPaymentOrder($transactionId, $orderIdentifiers, array $transactionData = [])
    {
        $qb = $this->getOrderQuery();
        $qb->andWhere(Operator::inStringArray($orderIdentifiers, 'order_no'));
        $orderID = $qb->execute()->fetchColumn();

        if (!$orderID) {
            $qb = $this->getOrderQuery();
            $qb->andWhere(Operator::inStringArray($orderIdentifiers, "TRIM(LEADING '0' FROM order_no)"));
            $orderID = $qb->execute()->fetchColumn();
        }

        if (!$orderID) {
            $allInOne = join('', $orderIdentifiers);
            preg_match_all('/(\d{5,})/', $allInOne, $agreements);
            $qb = $this->getOrderQuery();
            $qb->andWhere(Operator::inStringArray($agreements[0], 'order_no'));
            $orderID = $qb->execute()->fetchColumn();
        }

        if (!$orderID) {
            $data = $transactionData ?: ['orderIdentifiers' => $orderIdentifiers];
            addActivityLog(ActivityLog::SEVERITY_ERROR,
                ActivityLog::TYPE_SYNC,
                sprintf($this::getName().': '.translate('orderNotFound', 'autoPayment', false, true), $transactionId, json_encode($orderIdentifiers)),
                $data,
                [$this::getLogTag()]);

            return null;
        }

        return $orderID;
    }

    public function getOrderQuery()
    {
        return sqlQueryBuilder()->select('id')
            ->from('orders')
            ->andWhere(
                Operator::orX(
                    Operator::inIntArray(getStatuses('nothandled'), 'status'),
                    'TIMESTAMPDIFF(MONTH, date_handle, NOW()) <= 6'
                )
            )
            ->orderBy('id', 'DESC')
            ->sendToMaster();
    }

    protected function createPayment($orderId, $price, $transactionId, string $accountNo = ''): false|int|string
    {
        $fields = [
            'id_order' => $orderId,
            'price' => $price,
            'note' => "Platba č. {$transactionId} modulu ".$this::getName().' '.(!empty($accountNo) ? " č.ú.: {$accountNo}" : ''),
            'date' => (new \DateTime())->format('Y-m-d H:i:s'),
            'payment_data' => json_encode(['transactionId' => $transactionId]),
            'method' => \Payment::METHOD_TRANSFER,
        ];

        $paymentExists = sqlQueryBuilder()->select('id')->from('order_payments')
            ->where(Operator::equals(['id_order' => $fields['id_order']]))
            ->andWhere(
                Operator::orX(
                    Operator::like(['payment_data' => "%\"transactionId\":{$transactionId}%"]),
                    Operator::like(['payment_data' => "%\"transactionId\":\"{$transactionId}\"%"])
                ))
            ->sendToMaster()
            ->execute()->fetch();

        if (!$paymentExists) {
            return QueryHint::withRouteToMaster(function () use ($fields) {
                sqlQueryBuilder()->insert('order_payments')->directValues($fields)->execute();

                return sqlInsertId();
            });
        }

        return false;
    }

    public function runCheckPaymentsCron(): bool
    {
        if (!$this->isEnabled() || !($this->config['enableCron'] ?? false)) {
            return true;
        }

        return $this->checkPayments();
    }

    public function runCheckPayments(?\DateTime $date = null)
    {
        if (!$this->isEnabled()) {
            return true;
        }

        addActivityLog(ActivityLog::SEVERITY_NOTICE,
            ActivityLog::TYPE_SYNC,
            'Kontrola plateb '.$this->getName().' API (manuálně)',
            tags: [$this::getLogTag()]);

        return $this->checkPayments(forceDate: $date);
    }

    protected function isEnabled(): bool
    {
        return $this->loadConfig() && !($this->config['test'] ?? false);
    }

    #[Required]
    public function setCurlUtil(CurlUtil $curlUtil): void
    {
        $this->curlUtil = $curlUtil;
    }

    #[Required]
    public function setSentryLogger(SentryLogger $sentryLogger): void
    {
        $this->sentryLogger = $sentryLogger;
    }
}
