<?php

/**
 * Reliable cart-to-cart pay approval (single request, idempotent, deadlock-safe panel writes).
 */
class PayApprovalService
{
    /**
     * @return array{ok:bool,alert:string,error?:string}
     */
    public static function approve(string $payHash): array
    {
        global $connection;

        @ignore_user_abort(true);
        @set_time_limit(0);

        $stmt = $connection->prepare('SELECT * FROM `pays` WHERE `hash_id` = ? LIMIT 1');
        $stmt->bind_param('s', $payHash);
        $stmt->execute();
        $payInfo = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        if (!$payInfo) {
            return ['ok' => false, 'alert' => 'سفارش یافت نشد'];
        }
        if ($payInfo['state'] === 'approved') {
            return ['ok' => false, 'alert' => '✅ قبلاً تأیید شده'];
        }

        self::normalizeLegacyQueueState($payHash);

        $existingOrder = getOrderByPayHash($payHash);
        if ($existingOrder && ($payInfo['type'] ?? '') !== 'RENEW_SCONFIG') {
            return self::resendExistingOrder($payHash, $payInfo, $existingOrder);
        }

        if (!claimPayProcessing($payHash, 120)) {
            $existingOrder = getOrderByPayHash($payHash);
            if ($existingOrder) {
                return self::resendExistingOrder($payHash, $payInfo, $existingOrder);
            }
            return ['ok' => false, 'alert' => '⏳ این سفارش در حال پردازش است. چند ثانیه بعد دوباره بزنید.'];
        }

        try {
            global $admin;
            if (($payInfo['type'] ?? '') === 'RENEW_SCONFIG') {
                $result = self::approveRenew($payHash, $payInfo);
            } else {
                $result = self::approvePurchase($payHash, $payInfo);
            }
            if (!$result['ok']) {
                releasePayProcessing($payHash);
            }
            return $result;
        } catch (Throwable $e) {
            global $admin;
            releasePayProcessing($payHash);
            if (!empty($admin)) {
                sendMessage("❌ خطای تأیید خرید:\n" . $e->getMessage(), null, null, $admin);
            }
            return ['ok' => false, 'alert' => '❌ خطای داخلی', 'error' => $e->getMessage()];
        }
    }

    private static function normalizeLegacyQueueState(string $payHash): void
    {
        global $connection;
        $stmt = $connection->prepare(
            "UPDATE `pays` SET `state` = 'have_sent' WHERE `hash_id` = ? AND `state` IN ('queued', 'processing')"
        );
        $stmt->bind_param('s', $payHash);
        $stmt->execute();
        $stmt->close();
    }

    /** @param array<string, mixed> $payInfo @param array<string, mixed> $existingOrder */
    private static function resendExistingOrder(string $payHash, array $payInfo, array $existingOrder): array
    {
        global $buttonValues, $mainValues;

        $uid = (int) $payInfo['user_id'];
        $remark = (string) $existingOrder['remark'];
        $fid = (int) $payInfo['plan_id'];

        $file_detail = self::loadPlan($fid);
        if (!$file_detail) {
            return ['ok' => false, 'alert' => '❌ پلن یافت نشد'];
        }

        $keyboard = json_encode(['inline_keyboard' => [[['text' => $buttonValues['back_to_main'], 'callback_data' => 'mainMenu']]]]);
        $server_id = (int) $existingOrder['server_id'];
        $serverType = (string) (self::loadServerConfig($server_id)['type'] ?? '');
        if (isUserApiPanelType($serverType)) {
            resendUserApiPanelOrderToCustomer($uid, $server_id, $remark, $existingOrder, $keyboard);
        } else {
            deliverPanelOrder($uid, $server_id, $remark, $keyboard);
        }

        markPayApproved($payHash);
        self::markAdminDone();
        sendMessage(
            str_replace(
                ['REMARK', 'VOLUME', 'DAYS'],
                [$remark, (int) $file_detail['volume'], (int) $file_detail['days']],
                $mainValues['sent_config_to_user']
            ),
            getMainKeys(),
            null,
            null,
            $uid
        );

        return ['ok' => true, 'alert' => (string) $mainValues['sending_config_to_user']];
    }

