<?php
namespace Bot\Commands\SystemCommands;

use Bot\Config;
use Bot\Database;
use Bot\Localization;
use Bot\Services\AdminWalletService;
use Bot\Services\BalanceService;
use Bot\Services\FreeGoodsService;
use Bot\Services\KycService;
use Bot\Services\OrderService;
use Bot\Services\PaymentMethodService;
use Bot\Services\RateService;
use Bot\Services\ReferralService;
use Bot\Services\StateService;
use Bot\Services\VirtualCardTemplateService;
use Bot\Services\WithdrawalService;
use Bot\WalletService;
use Longman\TelegramBot\Commands\SystemCommand;
use Longman\TelegramBot\Entities\InlineKeyboard;
use Longman\TelegramBot\Entities\ServerResponse;
use Longman\TelegramBot\Entities\Keyboard;
use Longman\TelegramBot\Request;
use PDO;

class GenericmessageCommand extends SystemCommand
{
    protected $name = 'genericmessage';
    protected $description = 'Handle text-based interactions';
    protected $version = '3.0.0';

    private PDO $pdo;
    private StateService $stateService;
    private OrderService $orderService;
    private RateService $rateService;
    private KycService $kycService;
    private PaymentMethodService $paymentMethodService;
    private AdminWalletService $adminWalletService;
    private BalanceService $balanceService;
    private WithdrawalService $withdrawalService;
    private ReferralService $referralService;
    private FreeGoodsService $freeGoodsService;
    private VirtualCardTemplateService $virtualCardTemplates;

    public function __construct($telegram, $update)
    {
        parent::__construct($telegram, $update);
        $this->pdo = Database::connection();
        $this->stateService = new StateService();
        $this->rateService = new RateService();
        $this->orderService = new OrderService($this->rateService);
        $this->kycService = new KycService();
        $this->paymentMethodService = new PaymentMethodService();
        $this->adminWalletService = new AdminWalletService();
        $this->balanceService = new BalanceService();
        $this->withdrawalService = new WithdrawalService();
        $this->referralService = new ReferralService();
        $this->freeGoodsService = new FreeGoodsService();
        $this->virtualCardTemplates = new VirtualCardTemplateService();
    }

