<?php

namespace Autogedal\TbiBank\Model;

use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Sales\Model\OrderFactory;
use Magento\Sales\Model\ResourceModel\Order as OrderResource;

class ApplicationRegistry
{
    public const TABLE = 'autogedal_tbi_application';

    public function __construct(
        private ResourceConnection $resource,
        private EncryptorInterface $encryptor,
        private Environment $environment,
        private OrderFactory $orderFactory,
        private OrderResource $orderResource
    ) {
    }

    public function save(string $merchantOrderId, int $providerOrderId, string $token): void
    {
        if ($providerOrderId <= 0 || $token === '') {
            throw new LocalizedException(__('TBI did not return a valid application ID and token.'));
        }
        $connection = $this->resource->getConnection();
        $ids = $connection->fetchCol(
            'SELECT entity_id FROM ' . $connection->quoteIdentifier($this->resource->getTableName('sales_order'))
                . ' WHERE increment_id = ?', [$merchantOrderId]
        );
        if (count($ids) !== 1) {
            throw new LocalizedException(__('The TBI order number is unknown or ambiguous.'));
        }
        $order = $this->orderFactory->create();
        $this->orderResource->load($order, (int) $ids[0]);
        if (!$order->getPayment() || $order->getPayment()->getMethod() !== PaymentMethod::CODE) {
            throw new LocalizedException(__('The order does not use TBI financing.'));
        }
        if (!hash_equals($this->environment->getAccountFingerprint(), $this->environment->getAccountFingerprint($order->getStoreId()))) {
            throw new LocalizedException(__('The TBI account does not match the order store.'));
        }

        $connection->beginTransaction();
        try {
            $existing = $connection->fetchRow(
                $connection->select()
                    ->from($this->resource->getTableName(self::TABLE), ['provider_order_id'])
                    ->where('order_id = ?', (int) $ids[0])
                    ->forUpdate(true)
            );
            if (is_array($existing) && (int) ($existing['provider_order_id'] ?? 0) !== $providerOrderId) {
                $order->addStatusHistoryComment(
                    __(
                        'TBI Bank: a doua aplicatie de finantare a fost inregistrata pentru aceasta comanda. '
                        . 'Aplicatia initiala este %1, iar aplicatia noua este %2. Tokenul initial a fost pastrat.',
                        (int) $existing['provider_order_id'],
                        $providerOrderId
                    )
                )->setIsCustomerNotified(false);
                $this->orderResource->save($order);
                $connection->commit();
                return;
            }
            if (is_array($existing)) {
                $connection->commit();
                return;
            }

            $connection->insertOnDuplicate($this->resource->getTableName(self::TABLE), [
                'order_id' => (int) $ids[0],
                'provider_order_id' => $providerOrderId,
                'token' => $this->encryptor->encrypt($token),
                'account_fingerprint' => $this->environment->getAccountFingerprint($order->getStoreId()),
                'last_status' => null,
                'last_checked_at' => null,
            ], ['provider_order_id', 'token', 'account_fingerprint', 'last_status', 'last_checked_at']);
            $connection->commit();
        } catch (\Throwable $exception) {
            $connection->rollBack();
            throw $exception;
        }
    }

    public function getCandidates(int $limit): array
    {
        $connection = $this->resource->getConnection();
        return $connection->fetchAll($connection->select()
            ->from(['a' => $this->resource->getTableName(self::TABLE)])
            ->joinInner(['o' => $this->resource->getTableName('sales_order')], 'o.entity_id = a.order_id', [])
            ->where('o.status = ?', ApprovalConfig::WAITING_STATUS)
            ->order('a.last_checked_at ASC')->order('a.order_id ASC')->limit($limit));
    }

    public function token(array $row): string
    {
        $token = $this->encryptor->decrypt((string) $row['token']);
        if ($token === '') {
            throw new LocalizedException(__('The stored TBI application token cannot be decrypted.'));
        }
        return $token;
    }

    public function checked(int $orderId, ?string $status = null): void
    {
        $values = ['last_checked_at' => gmdate('Y-m-d H:i:s')];
        if ($status !== null) {
            $values['last_status'] = $status;
        }
        $this->resource->getConnection()->update(
            $this->resource->getTableName(self::TABLE), $values, ['order_id = ?' => $orderId]
        );
        if ($status !== null) {
            // Show the bank response, but never overwrite an approval that raced with this poll.
            foreach (['sales_order', 'sales_order_grid'] as $table) {
                $this->resource->getConnection()->update(
                    $this->resource->getTableName($table), ['tbi_status' => $status],
                    ['entity_id = ?' => $orderId, 'status = ?' => ApprovalConfig::WAITING_STATUS]
                );
            }
        }
    }
}