    /** @param array<string, mixed> $payInfo @return array{ok:bool,alert:string,error?:string} */
    private static function approveRenew(string $payHash, array $payInfo): array
    {
        global $mainValues;

        $uid = (int) $payInfo['user_id'];
        $fid = (int) $payInfo['plan_id'];
        $price = (int) $payInfo['price'];
        $file_detail = self::loadPlan($fid);
        if (!$file_detail) {
            return ['ok' => false, 'alert' => '❌ پلن یافت نشد'];
        }

        $days = (int) $file_detail['days'];
        $volume = (int) $file_detail['volume'];
        $server_id = (int) $file_detail['server_id'];
        $configInfo = json_decode((string) ($payInfo['description'] ?? ''), true) ?: [];
        $uuid = (string) ($configInfo['uuid'] ?? '');
        $remark = (string) ($configInfo['remark'] ?? '');
        $isUserApi = !empty($configInfo['marzban']) || !empty($configInfo['pasarguard']);
        $inbound_id = (int) $payInfo['volume'];
        $time = time();

        $response = withPanelWriteLock(function () use ($isUserApi, $server_id, $remark, $days, $volume, $inbound_id, $uuid) {
            if ($isUserApi) {
                return editUserApiPanelConfig($server_id, ['remark' => $remark, 'days' => $days, 'volume' => $volume]);
            }
            if ($inbound_id > 0) {
                return editClientTraffic($server_id, $inbound_id, $uuid, $volume, $days, 'renew');
            }
            return editInboundTraffic($server_id, $uuid, $volume, $days, 'renew');
        });

        if (is_null($response)) {
            return ['ok' => false, 'alert' => '🔻 مشکل فنی در اتصال به سرور', 'error' => 'connection failed'];
        }

        global $connection;
        $stmt = $connection->prepare('INSERT INTO `increase_order` VALUES (NULL, ?, ?, ?, ?, ?, ?)');
        $stmt->bind_param('iiisii', $uid, $server_id, $inbound_id, $remark, $price, $time);
        $stmt->execute();
        $stmt->close();

        sendMessage(
            str_replace(['REMARK', 'VOLUME', 'DAYS'], [$remark, $volume, $days], $mainValues['renewed_config_to_user']),
            getMainKeys(),
            null,
            null,
            $uid
        );
        sendMessage("✅سرویس $remark با موفقیت تمدید شد", null, null, $uid);

        markPayApproved($payHash);
        self::markAdminDone();

        return ['ok' => true, 'alert' => '✅ تمدید انجام شد'];
    }