    public function execute(): ServerResponse
    {
        $message = $this->getMessage();
        $photo = $message->getPhoto() ?: [];
        $document = $message->getDocument();
        $skipExpire = !empty($photo) || $document !== null;
        if (!$skipExpire) {
            $expired = $this->orderService->expireStaleOrders();
            if ($expired) {
                $this->notifyExpiredOrders($expired);
            }
        }

        $chat = $message->getChat();
        $chatId = $chat->getId();
        $telegramId = $message->getFrom()->getId();

        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE telegram_id = ?');
        $stmt->execute([$telegramId]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$user) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => 'Please send /start to register first.',
            ]);
        }
        if (!empty($user['is_banned'])) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'account_banned_message'),
            ]);
        }
        $userId = (int) $user['id'];
        $lang = $user['language'] ?? 'en';

        $photo = $message->getPhoto() ?: [];
        $document = $message->getDocument();
        $skipExpire = !empty($photo) || $document !== null;
        if ($photo || $document) {
            $response = $this->handleMediaSubmission($user, $chatId, $photo, $document, $message->getCaption());
            $this->maybeExpireOrders($skipExpire);
            return $response;
        }

        $text = trim($message->getText() ?? '');
        if ($text === '') {
            return Request::emptyResponse();
        }

        if (str_starts_with($text, '/')) {
            $command = substr($text, 1);
            
            // Let the library handle the command.
            $this->telegram->executeCommand($command);
            return Request::emptyResponse();
        }

        if (strcasecmp($text, 'cancel') === 0) {
            $this->stateService->clearState($userId);
            $response = Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'flow_cancelled'),
            ]);
            $this->maybeExpireOrders($skipExpire);
            return $response;
        }

        $state = $this->stateService->getState($userId);
        if ($state) {
            $response = $this->handleStateInput($user, $chatId, $text, $state);
            $this->maybeExpireOrders($skipExpire);
            return $response;
        }

        $normalized = $this->normalizeUserText($text);
        $result = match ($normalized) {
            'p2p', 'p2p menu'                   => $this->sendP2PMenu($chatId, $lang),
            'buy', 'buy menu', 'buy options'    => $this->sendBuyMenu($chatId, $lang),
            'sell', 'sell menu', 'sell options' => $this->sendSellMenu($chatId, $lang),
            'wallet'                            => $this->showWalletMenu($user),
            'my profile', 'profile'             => $this->sendProfileMenu($chatId, $lang),
            'view profile', 'profile info'      => $this->showProfile($user, $chatId),
            'my orders', 'orders'               => $this->showOrders($user, $chatId),
            'kyc verification', 'kyc'           => $this->handleKyc($user, $chatId),
            'help & support', 'help support', 'help', 'support'   => $this->sendHelpMenu($chatId, $lang),
            'change network', 'network'         => $this->showNetworkSelector($user),
            'buy usdt', 'buy usd/crypto'        => $this->promptAmount($user, $chatId, 'buy', 'USDT'),
            'buy crypto'                        => $this->promptCurrency($user, $chatId, 'buy'),
            'buy fiat'                          => $this->promptCurrency($user, $chatId, 'fiat_buy'),
            'sell usdt', 'sell usd/crypto'      => $this->promptAmount($user, $chatId, 'sell', 'USDT'),
            'sell crypto'                       => $this->promptCurrency($user, $chatId, 'sell'),
            'sell fiat'                         => $this->promptCurrency($user, $chatId, 'fiat_sell'),
            'virtual card'                      => $this->handleVirtualCard($chatId, $lang),
            'withdraw', 'withdraw funds'        => $this->startWithdrawFlow($user, $chatId),
            'referral & points', 'points'       => $this->sendPointsSummary($user, $chatId),
            'referral link', 'refer link', 'share referral link' => $this->sendReferralLink($user, $chatId),
            'convert points', 'convertpoints', '♻️ convert points'   => $this->startConvertPointsFlow($user, $chatId),
            'balance', 'wallet balance', '💰 balance' => $this->sendWalletSummaryMessage($user, $chatId, $lang),
            'wallet history', 'history', 'my history' => $this->sendWalletHistory($user, $chatId, $lang),
            'free', 'free menu', '🎁 free', 'free goods' => $this->sendFreeGoodsMenu($chatId, $lang),
            'wallet rates', 'rates'             => $this->sendRatesOverview($chatId, $lang),
            'back', 'back menu', 'main menu'    => $this->sendMainMenu($chatId, $lang),
            default                             => Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'unknown_command'),
            ]),
        };
        $this->maybeExpireOrders($skipExpire);
        return $result;
    }

    private function handleStateInput(array $user, int $chatId, string $text, array $state): ServerResponse
    {
        $stateName = $state['state'];
        $data = $state['data'] ?? [];

        return match ($stateName) {
            'await_currency'         => $this->handleCurrencySelection($user, $chatId, $text, $data),
            'await_amount'           => $this->handleAmountEntry($user, $chatId, $text, $data),
            'await_payment_method'   => Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'choose_payment_method'),
            ]),
            'await_sell_payout_method' => Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'sell_choose_payout_method'),
            ]),
            'await_payout_details'   => $this->handlePayoutDetailsEntry($user, $chatId, $text, $data),
            'await_crypto_network'   => Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'sell_network_prompt', [$data['currency'] ?? 'USDT']),
            ]),
            'await_withdraw_amount'  => $this->handleWithdrawAmount($user, $chatId, $text, $data),
            'await_withdraw_address' => $this->handleWithdrawAddress($user, $chatId, $text, $data),
            'await_payment_proof'    => $this->remindPaymentProof($user, $chatId),
            'await_txid'             => $this->handleTxidEntry($user, $chatId, $text, $data),
            'await_admin_response'   => $this->handleAdminResponse($user, $chatId, $text, $data),
            'await_convert_points_amount' => $this->handleConvertPointsAmount($user, $chatId, $text),
            default                  => Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'unknown_command'),
            ]),
        };
    }

    private function promptCurrency(array $user, int $chatId, string $mode): ServerResponse
    {
        $userId = (int) $user['id'];
        $lang = $user['language'] ?? 'en';
        $this->stateService->setState($userId, 'await_currency', [
            'mode' => $mode,
        ]);
        $supported = implode(', ', $this->rateService->getSupportedCurrencies());
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'prompt_currency', [$supported]),
        ]);
    }

    private function promptAmount(array $user, int $chatId, string $mode, string $currency): ServerResponse
    {
        $userId = (int) $user['id'];
        $lang = $user['language'] ?? 'en';
        $this->stateService->setState($userId, 'await_amount', [
            'mode'     => $mode,
            'currency' => strtoupper($currency),
        ]);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'prompt_amount', [strtoupper($currency), $mode]),
        ]);
    }

    private function handleCurrencySelection(array $user, int $chatId, string $text, array $data): ServerResponse
    {
        $currency = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $text));
        if ($currency === '') {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'prompt_currency_retry'),
            ]);
        }
        $supported = $this->rateService->getSupportedCurrencies();
        if (!in_array($currency, $supported, true)) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'unsupported_currency', [$currency]),
            ]);
        }
        $mode = $data['mode'] ?? 'buy';
        $this->stateService->setState((int) $user['id'], 'await_amount', [
            'mode'     => $mode,
            'currency' => $currency,
        ]);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($user['language'] ?? 'en', 'prompt_amount', [$currency, $mode]),
        ]);
    }

    private function handlePayoutDetailsEntry(array $user, int $chatId, string $text, array $data): ServerResponse
    {
        if (empty($data['payout_method_id'])) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'sell_no_payment_methods'),
            ]);
        }
        $details = trim($text);
        if ($details === '') {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'sell_enter_payout_details', [$data['payout_method_name'] ?? '']),
            ]);
        }
        $stateData = [
            'mode'               => $data['mode'] ?? 'sell',
            'currency'           => $data['currency'] ?? 'USDT',
            'amount'             => $data['amount'] ?? 0,
            'payout_method_id'   => $data['payout_method_id'] ?? null,
            'payout_method_name' => $data['payout_method_name'] ?? '',
            'payout_details'     => $details,
            'source'             => $data['source'] ?? 'onchain',
        ];
        if (($stateData['source'] ?? 'onchain') === 'balance') {
            $this->stateService->clearState((int) $user['id']);
            return $this->createBalanceSellOrder($user, $stateData, $chatId);
        }
        $this->stateService->setState((int) $user['id'], 'await_crypto_network', $stateData);
        Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($user['language'] ?? 'en', 'sell_payout_details_saved'),
        ]);
        return $this->promptSellNetworkSelection($user, $chatId, $stateData);
    }

    private function promptSellNetworkSelection(array $user, int $chatId, array $data): ServerResponse
    {
        $networks = $this->adminWalletService->getAvailable();
        if (!$networks) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'sell_network_missing', [$data['currency'] ?? 'USDT']),
            ]);
        }
        $rows = [];
        foreach ($networks as $network => $address) {
            $rows[] = [['text' => strtoupper($network), 'callback_data' => 'sellnet_' . strtoupper($network)]];
        }
        $rows[] = [['text' => '⬅️ Back', 'callback_data' => 'back_main']];
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($user['language'] ?? 'en', 'sell_network_prompt', [$data['currency'] ?? 'USDT']),
            'reply_markup' => new InlineKeyboard(...$rows),
        ]);
    }

    private function startWithdrawFlow(array $user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        if (($user['kyc_status'] ?? 'unverified') !== 'verified') {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'withdraw_kyc_required'),
            ]);
        }
        $currency = 'USDT';
        $network = strtoupper($user['default_network'] ?? 'ERC20');
        $balance = $this->balanceService->getBalance((int) $user['id'], $currency);
        $available = $balance['available'];
        $this->stateService->setState((int) $user['id'], 'await_withdraw_amount', [
            'currency' => $currency,
            'network'  => $network,
        ]);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'withdraw_enter_amount', [$currency, $available]),
        ]);
    }

    private function handleWithdrawAmount(array $user, int $chatId, string $text, array $data): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $amount = (float) str_replace(',', '', $text);
        if ($amount <= 0) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'withdraw_amount_invalid'),
            ]);
        }
        $currency = $data['currency'] ?? 'USDT';
        $balance = $this->balanceService->getBalance((int) $user['id'], $currency);
        if ($amount > (float) $balance['available']) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'withdraw_amount_invalid'),
            ]);
        }
        $amountFormatted = number_format($amount, 6, '.', '');
        $network = $data['network'] ?? $user['default_network'] ?? 'ERC20';
        $this->stateService->setState((int) $user['id'], 'await_withdraw_address', [
            'currency' => $currency,
            'network'  => strtoupper($network),
            'amount'   => $amountFormatted,
        ]);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'withdraw_enter_address', [$currency, strtoupper($network)]),
        ]);
    }

    private function handleWithdrawAddress(array $user, int $chatId, string $text, array $data): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $address = trim($text);
        if ($address === '') {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'withdraw_enter_address', [$data['currency'] ?? 'USDT', $data['network'] ?? 'ERC20']),
            ]);
        }
        $currency = $data['currency'] ?? 'USDT';
        $network = strtoupper($data['network'] ?? $user['default_network'] ?? 'ERC20');
        $amount = $data['amount'] ?? '0';
        $walletService = new WalletService();
        $wallet = $walletService->getWallet((int) $user['id'], $network);
        if (!$wallet) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'no_wallet_generate'),
            ]);
        }
        try {
            $this->balanceService->lock((int) $user['id'], $currency, $amount);
            $withdrawalId = $this->withdrawalService->create(
                (int) $user['id'],
                (int) $wallet['id'],
                $currency,
                $network,
                $amount,
                $address
            );
        } catch (\Throwable $e) {
            $this->balanceService->unlock((int) $user['id'], $currency, $amount);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'withdraw_request_failed', [$e->getMessage()]),
            ]);
        }
        $this->stateService->clearState((int) $user['id']);
        $this->notifyAdminsWithdrawal($user, $withdrawalId, $currency, $amount, $network, $address);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'withdraw_under_review', [$withdrawalId]),
        ]);
    }

    private function createBalanceSellOrder(array $user, array $data, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $currency = $data['currency'] ?? 'USDT';
        $amount = $data['amount'] ?? '0';
        $method = $this->paymentMethodService->getById((int) ($data['payout_method_id'] ?? 0));
        if (!$method) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'sell_balance_failed', ['Method missing']),
            ]);
        }
        try {
            $this->balanceService->lock((int) $user['id'], $currency, $amount);
            $order = $this->orderService->createOrder(
                (int) $user['id'],
                'sell',
                $currency,
                (float) $amount,
                $method,
                [
                    'initial_payment_status' => 'from_balance',
                    'payout_details'         => $data['payout_details'] ?? null,
                    'crypto_status'          => 'from_balance',
                ]
            );
            $this->balanceService->consumeLocked((int) $user['id'], $currency, $amount);
            $this->notifyAdminsNewOrder($user, $order, $method, $data['payout_details'] ?? null);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'sell_balance_submitted', [$order['id']]),
            ]);
        } catch (\Throwable $e) {
            $this->balanceService->unlock((int) $user['id'], $currency, $amount);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'sell_balance_failed', [$e->getMessage()]),
            ]);
        }
    }

    private function handleAmountEntry(array $user, int $chatId, string $text, array $data): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $amount = (float) str_replace(',', '', $text);
        if ($amount <= 0) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'amount_invalid'),
            ]);
        }
        $mode = $data['mode'] ?? 'buy';
        $currency = $data['currency'] ?? 'USDT';
        $options = [];
        if ($mode === 'sell') {
            $balance = $this->balanceService->getBalance((int) $user['id'], $currency);
            if ($amount <= (float) $balance['available']) {
                $options['source'] = 'balance';
            } else {
                $options['source'] = 'onchain';
            }
        }

        try {
            $this->orderService->enforceLimits((int) $user['id'], $amount);
        } catch (\RuntimeException $e) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'limit_error', [$e->getMessage()]),
            ]);
        }

        if ($this->kycService->requiresKyc($amount) && $user['kyc_status'] !== 'verified') {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'kyc_required_message'),
            ]);
        }

        if ($mode === 'sell') {
            return $this->promptSellPayoutMethod($user, $chatId, $currency, $amount);
        }
        if ($mode === 'sell') {
            return $this->promptSellPayoutMethod($user, $chatId, $currency, $amount, $options);
        }

        return $this->promptPaymentMethod($user, $chatId, $mode, $currency, $amount, $options);
    }

    private function promptPaymentMethod(
        array $user,
        int $chatId,
        string $mode,
        string $currency,
        float $amount,
        array $options = []
    ): ServerResponse
    {
        $methods = $this->paymentMethodService->getActive('buy');
        if (!$methods) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'no_payment_methods'),
            ]);
        }
        $state = [
            'mode'     => $mode,
            'currency' => $currency,
            'amount'   => $amount,
        ];
        if ($options) {
            $state = array_merge($state, $options);
        }
        $this->stateService->setState((int) $user['id'], 'await_payment_method', $state);
        $rows = [];
        foreach ($methods as $method) {
            $rows[] = [['text' => $method['name'], 'callback_data' => 'paymethod_' . $method['id']]];
        }
        $rows[] = [['text' => '⬅️ Back', 'callback_data' => 'back_main']];
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($user['language'] ?? 'en', 'choose_payment_method'),
            'reply_markup' => new InlineKeyboard(...$rows),
        ]);
    }

    private function promptSellPayoutMethod(
        array $user,
        int $chatId,
        string $currency,
        float $amount,
        array $options = []
    ): ServerResponse
    {
        $methods = $this->paymentMethodService->getActive('sell');
        if (!$methods) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'sell_no_payment_methods'),
            ]);
        }
        $state = [
            'mode'     => 'sell',
            'currency' => $currency,
            'amount'   => $amount,
            'source'   => $options['source'] ?? 'onchain',
        ];
        $this->stateService->setState((int) $user['id'], 'await_sell_payout_method', $state);
        $rows = [];
        foreach ($methods as $method) {
            $rows[] = [['text' => $method['name'], 'callback_data' => 'sellpm_' . $method['id']]];
        }
        $rows[] = [['text' => '⬅️ Back', 'callback_data' => 'back_main']];
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($user['language'] ?? 'en', 'sell_choose_payout_method'),
            'reply_markup' => new InlineKeyboard(...$rows),
        ]);
    }

    private function showProfile(array $user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $limits = $user['limits'] ? json_decode($user['limits'], true) : Config::get()->getValue('limits_defaults', []);
        $text = Localization::t($lang, 'profile_summary', [
            $user['username'] ? '@' . $user['username'] : $user['first_name'],
            ucfirst($user['kyc_status']),
            strtoupper($user['default_network'] ?? 'ERC20'),
            $user['referral_code'] ?? 'N/A',
            number_format((float) ($user['referral_bonus'] ?? 0), 2),
            $limits['daily'] ?? 0,
            $limits['weekly'] ?? 0,
            $limits['monthly'] ?? 0,
        ]);
        $points = $this->balanceService->getBalance((int) $user['id'], 'POINT');
        $text .= sprintf(
            "\nReferral Level: %s\nReferral Points: %s",
            $user['referral_level'] ?? 'Bronze',
            number_format((float) $points['available'], 4)
        );
        $link = $this->buildReferralLink($user);
        if ($link) {
            $text .= "\n" . Localization::t($lang, 'profile_referral_link', [$link]);
        }
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => $text,
        ]);
    }

    private function sendReferralLink(array &$user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $link = $this->buildReferralLink($user);
        if (!$link) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'referral_link_unavailable'),
            ]);
        }
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t(
                $lang,
                'referral_link_text',
                [$user['referral_code'] ?? '', $link]
            ),
        ]);
    }

    private function buildReferralLink(array &$user): ?string
    {
        $botUsername = ltrim((string) Config::get()->getValue('bot_username', ''), '@');
        if ($botUsername === '') {
            return null;
        }
        $code = $this->ensureReferralCode($user);
        return sprintf('https://t.me/%s?start=%s', $botUsername, $code);
    }

    private function ensureReferralCode(array &$user): string
    {
        if (!empty($user['referral_code'])) {
            return (string) $user['referral_code'];
        }
        $code = $this->referralService->generateCode();
        $stmt = $this->pdo->prepare('UPDATE users SET referral_code = ? WHERE id = ?');
        $stmt->execute([$code, $user['id']]);
        $user['referral_code'] = $code;
        return $code;
    }

    private function handleKyc(array $user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        if ($user['kyc_status'] === 'verified') {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'kyc_already_verified'),
            ]);
        }

        Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'kyc_intro'),
        ]);

        try {
            $session = $this->kycService->createSession((int) $user['id'], $user);
        } catch (\Throwable $e) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => 'Unable to start KYC: ' . $e->getMessage(),
            ]);
        }

        $keyboard = new InlineKeyboard(
            [
                ['text' => Localization::t($lang, 'kyc_verify_button'), 'web_app' => ['url' => $session['url']]],
            ]
        );

        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'kyc_start_webview'),
            'reply_markup' => $keyboard,
        ]);
    }

    private function showWalletMenu(array $user): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $balances = $this->balanceService->listBalances((int) $user['id']);
        $summary = $this->formatBalanceSummary($balances, $lang);
        $balanceLabel = $this->getWalletBalanceLabel($user);
        $keyboard = new InlineKeyboard(
            [
                ['text' => $balanceLabel, 'callback_data' => 'wallet_balance'],
            ],
            [
                ['text' => 'Generate Address', 'callback_data' => 'wallet_generate'],
                ['text' => 'Deposit Address', 'callback_data' => 'wallet_deposit'],
            ],
            [
                ['text' => 'Withdraw', 'callback_data' => 'start_withdraw'],
            ],
            [
                ['text' => '🧾 History', 'callback_data' => 'wallet_history'],
            ],
            [
                ['text' => '⭐ Points', 'callback_data' => 'wallet_points'],
                ['text' => '♻️ Convert Points', 'callback_data' => 'wallet_convert_points'],
            ],
            [
                ['text' => '📈 Rates', 'callback_data' => 'wallet_rates'],
            ],
            [
                ['text' => '🌐 Change Network', 'callback_data' => 'wallet_change_network'],
            ],
            [
                ['text' => '❌ Cancel', 'callback_data' => 'back_main'],
            ]
        );
        return Request::sendMessage([
            'chat_id'      => $user['telegram_id'],
            'text'         => $summary,
            'reply_markup' => $keyboard,
        ]);
    }

    /**
     * @param array<int,array{currency:string,available:string,locked:string}> $balances
     */
    private function formatBalanceSummary(array $balances, string $lang): string
    {
        if (!$balances) {
            return Localization::t($lang, 'wallet_summary_empty');
        }
        $lines = [Localization::t($lang, 'wallet_summary_title')];
        foreach ($balances as $row) {
            $lines[] = Localization::t(
                $lang,
                'wallet_summary_line',
                [
                    $row['currency'],
                    number_format((float) $row['available'], 4),
                    number_format((float) $row['locked'], 4),
                ]
            );
        }
        return implode("\n", $lines);
    }

    private function getWalletBalanceLabel(array $user): string
    {
        $currency = 'USDT';
        $balance = $this->balanceService->getBalance((int) $user['id'], $currency);
        $amount = number_format((float) $balance['available'], 4);
        return sprintf('Balance: %s %s', $amount, $currency);
    }

    private function sendWalletSummaryMessage(array $user, int $chatId, string $lang): ServerResponse
    {
        $balances = $this->balanceService->listBalances((int) $user['id']);
        $summary = $this->formatBalanceSummary($balances, $lang);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => $summary,
        ]);
    }

    private function showNetworkSelector(array $user): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $networks = Config::get()->getValue('supported_networks', ['ERC20', 'BEP20', 'TRC20']);
        $buttons = [];
        foreach ($networks as $network) {
            $buttons[] = [['text' => $network, 'callback_data' => 'network_' . $network]];
        }
        $buttons[] = [['text' => '⬅️ Back', 'callback_data' => 'back_main']];
        return Request::sendMessage([
            'chat_id'      => $user['telegram_id'],
            'text'         => Localization::t($lang, 'network_prompt'),
            'reply_markup' => new InlineKeyboard(...$buttons),
        ]);
    }

    private function showOrders(array $user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $orders = $this->orderService->listRecentOrders((int) $user['id']);
        if (!$orders) {
            $text = Localization::t($lang, 'no_orders');
        } else {
            $lines = [];
            foreach ($orders as $order) {
                $lines[] = sprintf(
                    '#%d %s %s %.2f @ %.4f (%s)',
                    $order['id'],
                    strtoupper($order['type']),
                    strtoupper($order['currency']),
                    $order['amount'],
                    $order['price'],
                    $order['payment_status'] ?? $order['status']
                );
            }
            $text = implode("\n", $lines);
        }
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => $text,
        ]);
    }

    private function showHelp(array $user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'help_text'),
        ]);
    }

    private function handleVirtualCard(int $chatId, string $lang): ServerResponse
    {
        $template = $this->virtualCardTemplates->getTemplate();
        $text = $template['text'] ?? Localization::t($lang, 'virtual_card_soon');
        $payload = [
            'chat_id' => $chatId,
            'text'    => $text,
        ];
        if (!empty($template['parse_mode'])) {
            $payload['parse_mode'] = $template['parse_mode'];
        }
        if (!empty($template['buttons'])) {
            $keyboard = $this->buildInlineKeyboard($template['buttons']);
            if ($keyboard) {
                $payload['reply_markup'] = $keyboard;
            }
        }
        return Request::sendMessage($payload);
    }

    private function sendMainMenu(int $chatId, string $lang): ServerResponse
    {
        $keyboard = $this->buildMainMenuKeyboard();
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($lang, 'main_menu_prompt'),
            'reply_markup' => $keyboard,
        ]);
    }

    private function buildMainMenuKeyboard(): Keyboard
    {
        $keyboard = new Keyboard(
            ['🅿️ P2P', '👛 Wallet'],
            ['💳 Virtual Card', '👤 My Profile'],
            ['🎁 Free', '🆘 Help & Support']
        );
        return $keyboard->setResizeKeyboard(true)->setOneTimeKeyboard(false);
    }

    private function sendBuyMenu(int $chatId, string $lang): ServerResponse
    {
        $keyboard = new Keyboard(
            ['🟢 Buy USDT', '🪙 Buy Crypto'],
            ['🏦 Buy Fiat', '⬅️ Back']
        );
        $keyboard->setResizeKeyboard(true)->setOneTimeKeyboard(false);
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($lang, 'choose_buy_option'),
            'reply_markup' => $keyboard,
        ]);
    }

    private function sendP2PMenu(int $chatId, string $lang): ServerResponse
    {
        $keyboard = new Keyboard(
            ['🛒 Buy', '💰 Sell'],
            ['⬅️ Back']
        );
        $keyboard->setResizeKeyboard(true)->setOneTimeKeyboard(false);
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($lang, 'p2p_menu_prompt'),
            'reply_markup' => $keyboard,
        ]);
    }

    private function sendSellMenu(int $chatId, string $lang): ServerResponse
    {
        $keyboard = new Keyboard(
            ['🔴 Sell USDT', '🪙 Sell Crypto'],
            ['💵 Sell Fiat', '⬅️ Back']
        );
        $keyboard->setResizeKeyboard(true)->setOneTimeKeyboard(false);
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($lang, 'choose_sell_option'),
            'reply_markup' => $keyboard,
        ]);
    }

    private function sendProfileMenu(int $chatId, string $lang): ServerResponse
    {
        $keyboard = new Keyboard(
            ['👤 View Profile', '🧾 My Orders'],
            ['✅ KYC Verification', '⭐ Referral & Points'],
            ['🔗 Referral Link'],
            ['⬅️ Back']
        );
        $keyboard->setResizeKeyboard(true)->setOneTimeKeyboard(false);
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($lang, 'profile_menu'),
            'reply_markup' => $keyboard,
        ]);
    }

    private function sendHelpMenu(int $chatId, string $lang): ServerResponse
    {
        $config = Config::get();
        $buttons = [];
        $privacy = (string) $config->getValue('privacy_url', '');
        if ($privacy !== '') {
            $buttons[] = [['text' => '📘 Privacy Policy', 'url' => $privacy]];
        }
        $tos = (string) $config->getValue('tos_url', '');
        if ($tos !== '') {
            $buttons[] = [['text' => '📄 Terms of Service', 'url' => $tos]];
        }
        $buttons[] = [['text' => '❌ Cancel', 'callback_data' => 'back_main']];
        $keyboard = new InlineKeyboard(...$buttons);
        return Request::sendMessage([
            'chat_id'      => $chatId,
            'text'         => Localization::t($lang, 'help_menu') . "\n\n" . Localization::t($lang, 'help_text'),
            'reply_markup' => $keyboard,
        ]);
    }

    private function sendWalletHistory(array $user, int $chatId, string $lang, int $limit = 10): ServerResponse
    {
        $entries = $this->balanceService->getLedger((int) $user['id'], $limit);
        if (!$entries) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'wallet_history_empty'),
            ]);
        }
        $lines = [Localization::t($lang, 'wallet_history_title')];
        foreach ($entries as $entry) {
            $sign = $entry['direction'] === 'credit' ? '+' : '-';
            $amount = number_format((float) $entry['amount'], 4);
            $category = $entry['category'];
            $when = (new \DateTime($entry['created_at']))->format('d M H:i');
            $lines[] = Localization::t(
                $lang,
                'wallet_history_line',
                [
                    $entry['currency'],
                    $sign,
                    $amount,
                    strtoupper($category),
                    $when,
                ]
            );
        }
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => implode("\n", $lines),
        ]);
    }

    private function sendFreeGoodsMenu(int $chatId, string $lang): ServerResponse
    {
        $items = $this->freeGoodsService->listActive();
        if (!$items) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'free_menu_empty'),
            ]);
        }
        Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'free_menu_intro'),
        ]);
        $response = null;
        foreach ($items as $item) {
            $text = Localization::t($lang, 'free_item_format', [$item['title'], $item['description']]);
            $payload = [
                'chat_id' => $chatId,
                'text'    => $text,
            ];
            if (!empty($item['parse_mode'])) {
                $payload['parse_mode'] = $item['parse_mode'];
            }
            if (!empty($item['button_url'])) {
                $label = $item['button_label'] ?: Localization::t($lang, 'free_button_label_default');
                $keyboard = $this->buildInlineKeyboard([[['text' => $label, 'url' => $item['button_url']]]]);
                if ($keyboard) {
                    $payload['reply_markup'] = $keyboard;
                }
            }
            $response = Request::sendMessage($payload);
        }
        $response = Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'free_menu_footer'),
        ]);
        return $response;
    }

    private function sendRatesOverview(int $chatId, string $lang): ServerResponse
    {
        $lines = [Localization::t($lang, 'wallet_rates_title')];
        $hasRates = false;
        foreach ($this->rateService->getSupportedCurrencies() as $currency) {
            $rates = $this->rateService->getRates($currency);
            if (!$rates) {
                continue;
            }
            $hasRates = true;
            $lines[] = Localization::t(
                $lang,
                'wallet_rates_line',
                [
                    strtoupper($currency),
                    number_format($rates['rate_buy'], 2),
                    number_format($rates['rate_sell'], 2),
                ]
            );
        }
        if (!$hasRates) {
            $lines[] = Localization::t($lang, 'wallet_rates_empty');
        }
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => implode("\n", $lines),
        ]);
    }

    /**
     * @param array<int,array<int,array<string,string>>> $rows
     */
    private function buildInlineKeyboard(array $rows): ?InlineKeyboard
    {
        $keyboardRows = [];
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $buttons = [];
            foreach ($row as $button) {
                if (!is_array($button) || empty($button['text'])) {
                    continue;
                }
                $btn = ['text' => $button['text']];
                if (!empty($button['url'])) {
                    $btn['url'] = $button['url'];
                } elseif (!empty($button['callback_data'])) {
                    $btn['callback_data'] = $button['callback_data'];
                } else {
                    continue;
                }
                $buttons[] = $btn;
            }
            if ($buttons) {
                $keyboardRows[] = $buttons;
            }
        }
        if (!$keyboardRows) {
            return null;
        }
        return new InlineKeyboard(...$keyboardRows);
    }

    private function notifyAdminsWithdrawal(array $user, int $withdrawalId, string $currency, string $amount, string $network, string $address): void
    {
        $admins = Config::get()->getValue('admin_ids', []);
        $text = sprintf(
            "💸 New withdrawal request #%d\nUser: %s (ID %d)\nAmount: %s %s\nNetwork: %s\nAddress: %s\n\nCommands:\n`/adminwithdraw approve %d`\n`/adminwithdraw hold %d <reason>`\n`/adminwithdraw reject %d <reason>`",
            $withdrawalId,
            $user['username'] ? '@' . $user['username'] : $user['first_name'],
            $user['id'],
            $amount,
            strtoupper($currency),
            strtoupper($network),
            $address,
            $withdrawalId,
            $withdrawalId,
            $withdrawalId
        );
        foreach ($admins as $adminId) {
            if (!is_numeric($adminId)) {
                continue;
            }
            Request::sendMessage([
                'chat_id' => (int) $adminId,
                'text'    => $text,
            ]);
        }
    }

    private function sendPointsSummary(array $user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $pointsBalance = $this->balanceService->getBalance((int) $user['id'], 'POINT');
        $points = number_format((float) $pointsBalance['available'], 4);
        $rate = $this->referralService->getPointRate();
        $usd = number_format((float) $pointsBalance['available'] * $rate, 2);
        $text = sprintf(
            "Referral Level: %s\nPoints: %s (≈ %s USDT)\nUse /convertpoints <amount> to convert points into USDT.",
            $user['referral_level'] ?? 'Bronze',
            $points,
            $usd
        );
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => $text,
        ]);
    }

    private function startConvertPointsFlow(array $user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $this->stateService->setState((int) $user['id'], 'await_convert_points_amount');
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($lang, 'convert_points_prompt'),
        ]);
    }

    private function handleConvertPointsAmount(array $user, int $chatId, string $text): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $amount = (float) str_replace(',', '', $text);
        if ($amount <= 0) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'convert_points_invalid'),
            ]);
        }
        try {
            $usd = $this->referralService->convertPoints((int) $user['id'], $amount);
        } catch (\Throwable $e) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => 'Conversion failed: ' . $e->getMessage(),
            ]);
        }
        $this->stateService->clearState((int) $user['id']);
        $message = sprintf(
            Localization::t($lang, 'convert_points_success'),
            $amount,
            $usd
        );
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => $message,
        ]);
    }

    /**
     * @param array<int,\Longman\TelegramBot\Entities\PhotoSize> $photo
     */
    private function handleMediaSubmission(array $user, int $chatId, array $photo, $document, ?string $caption): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $state = $this->stateService->getState((int) $user['id']);
        $pendingOrderId = !empty($state['data']['order_id']) ? (int) $state['data']['order_id'] : 0;
        $order = $pendingOrderId
            ? $this->orderService->getOrderById($pendingOrderId)
            : $this->getActivePaymentOrder($user);

        if (!$order) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'payment_proof_invalid'),
            ]);
        }

        $lang = $user['language'] ?? 'en';
        if ($order['status'] === 'cancelled' || $order['payment_status'] === 'expired') {
            $this->stateService->clearState((int) $user['id']);
            [$deadlineText] = $this->formatDeadlineInfo($order['payment_deadline'] ?? null);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'order_cancelled_due_timeout', [$order['id'], $deadlineText]),
            ]);
        }

        if (!in_array($order['payment_status'], ['waiting_proof', 'under_review'], true)) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'payment_proof_invalid'),
            ]);
        }
        $orderId = (int) $order['id'];
        $fileId = null;
        $type = 'photo';
        if ($photo) {
            $last = end($photo);
            $fileId = $last ? $last->getFileId() : null;
        } elseif ($document) {
            $fileId = $document->getFileId();
            $type = 'document';
        }
        if (!$fileId) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'payment_proof_invalid'),
            ]);
        }
        $this->orderService->submitPaymentProof($orderId, $fileId, $type, $caption);
        $this->stateService->clearState((int) $user['id']);
        $this->notifyAdmins(sprintf('🧾 New payment proof for order #%d', $orderId), $orderId);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($user['language'] ?? 'en', 'payment_proof_received'),
        ]);
    }

    private function handleTxidEntry(array $user, int $chatId, string $text, array $data): ServerResponse
    {
        $orderId = (int) ($data['order_id'] ?? 0);
        $order = $orderId > 0 ? $this->orderService->getOrderById($orderId) : null;
        if (!$order || (int) $order['user_id'] !== (int) $user['id']) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'unknown_command'),
            ]);
        }
        $txid = trim($text);
        if ($txid === '') {
            [$deadlineText, $countdown] = $this->formatDeadlineInfo($order['payment_deadline'] ?? null);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'sell_txid_prompt', [$deadlineText, $countdown]),
            ]);
        }
        $this->orderService->recordTxid($orderId, $txid);
        $this->stateService->clearState((int) $user['id']);
        $this->notifyAdmins(sprintf('🔁 Sell order #%d TXID submitted: %s', $orderId, $txid), $orderId);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($user['language'] ?? 'en', 'sell_txid_received'),
        ]);
    }
    private function handleAdminResponse(array $user, int $chatId, string $text, array $data): ServerResponse
    {
        $messageId = (int) ($data['admin_message_id'] ?? 0);
        if ($messageId <= 0) {
            $this->stateService->clearState((int) $user['id']);
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($user['language'] ?? 'en', 'unknown_command'),
            ]);
        }
        $stmt = $this->pdo->prepare(
            'SELECT order_id FROM admin_messages WHERE id = ?'
        );
        $stmt->execute([$messageId]);
        $adminMessage = $stmt->fetch(PDO::FETCH_ASSOC);
        $orderId = $adminMessage['order_id'] ?? null;

        $stmt = $this->pdo->prepare(
            'UPDATE admin_messages SET user_response = ?, status = ?, responded_at = NOW() WHERE id = ?'
        );
        $stmt->execute([$text, 'answered', $messageId]);
        $this->stateService->clearState((int) $user['id']);
        $this->notifyAdmins(sprintf('💬 User #%d replied: %s', $user['id'], $text), $orderId ? (int) $orderId : null);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($user['language'] ?? 'en', 'admin_response_received'),
        ]);
    }

    private function notifyAdmins(string $text, ?int $orderId = null): void
    {
        $adminIds = Config::get()->getValue('admin_ids', []);
        if ($orderId) {
            $text .= sprintf(
                "\n\nCommands:\nðŸ”ª `/adminorders view %d` \nðŸ”ª `/adminorders approve %d` \nðŸ”ª `/adminorders reject %d <reason>` \nðŸ”ª `/adminorders ask %d <question>` ",
                $orderId,
                $orderId,
                $orderId,
                $orderId
            );
        }
        foreach ($adminIds as $adminId) {
            if (!is_numeric($adminId)) {
                continue;
            }
            Request::sendMessage([
                'chat_id' => (int) $adminId,
                'text'    => $text,
            ]);
        }
    }