    /** @param array<string, mixed> $payInfo @return array{ok:bool,alert:string,error?:string} */
    private static function approvePurchase(string $payHash, array $payInfo): array
    {
        global $botState, $mainValues, $admin, $botUrl, $buttonValues;

        $uid = (int) $payInfo['user_id'];
        $fid = (int) $payInfo['plan_id'];
        $price = (int) $payInfo['price'];
        $file_detail = self::loadPlan($fid);
        if (!$file_detail) {
            return ['ok' => false, 'alert' => '❌ پلن یافت نشد'];
        }

        $days = (int) $file_detail['days'];
        $volume = (int) $file_detail['volume'];
        $protocol = (string) $file_detail['protocol'];
        $server_id = (int) $file_detail['server_id'];
        $netType = (string) $file_detail['type'];
        $acount = (int) $file_detail['acount'];
        $inbound_id = (int) $file_detail['inbound_id'];
        $limitip = (int) $file_detail['limitip'];
        $rahgozar = (int) $file_detail['rahgozar'];
        $accountCount = ((int) ($payInfo['agent_count'] ?? 0)) !== 0 ? (int) $payInfo['agent_count'] : 1;
        $eachPrice = (int) ($price / $accountCount);
        $date = time();
        $expire_microdate = (int) floor(microtime(true) * 1000) + (864000 * $days * 100);
        $expire_date = $date + (86400 * $days);

        if ($acount === 0 && planHasInbounds($file_detail)) {
            return ['ok' => false, 'alert' => (string) $mainValues['out_of_connection_capacity']];
        }

        $serverInfo = self::loadServerInfo($server_id);
        $serverConfig = self::loadServerConfig($server_id);
        if (!$serverInfo || !$serverConfig) {
            return ['ok' => false, 'alert' => '❌ سرور یافت نشد'];
        }

        if (!planHasInbounds($file_detail) && (int) ($serverInfo['ucount'] ?? 0) <= 0) {
            return ['ok' => false, 'alert' => (string) $mainValues['out_of_server_capacity']];
        }

        $srv_remark = (string) ($serverInfo['remark'] ?? '');
        $serverType = (string) ($serverConfig['type'] ?? '');
        $portType = (string) ($serverConfig['port_type'] ?? '');
        $keyboard = json_encode(['inline_keyboard' => [[['text' => $buttonValues['back_to_main'], 'callback_data' => 'mainMenu']]]]);

        $remark = '';
        for ($i = 1; $i <= $accountCount; $i++) {
            $remark = self::buildRemark($payInfo, $srv_remark, $uid);

            if (planHasInbounds($file_detail) && isXuiPanel($server_id) && panelClientExistsForRemark($server_id, $remark)) {
                $resume = self::completeExistingPanelClient(
                    $payHash,
                    $payInfo,
                    $file_detail,
                    $uid,
                    $fid,
                    $server_id,
                    $inbound_id,
                    $remark,
                    $protocol,
                    $expire_date,
                    $eachPrice,
                    $date,
                    $rahgozar,
                    $keyboard
                );
                if ($resume['ok']) {
                    self::decrementCapacity($file_detail, $accountCount, $server_id, $fid);
                    self::notifyAfterPurchase($payInfo, $file_detail, $remark, $price, $uid);
                    markPayApproved($payHash);
                    self::markAdminDone();
                    sendMessage(
                        str_replace(['REMARK', 'VOLUME', 'DAYS'], [$remark, $volume, $days], $mainValues['sent_config_to_user']),
                        getMainKeys(),
                        null,
                        null,
                        $uid
                    );
                    return ['ok' => true, 'alert' => (string) $mainValues['sending_config_to_user']];
                }
            }
            $uniqid = generateRandomString(42, $protocol);
            $port = self::nextPort($portType);

            $response = withPanelWriteLock(function () use (
                $file_detail,
                $serverType,
                $server_id,
                $uniqid,
                $protocol,
                $port,
                $expire_microdate,
                $remark,
                $volume,
                $netType,
                $rahgozar,
                $fid,
                $inbound_id,
                $limitip,
                $days
            ) {
                if (!planHasInbounds($file_detail)) {
                    if (isUserApiPanelType($serverType)) {
                        $response = addUserApiPanelUser($server_id, $remark, $volume, $days, $fid);
                        if (!$response->success && ($response->msg ?? '') === 'User already exists') {
                            return $response;
                        }
                        return $response;
                    }
                    $response = addUser($server_id, $uniqid, $protocol, $port, $expire_microdate, $remark, $volume, $netType, 'none', $rahgozar, $fid);
                    if (!$response->success && (isPanelDuplicateEmailError($response->msg ?? '') || strstr($response->msg ?? '', 'Duplicate email'))) {
                        return $response;
                    }
                    if (!$response->success && strstr($response->msg ?? '', 'Port already exists')) {
                        $port = rand(1111, 65000);
                        $response = addUser($server_id, $uniqid, $protocol, $port, $expire_microdate, $remark, $volume, $netType, 'none', $rahgozar, $fid);
                    }
                    return $response;
                }
                return addInboundAccount($server_id, $uniqid, $inbound_id, $expire_microdate, $remark, $volume, $limitip, null, $fid);
            });

            if ($response === null || $response === 'inbound not Found') {
                return ['ok' => false, 'alert' => '❌ اتصال به سرور برقرار نیست', 'error' => 'connection failed'];
            }
            if (is_object($response) && !$response->success) {
                if (planHasInbounds($file_detail) && isXuiPanel($server_id) && panelClientExistsForRemark($server_id, $remark)) {
                    $resume = self::completeExistingPanelClient(
                        $payHash,
                        $payInfo,
                        $file_detail,
                        $uid,
                        $fid,
                        $server_id,
                        $inbound_id,
                        $remark,
                        $protocol,
                        $expire_date,
                        $eachPrice,
                        $date,
                        $rahgozar,
                        $keyboard
                    );
                    if ($resume['ok']) {
                        self::decrementCapacity($file_detail, $accountCount, $server_id, $fid);
                        self::notifyAfterPurchase($payInfo, $file_detail, $remark, $price, $uid);
                        markPayApproved($payHash);
                        self::markAdminDone();
                        sendMessage(
                            str_replace(['REMARK', 'VOLUME', 'DAYS'], [$remark, $volume, $days], $mainValues['sent_config_to_user']),
                            getMainKeys(),
                            null,
                            null,
                            $uid
                        );
                        return ['ok' => true, 'alert' => (string) $mainValues['sending_config_to_user']];
                    }
                }
                $err = (string) ($response->msg ?? 'panel error');
                sendMessage('❌ | 😮 وای خطا داد لطفا سریع به مدیر بگو ...', null, null, $admin);
                sendMessage('خطای سرور ' . ($serverInfo['title'] ?? '') . ":\n\n" . $err, null, null, $admin);
                return ['ok' => false, 'alert' => '❌ خطای پنل', 'error' => $err];
            }

            $remark = syncRemarkFromPanelResponse($response, $remark);

            if (planHasInbounds($file_detail) && isXuiPanel($server_id)) {
                $panelReady = self::ensurePanelClientReady($server_id, $fid, $inbound_id, $remark);
                if (!$panelReady['ok']) {
                    sendMessage('❌ | 😮 وای خطا داد لطفا سریع به مدیر بگو ...', null, null, $admin);
                    sendMessage(
                        'خطای سرور ' . ($serverInfo['title'] ?? '') . ":\n\n" . ($panelReady['error'] ?? 'panel not ready'),
                        null,
                        null,
                        $admin
                    );
                    return ['ok' => false, 'alert' => '❌ خطای پنل', 'error' => $panelReady['error'] ?? 'panel not ready'];
                }
            }

            if (isUserApiPanelType($serverType)) {
                $serverConfig = getServerInfoById($server_id);
                $delivery = SubscriptionDelivery::preparePurchaseDelivery($serverConfig, $serverType, $remark, $response, '', 0);
                self::persistOrder(
                    $payHash,
                    $uid,
                    $delivery['token'],
                    $fid,
                    $server_id,
                    $inbound_id,
                    $remark,
                    $delivery['uniqid'],
                    $protocol,
                    $expire_date,
                    $delivery['vray_link'],
                    $eachPrice,
                    $date,
                    $rahgozar,
                    (int) ($payInfo['agent_bought'] ?? 0)
                );
                deliverUserApiPanelPurchaseToCustomer(
                    $uid,
                    $server_id,
                    $serverType,
                    $remark,
                    $response,
                    $protocol,
                    $volume,
                    $days,
                    $keyboard
                );
            } else {
                self::persistOrder($payHash, $uid, '', $fid, $server_id, $inbound_id, $remark, $uniqid, $protocol, $expire_date, '[]', $eachPrice, $date, $rahgozar, (int) ($payInfo['agent_bought'] ?? 0));
                $delivery = deliverPanelOrder($uid, $server_id, $remark, $keyboard);
                self::updateOrderLinks($payHash, $delivery['token'], $delivery['uniqid'], $delivery['vray_link']);
            }
        }

        self::decrementCapacity($file_detail, $accountCount, $server_id, $fid);
        self::notifyAfterPurchase($payInfo, $file_detail, $remark, $price, $uid);
        markPayApproved($payHash);
        self::markAdminDone();
        sendMessage(
            str_replace(['REMARK', 'VOLUME', 'DAYS'], [$remark, $volume, $days], $mainValues['sent_config_to_user']),
            getMainKeys(),
            null,
            null,
            $uid
        );

        return ['ok' => true, 'alert' => (string) $mainValues['sending_config_to_user']];
    }

    /** @param array<string, mixed> $payInfo */
    /** @return array{ok:bool,error?:string} */
    private static function ensurePanelClientReady(int $server_id, int $fid, int $inbound_id, string $remark): array
    {
        $service = getXuiV3Service($server_id);
        if ($service->isClientReadyForDelivery($remark)) {
            return ['ok' => true];
        }

        $targetIds = syncPlanInboundIdsWithPanel($fid, $server_id);
        if (empty($targetIds) && $inbound_id > 0) {
            $targetIds = [$inbound_id];
        }
        if (!empty($targetIds)) {
            $service->attachMissingInboundsOnce($remark, $targetIds);
        }

        if ($service->isClientReadyForDelivery($remark)) {
            return ['ok' => true];
        }

        return [
            'ok' => false,
            'error' => 'کاربر روی پنل ساخته نشد یا هنوز آماده ارسال کانفیگ نیست (subId/لینک موجود نیست)',
        ];
    }

    /** @param array<string, mixed> $payInfo */
    private static function buildRemark(array $payInfo, string $srvRemark, int $uid): string
    {
        global $botState;
        if (($botState['remark'] ?? '') === 'manual') {
            return (string) ($payInfo['description'] ?? '');
        }
        return buildOrderRemarkFromPay($payInfo, $srvRemark, $uid);
    }