/**
     * @param array<int,array{id:int,telegram_id:string,language:string}> $orders
     */
    private function notifyExpiredOrders(array $orders): void
    {
        foreach ($orders as $order) {
            if (empty($order['telegram_id'])) {
                continue;
            }
            [$deadlineText] = $this->formatDeadlineInfo($order['payment_deadline'] ?? null);
            Request::sendMessage([
                'chat_id' => $order['telegram_id'],
                'text'    => Localization::t(
                    $order['language'] ?? 'en',
                    'order_cancelled_due_timeout',
                    [$order['id'], $deadlineText]
                ),
            ]);
        }
    }

    private function getActivePaymentOrder(array $user): ?array
    {
        return $this->orderService->getLatestAwaitingProofOrder((int) $user['id']);
    }

    private function remindPaymentProof(array $user, int $chatId): ServerResponse
    {
        $lang = $user['language'] ?? 'en';
        $order = $this->getActivePaymentOrder($user);
        if (!$order) {
            return Request::sendMessage([
                'chat_id' => $chatId,
                'text'    => Localization::t($lang, 'payment_proof_invalid'),
            ]);
        }
        [$deadlineText, $countdown] = $this->formatDeadlineInfo($order['payment_deadline'] ?? null);
        return Request::sendMessage([
            'chat_id' => $chatId,
            'text'    => Localization::t($user['language'] ?? 'en', 'payment_proof_text_warning', [$deadlineText, $countdown]),
        ]);
    }

    private function maybeExpireOrders(bool $skip): void
    {
        if ($skip) {
            return;
        }
        $expired = $this->orderService->expireStaleOrders();
        if ($expired) {
            $this->notifyExpiredOrders($expired);
        }
    }

    /**
     * @return array{0:string,1:string}
     */
    private function formatDeadlineInfo(?string $deadline): array
    {
        if (!$deadline) {
            return ['N/A', '0m'];
        }
        $deadlineDt = new \DateTimeImmutable($deadline);
        $now = new \DateTimeImmutable('now');
        $formatted = $deadlineDt->format('Y-m-d H:i');
        $seconds = $deadlineDt->getTimestamp() - $now->getTimestamp();
        if ($seconds <= 0) {
            return [$formatted, '0m'];
        }
        $minutes = intdiv($seconds, 60);
        $hours = intdiv($minutes, 60);
        $mins = $minutes % 60;
        $parts = [];
        if ($hours > 0) {
            $parts[] = $hours . 'h';
        }
        if ($mins > 0) {
            $parts[] = $mins . 'm';
        }
        if ($hours === 0 && $mins === 0) {
            $parts[] = ($seconds % 60) . 's';
        }
        return [$formatted, implode(' ', $parts) ?: '0m'];
    }

    private function normalizeUserText(string $text): string
    {
        $text = mb_strtolower(trim($text));
        $text = preg_replace('/[^\p{L}\p{N}\/& ]+/u', '', $text);
        $text = preg_replace('/\s+/', ' ', $text);
        return trim($text);
    }
}