    private static function nextPort(string $portType): int
    {
        $savedinfo = explode('-', (string) file_get_contents(__DIR__ . '/../settings/temp.txt'));
        $port = ((int) ($savedinfo[0] ?? 0)) + 1;
        $last_num = ((int) ($savedinfo[1] ?? 0)) + 1;
        if ($portType === 'auto') {
            file_put_contents(__DIR__ . '/../settings/temp.txt', $port . '-' . $last_num);
            return $port;
        }
        return rand(1111, 65000);
    }

    /** @param array<string, mixed> $file_detail */
    private static function completeExistingPanelClient(
        string $payHash,
        array $payInfo,
        array $file_detail,
        int $uid,
        int $fid,
        int $server_id,
        int $inbound_id,
        string $remark,
        string $protocol,
        int $expire_date,
        int $eachPrice,
        int $date,
        int $rahgozar,
        string $keyboard
    ): array {
        if (getOrderByPayHash($payHash)) {
            return ['ok' => true];
        }

        $service = getXuiV3Service($server_id);
        $targetIds = syncPlanInboundIdsWithPanel($fid, $server_id);
        if (empty($targetIds) && $inbound_id > 0) {
            $targetIds = [$inbound_id];
        }
        if (!empty($targetIds)) {
            withPanelWriteLock(function () use ($service, $remark, $targetIds) {
                $service->attachMissingInboundsOnce($remark, $targetIds);
            });
        }

        if (!$service->isClientReadyForDelivery($remark)) {
            return ['ok' => false];
        }

        $delivery = deliverPanelOrder($uid, $server_id, $remark, $keyboard);
        self::persistOrder(
            $payHash,
            $uid,
            $delivery['token'],
            $fid,
            $server_id,
            $inbound_id,
            $remark,
            $delivery['uniqid'],
            $protocol,
            $expire_date,
            $delivery['vray_link'],
            $eachPrice,
            $date,
            $rahgozar,
            (int) ($payInfo['agent_bought'] ?? 0)
        );

        return ['ok' => true];
    }

    private static function persistOrder(
        string $payHash,
        int $uid,
        string $token,
        int $fid,
        int $server_id,
        int $inbound_id,
        string $remark,
        string $uniqid,
        string $protocol,
        int $expire_date,
        string $vray_link,
        int $eachPrice,
        int $date,
        int $rahgozar,
        int $agent_bought
    ): void {
        if (getOrderByPayHash($payHash)) {
            return;
        }
        global $connection;
        $stmt = $connection->prepare(
            "INSERT INTO `orders_list`
            (`userid`, `token`, `transid`, `fileid`, `server_id`, `inbound_id`, `remark`, `uuid`, `protocol`, `expire_date`, `link`, `amount`, `status`, `date`, `notif`, `rahgozar`, `agent_bought`)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, 0, ?, ?)"
        );
        $stmt->bind_param(
            'issiiisssisiiii',
            $uid,
            $token,
            $payHash,
            $fid,
            $server_id,
            $inbound_id,
            $remark,
            $uniqid,
            $protocol,
            $expire_date,
            $vray_link,
            $eachPrice,
            $date,
            $rahgozar,
            $agent_bought
        );
        $stmt->execute();
        $stmt->close();
    }

    private static function updateOrderLinks(string $payHash, string $token, string $uniqid, string $vray_link): void
    {
        global $connection;
        $stmt = $connection->prepare('UPDATE `orders_list` SET `token` = ?, `uuid` = ?, `link` = ? WHERE `transid` = ? LIMIT 1');
        $stmt->bind_param('ssss', $token, $uniqid, $vray_link, $payHash);
        $stmt->execute();
        $stmt->close();
    }

    private static function sendUserApiPanelDelivery(
        int $uid,
        int $server_id,
        string $serverType,
        string $protocol,
        string $remark,
        int $volume,
        int $days,
        string $subLink,
        string $uniqid,
        string $keyboard
    ): void {
        global $botState, $botUrl;

        if (!class_exists('QRcode', false)) {
            include __DIR__ . '/../phpqrcode/qrlib.php';
        }
        if (!defined('IMAGE_WIDTH')) {
            define('IMAGE_WIDTH', 540);
            define('IMAGE_HEIGHT', 540);
        }

        $link = $subLink;
        $acc_text = "
😍 سفارش جدید شما
📡 پروتکل: $protocol
🔮 نام سرویس: $remark
🔋حجم سرویس: $volume گیگ
⏰ مدت سرویس: $days روز
" . (shouldShowRawConfigLink($serverType, $server_id) ? "
💝 config : <code>$link</code>" : '');
        if (($botState['subLinkState'] ?? '') === 'on') {
            $acc_text .= "

🔋 Volume web: <code> $botUrl" . "search.php?id=" . $uniqid . "</code>

\n🌐 subscription : <code>$subLink</code>";
        }
        $file = RandomString() . '.png';
        QRcode::png($link, $file, 'L', 11, 0);
        addBorderImage($file);
        applyQrBackground($file);
        sendPhoto($botUrl . $file, $acc_text, $keyboard, 'HTML', $uid);
        if (file_exists($file)) {
            unlink($file);
        }
    }

    /** @param array<string, mixed> $file_detail */
    private static function decrementCapacity(array $file_detail, int $accountCount, int $server_id, int $fid): void
    {
        global $connection;
        if (!planHasInbounds($file_detail)) {
            $stmt = $connection->prepare('UPDATE `server_info` SET `ucount` = `ucount` - ? WHERE `id`=?');
            $stmt->bind_param('ii', $accountCount, $server_id);
            $stmt->execute();
            $stmt->close();
            return;
        }
        $stmt = $connection->prepare('UPDATE `server_plans` SET `acount` = `acount` - ? WHERE id=?');
        $stmt->bind_param('ii', $accountCount, $fid);
        $stmt->execute();
        $stmt->close();
    }

    /** @param array<string, mixed> $payInfo @param array<string, mixed> $file_detail */
    private static function notifyAfterPurchase(array $payInfo, array $file_detail, string $remark, int $price, int $uid): void
    {
        global $connection, $admin, $from_id, $mainValues;

        $stmt = $connection->prepare('SELECT * FROM `users` WHERE `userid`=?');
        $stmt->bind_param('i', $uid);
        $stmt->execute();
        $user_detail = $stmt->get_result()->fetch_assoc();
        $stmt->close();
        if (!$user_detail) {
            return;
        }

        if (!empty($user_detail['refered_by'])) {
            ReferralService::onReferralPurchase($uid);
        }

        if ($admin != $from_id) {
            $msg = str_replace(
                ['USER-ID', 'USERNAME', 'NAME', 'PRICE', 'REMARK', 'FILENAME'],
                [$uid, $user_detail['username'] ?? '', $user_detail['name'] ?? '', $price, $remark, $file_detail['title'] ?? ''],
                $mainValues['invite_buy_new_account']
            );
            sendMessage($msg, null, null, $admin);
        }
    }

    private static function markAdminDone(): void
    {
        global $markup;
        if (!is_array($markup) || $markup === []) {
            return;
        }
        unset($markup[count($markup) - 1]);
        $markup[] = [['text' => '✅', 'callback_data' => 'wizwizch']];
        editKeys(json_encode(['inline_keyboard' => array_values($markup)], 488));
    }

    /** @return array<string, mixed>|null */
    private static function loadPlan(int $fid): ?array
    {
        global $connection;
        $stmt = $connection->prepare('SELECT * FROM `server_plans` WHERE `id`=?');
        $stmt->bind_param('i', $fid);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc();
        $stmt->close();
        return $row ?: null;
    }

    /** @return array<string, mixed>|null */
    private static function loadServerInfo(int $server_id): ?array
    {
        global $connection;
        $stmt = $connection->prepare('SELECT * FROM `server_info` WHERE `id`=?');
        $stmt->bind_param('i', $server_id);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc();
        $stmt->close();
        return $row ?: null;
    }

    /** @return array<string, mixed>|null */
    private static function loadServerConfig(int $server_id): ?array
    {
        global $connection;
        $stmt = $connection->prepare('SELECT * FROM `server_config` WHERE `id`=?');
        $stmt->bind_param('i', $server_id);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc();
        $stmt->close();
        return $row ?: null;
    }
}
