<?php
declare(strict_types=1);

require_once __DIR__ . '/../app/bootstrap.php';
Auth::start();

function miniapp_safe_text(string $text): string
{
    $text = trim($text);
    if ($text === '') {
        throw new RuntimeException('متن پیام را وارد کنید.');
    }
    return h(mb_substr($text, 0, 3900));
}

function miniapp_bulk_targets(Repository $repo): array
{
    return array_values(array_filter($repo->broadcastRecipients(5000), fn($u) => !empty($u['telegram_id'])));
}


try {
    $in = request_json();
    $action = (string)($in['action'] ?? '');
    if ($action === '') json_response(['ok' => false, 'error' => 'action خالی است.'], 400);

    if ($action === 'auth') {
        if (!empty($in['init_data'])) {
            $user = Auth::loginWithTelegram($repo, $config, (string)$in['init_data']);
            json_response(['ok' => true, 'user' => $user]);
        }
        if (!empty($in['panel_key']) && !empty($in['telegram_id'])) {
            $user = Auth::loginWithPanelKey($repo, $config, (string)$in['panel_key'], (string)$in['telegram_id']);
            json_response(['ok' => true, 'user' => $user]);
        }
        json_response(['ok' => false, 'error' => 'اطلاعات ورود کامل نیست.'], 422);
    }
    if ($action === 'me') { $user = Auth::requireUser($repo); json_response(['ok' => true, 'user' => $user]); }
    if ($action === 'logout') { session_destroy(); json_response(['ok' => true]); }

    $user = Auth::requireUser($repo);
    $service = new BotService($repo, $security, $config);

    switch ($action) {
        case 'dashboard':
            json_response([
                'ok' => true,
                'user' => $user,
                'stats' => $repo->stats((int)$user['id'], (string)$user['role']),
                'plans' => $repo->listPlans(),
                'bots' => $repo->listBots((int)$user['id'], (string)$user['role'], 100),
                'users' => $user['role'] === 'admin' ? $repo->listUsers(100) : [],
                'account' => $repo->accountDetails((int)$user['id']),
                'joins' => $user['role'] === 'admin' ? $repo->listRequiredJoins(false) : [],
                'logs' => $user['role'] === 'admin' ? $repo->listAuditLogs(80) : [],
                'topups' => $user['role'] === 'admin' ? $repo->listTopupRequests(100) : [],
                'payment' => $repo->paymentSettings(),
                'settings' => [
                    'activity_log_chat_id' => $repo->setting('activity_log_chat_id', (string)($config['activity_log_chat_id'] ?? '')),
                    'topup_min_amount' => $repo->setting('topup_min_amount', '200000'),
                    'topup_max_amount' => $repo->setting('topup_max_amount', '50000000'),
                    'bot_min_balance' => $repo->setting('bot_min_balance', '200000'),
                ],
                'config' => [
                    'app_name' => $config['app_name'] ?? 'ربات ساز میرزا احسان نت',
                    'app_url' => $config['app_url'] ?? '',
                    'child_base' => 'ربات میرزا ساز احسان نت',
                ],
            ]);

        case 'account_details': json_response(['ok' => true, 'account' => $repo->accountDetails((int)$user['id']), 'payment' => $repo->paymentSettings()]);

        case 'add_admin':
            Auth::requireAdmin($repo);
            $telegramId = digits_only((string)($in['telegram_id'] ?? ''));
            if ($telegramId === '') throw new RuntimeException('آیدی عددی ادمین را وارد کنید.');
            $admin = $repo->makeAdmin($telegramId, (string)($in['level'] ?? 'manager'));
            $repo->audit((int)$user['id'], 'add_admin', ['telegram_id' => $telegramId]);
            activity_notify($repo, $config, "🛡 <b>ادمین اضافه شد</b>\nمجری: " . format_user_for_log($user) . "\nآیدی ادمین: <code>" . h($telegramId) . "</code>");
            json_response(['ok' => true, 'admin' => $admin]);

        case 'charge_user':
            Auth::requireAdmin($repo);
            $telegramId = digits_only((string)($in['telegram_id'] ?? ''));
            $amount = (int)($in['amount'] ?? 0);
            $description = trim((string)($in['description'] ?? 'شارژ دستی'));
            $u = $repo->chargeUser((int)$user['id'], $telegramId, $amount, $description);
            activity_notify($repo, $config, "💰 <b>تغییر موجودی حساب</b>\nمجری: " . format_user_for_log($user) . "\nکاربر: <code>" . h($telegramId) . "</code>\nمبلغ: <b>" . money_format_ir($amount) . "</b>\nتوضیح: " . h($description));
            json_response(['ok' => true, 'user' => $u]);

        case 'user_status':
            Auth::requireAdmin($repo);
            $telegramId = digits_only((string)($in['telegram_id'] ?? ''));
            $status = (string)($in['status'] ?? 'active');
            $u = $repo->updateUserStatusByTelegramId($telegramId, $status, (int)$user['id']);
            activity_notify($repo, $config, "👤 <b>وضعیت کاربر تغییر کرد</b>\nمجری: " . format_user_for_log($user) . "\nکاربر: <code>" . h($telegramId) . "</code>\nوضعیت: <b>" . h($status) . "</b>");
            try {
                if ($status === 'blocked') {
                    (new Telegram((string)$config['main_bot_token']))->sendMessage($telegramId, '⛔️ دسترسی شما به ربات توسط مدیریت مسدود شد.');
                } else {
                    (new Telegram((string)$config['main_bot_token']))->sendMessage($telegramId, '✅ دسترسی شما به ربات دوباره فعال شد.');
                }
            } catch (Throwable $e) {}
            json_response(['ok' => true, 'user' => $u]);

        case 'save_payment_settings':
            Auth::requireAdmin($repo);
            $repo->savePaymentSettings(
                trim((string)($in['holder'] ?? '')),
                trim((string)($in['card_number'] ?? '')),
                trim((string)($in['iban'] ?? '')),
                trim((string)($in['title'] ?? 'کارت اصلی')),
                max(0, (int)($in['topup_min_amount'] ?? 200000)),
                max(0, (int)($in['topup_max_amount'] ?? 50000000)),
                max(0, (int)($in['bot_min_balance'] ?? 200000))
            );
            $repo->audit((int)$user['id'], 'save_payment_settings');
            activity_notify($repo, $config, "💳 <b>تنظیمات افزایش موجودی تغییر کرد</b>\nمجری: " . format_user_for_log($user));
            json_response(['ok' => true, 'payment' => $repo->paymentSettings()]);

        case 'save_card':
            Auth::requireAdmin($repo);
            $repo->saveCard(trim((string)($in['holder'] ?? '')), trim((string)($in['card_number'] ?? '')), trim((string)($in['iban'] ?? '')), trim((string)($in['title'] ?? 'کارت اصلی')));
            $repo->audit((int)$user['id'], 'save_card');
            activity_notify($repo, $config, "💳 <b>شماره کارت پرداخت تغییر کرد</b>\nمجری: " . format_user_for_log($user));
            json_response(['ok' => true]);

        case 'topup_approve':
            Auth::requireAdmin($repo);
            $payment = $repo->approveTopup((int)($in['id'] ?? 0), (int)$user['id']);
            try { (new Telegram((string)$config['main_bot_token']))->sendMessage((string)$payment['telegram_id'], "✅ رسید واریزی شما تایید شد.\nمبلغ <b>" . money_format_ir((int)$payment['amount']) . "</b> به حساب کاربری شما اضافه شد."); } catch (Throwable $e) {}
            json_response(['ok' => true, 'payment' => $payment]);

        case 'topup_reject':
            Auth::requireAdmin($repo);
            $payment = $repo->rejectTopup((int)($in['id'] ?? 0), (int)$user['id'], trim((string)($in['reason'] ?? '')));
            try { (new Telegram((string)$config['main_bot_token']))->sendMessage((string)$payment['telegram_id'], "❌ رسید واریزی شما رد شد.\nدلیل: <b>" . h((string)$payment['reject_reason']) . "</b>"); } catch (Throwable $e) {}
            json_response(['ok' => true, 'payment' => $payment]);

        case 'create_plan':
            Auth::requireAdmin($repo);
            $plan = $repo->createPlan(trim((string)($in['title'] ?? 'پلن')), max(1, (int)($in['months'] ?? 1)), max(0, (int)($in['price'] ?? 0)), !empty($in['is_custom']));
            $repo->audit((int)$user['id'], 'create_plan', $plan);
            activity_notify($repo, $config, "📦 <b>پلن جدید ثبت شد</b>\nمجری: " . format_user_for_log($user) . "\nعنوان: <b>" . h((string)$plan['title']) . "</b>\nمدت: " . (int)$plan['months'] . " ماه\nقیمت: " . money_format_ir((int)$plan['price']));
            json_response(['ok' => true, 'plan' => $plan]);

        case 'update_plan':
            Auth::requireAdmin($repo);
            $plan = $repo->updatePlan(
                (int)($in['id'] ?? 0),
                trim((string)($in['title'] ?? '')),
                max(1, (int)($in['months'] ?? 1)),
                max(0, (int)($in['price'] ?? 0)),
                !empty($in['is_custom']),
                (string)($in['status'] ?? 'active')
            );
            $repo->audit((int)$user['id'], 'update_plan', $plan);
            activity_notify($repo, $config, "✏️ <b>پلن ویرایش شد</b>\nمجری: " . format_user_for_log($user) . "\nعنوان: <b>" . h((string)$plan['title']) . "</b>\nمدت: " . (int)$plan['months'] . " ماه\nقیمت: " . money_format_ir((int)$plan['price']) . "\nوضعیت: <b>" . h((string)$plan['status']) . "</b>");
            json_response(['ok' => true, 'plan' => $plan]);

        case 'delete_plan':
            Auth::requireAdmin($repo);
            $plan = $repo->deletePlan((int)($in['id'] ?? 0));
            $repo->audit((int)$user['id'], 'delete_plan', $plan);
            activity_notify($repo, $config, "🗑 <b>پلن حذف/غیرفعال شد</b>\nمجری: " . format_user_for_log($user) . "\nپلن: <b>" . h((string)($plan['title'] ?? '-')) . "</b> (#" . (int)($plan['id'] ?? 0) . ")");
            json_response(['ok' => true, 'plan' => $plan]);

        case 'register_reseller':
            Auth::requireAdmin($repo);
            $telegramId = digits_only((string)($in['telegram_id'] ?? ''));
            $name = trim((string)($in['display_name'] ?? 'نماینده'));
            $commission = (float)($in['commission_percent'] ?? 0);
            if ($telegramId === '') throw new RuntimeException('آیدی عددی نماینده را وارد کنید.');
            $reseller = $repo->makeReseller($telegramId, $name, $commission);
            $repo->audit((int)$user['id'], 'register_reseller', ['telegram_id' => $telegramId]);
            activity_notify($repo, $config, "🤝 <b>نماینده ثبت شد</b>\nمجری: " . format_user_for_log($user) . "\nنماینده: <b>" . h($name) . "</b> | <code>" . h($telegramId) . "</code>");
            json_response(['ok' => true, 'reseller' => $reseller]);

        case 'create_bot':
            @set_time_limit(0);
            @ignore_user_abort(true);
            $token = trim((string)($in['bot_token'] ?? ''));
            if (!Telegram::isValidTokenFormat($token)) throw new RuntimeException('توکن ربات معتبر نیست. توکن کامل را از BotFather کپی کنید.');
            $planId = !empty($in['plan_id']) ? (int)$in['plan_id'] : 0;

            if (($user['role'] ?? '') === 'customer') {
                if ($planId <= 0) throw new RuntimeException('لطفاً یکی از پلن‌های فعال را انتخاب کنید.');
                $plan = $repo->getPlan($planId);
                if (!$plan || ($plan['status'] ?? '') !== 'active') throw new RuntimeException('پلن انتخاب‌شده فعال نیست یا حذف شده است.');
                $ownerTelegramId = digits_only((string)($in['owner_telegram_id'] ?? ''));
                if ($ownerTelegramId === '') throw new RuntimeException('آیدی عددی مالک ربات را وارد کنید.');
                $price = (int)$plan['price'];
                $months = max(1, (int)$plan['months']);
                $fresh = $repo->findUserById((int)$user['id']) ?: $user;
                if ((int)$fresh['balance'] < $price) {
                    $need = max(0, $price - (int)$fresh['balance']);
                    json_response([
                        'ok' => false,
                        'error' => 'موجودی کافی نیست. مبلغ مورد نیاز برای این پلن: ' . money_format_ir($price) . ' | موجودی شما: ' . money_format_ir((int)$fresh['balance']) . ' | کسری: ' . money_format_ir($need),
                        'need_topup' => true,
                        'need_amount' => $need,
                    ], 402);
                }
                $debited = false;
                try {
                    if ($price > 0) {
                        $repo->debitUserBalance((int)$user['id'], $price, 'هزینه ساخت ربات با پلن ' . (string)$plan['title']);
                        $debited = true;
                    }
                    $bot = $service->registerChildBot((int)$user['id'], $ownerTelegramId, $token, $planId, $months, $price);
                } catch (Throwable $e) {
                    if ($debited && $price > 0) {
                        try { $repo->chargeUser((int)$user['id'], (string)$user['telegram_id'], $price, 'برگشت وجه ساخت ربات ناموفق'); } catch (Throwable $refundError) { app_log('bot create refund failed', ['user_id'=>$user['id'], 'amount'=>$price, 'error'=>$refundError->getMessage()]); }
                    }
                    throw $e;
                }
                activity_notify($repo, $config, "🤖 <b>ربات جدید از پنل کاربر ثبت شد</b>\nکاربر: " . format_user_for_log($user) . "\nمالک ربات: <code>" . h($ownerTelegramId) . "</code>\nپلن: <b>" . h((string)$plan['title']) . "</b>\nهزینه: " . money_format_ir($price) . "\nربات: @" . h((string)$bot['bot_username']) . " (#" . (int)$bot['id'] . ")");
                json_response(['ok' => true, 'bot' => $bot]);
            }

            Auth::requireAdminOrReseller($repo);
            $ownerTelegramId = digits_only((string)($in['owner_telegram_id'] ?? $user['telegram_id']));
            if ($ownerTelegramId === '') throw new RuntimeException('آیدی مالک ربات را وارد کنید.');
            $planId = $planId > 0 ? $planId : null;
            if ($planId !== null) {
                $plan = $repo->getPlan($planId);
                if (!$plan || ($plan['status'] ?? '') !== 'active') throw new RuntimeException('پلن انتخاب‌شده فعال نیست یا حذف شده است.');
            }
            $months = !empty($in['months']) ? (int)$in['months'] : null;
            $price = isset($in['price']) && $in['price'] !== '' ? (int)$in['price'] : null;
            $bot = $service->registerChildBot((int)$user['id'], $ownerTelegramId, $token, $planId, $months, $price);
            activity_notify($repo, $config, "🤖 <b>ربات جدید از پنل با سورس ربات میرزا ساز احسان نت ثبت شد</b>\nمجری: " . format_user_for_log($user) . "\nمالک: <code>" . h($ownerTelegramId) . "</code>\nربات: @" . h((string)$bot['bot_username']) . " (#" . (int)$bot['id'] . ")");
            json_response(['ok' => true, 'bot' => $bot]);

        case 'set_custom_subscription':
            Auth::requireAdmin($repo);
            $botId = (int)($in['bot_id'] ?? 0);
            $telegramId = digits_only((string)($in['telegram_id'] ?? ''));
            $months = max(1, (int)($in['months'] ?? 1));
            $expiresAt = trim((string)($in['expires_at'] ?? ''));
            $price = max(0, (int)($in['price'] ?? 0));
            if ($botId <= 0 || $telegramId === '') throw new RuntimeException('ربات و کاربر را درست وارد کنید.');
            $u = $repo->upsertUser(['telegram_id' => $telegramId], 'customer');
            $bot = $repo->setCustomSubscription($botId, (int)$u['id'], $months, $expiresAt ?: null, $price);
            $repo->audit((int)$user['id'], 'set_custom_subscription', ['bot_id' => $botId, 'telegram_id' => $telegramId]);
            activity_notify($repo, $config, "⏳ <b>اشتراک دلخواه ثبت شد</b>\nمجری: " . format_user_for_log($user) . "\nربات: #" . (int)$botId . "\nکاربر: <code>" . h($telegramId) . "</code>\nانقضا: <code>" . h((string)$bot['expires_at']) . "</code>");
            json_response(['ok' => true, 'bot' => $bot]);


        case 'delete_bot':
            Auth::requireAdmin($repo);
            $botId = (int)($in['bot_id'] ?? 0);
            if ($botId <= 0) throw new RuntimeException('شناسه ربات معتبر نیست.');
            $bot = $service->deleteChildBot((int)$user['id'], $botId);
            activity_notify($repo, $config, "🗑 <b>ربات حذف شد</b>
مجری: " . format_user_for_log($user) . "
ربات: @" . h((string)($bot['bot_username'] ?? '-')) . " (#" . $botId . ")");
            json_response(['ok' => true, 'bot' => $bot]);

        case 'bot_status':
            Auth::requireAdminOrReseller($repo);
            $botId = (int)($in['bot_id'] ?? 0);
            $status = (string)($in['status'] ?? 'active');
            if ($botId <= 0) throw new RuntimeException('شناسه ربات معتبر نیست.');
            if ($user['role'] !== 'admin') {
                $bot = $repo->botById($botId);
                $reseller = $repo->resellerByUserId((int)$user['id']);
                $allowed = $bot && ((int)$bot['owner_user_id'] === (int)$user['id'] || ($reseller && (int)($bot['reseller_id'] ?? 0) === (int)$reseller['id']));
                if (!$allowed) throw new RuntimeException('شما به این ربات دسترسی ندارید.');
            }
            $repo->updateBotStatus($botId, $status);
            $repo->audit((int)$user['id'], 'bot_status', ['bot_id' => $botId, 'status' => $status]);
            activity_notify($repo, $config, "🔁 <b>وضعیت ربات تغییر کرد</b>\nمجری: " . format_user_for_log($user) . "\nربات: #" . (int)$botId . "\nوضعیت: <b>" . h($status) . "</b>");
            json_response(['ok' => true]);


        case 'broadcast_message':
            Auth::requireAdmin($repo);
            @set_time_limit(0);
            $text = miniapp_safe_text((string)($in['text'] ?? ''));
            $tgMain = new Telegram((string)$config['main_bot_token']);
            $targets = miniapp_bulk_targets($repo);
            $sent = 0; $failed = 0;
            foreach ($targets as $target) {
                try { $tgMain->sendMessage((string)$target['telegram_id'], $text); $sent++; }
                catch (Throwable $e) { $failed++; app_log('broadcast message failed', ['telegram_id'=>$target['telegram_id'] ?? null, 'error'=>$e->getMessage()]); }
            }
            $repo->audit((int)$user['id'], 'broadcast_message', ['sent'=>$sent, 'failed'=>$failed]);
            activity_notify($repo, $config, "📢 <b>پیام همگانی ارسال شد</b>
مجری: " . format_user_for_log($user) . "
موفق: <b>" . $sent . "</b> | ناموفق: <b>" . $failed . "</b>");
            json_response(['ok'=>true, 'sent'=>$sent, 'failed'=>$failed]);

        case 'direct_message':
            Auth::requireAdmin($repo);
            $targetId = digits_only((string)($in['telegram_id'] ?? ''));
            if ($targetId === '') throw new RuntimeException('آیدی عددی کاربر را وارد کنید.');
            $text = miniapp_safe_text((string)($in['text'] ?? ''));
            (new Telegram((string)$config['main_bot_token']))->sendMessage($targetId, $text);
            $repo->audit((int)$user['id'], 'direct_message', ['telegram_id'=>$targetId]);
            activity_notify($repo, $config, "✉️ <b>پیام تکی ارسال شد</b>
مجری: " . format_user_for_log($user) . "
کاربر: <code>" . h($targetId) . "</code>");
            json_response(['ok'=>true]);

        case 'forward_broadcast':
            Auth::requireAdmin($repo);
            @set_time_limit(0);
            $fromChatId = trim((string)($in['from_chat_id'] ?? ''));
            $messageId = (int)($in['message_id'] ?? 0);
            if ($fromChatId === '' || $messageId <= 0) throw new RuntimeException('Chat ID مبدا و Message ID را کامل وارد کنید.');
            $tgMain = new Telegram((string)$config['main_bot_token']);
            $targets = miniapp_bulk_targets($repo);
            $sent = 0; $failed = 0;
            foreach ($targets as $target) {
                try { $tgMain->forwardMessage((string)$target['telegram_id'], $fromChatId, $messageId); $sent++; }
                catch (Throwable $e) { $failed++; app_log('forward broadcast failed', ['telegram_id'=>$target['telegram_id'] ?? null, 'error'=>$e->getMessage()]); }
            }
            $repo->audit((int)$user['id'], 'forward_broadcast', ['from_chat_id'=>$fromChatId, 'message_id'=>$messageId, 'sent'=>$sent, 'failed'=>$failed]);
            activity_notify($repo, $config, "↗️ <b>فوروارد همگانی انجام شد</b>
مجری: " . format_user_for_log($user) . "
موفق: <b>" . $sent . "</b> | ناموفق: <b>" . $failed . "</b>");
            json_response(['ok'=>true, 'sent'=>$sent, 'failed'=>$failed]);

        case 'pin_message':
            Auth::requireAdmin($repo);
            $targetChatId = trim((string)($in['chat_id'] ?? ''));
            $messageId = (int)($in['message_id'] ?? 0);
            $disableNotification = !empty($in['disable_notification']);
            if ($targetChatId === '' || $messageId <= 0) throw new RuntimeException('Chat ID و Message ID را کامل وارد کنید.');
            $res = (new Telegram((string)$config['main_bot_token']))->pinChatMessage($targetChatId, $messageId, $disableNotification);
            $repo->audit((int)$user['id'], 'pin_message', ['chat_id'=>$targetChatId, 'message_id'=>$messageId]);
            activity_notify($repo, $config, "📌 <b>پیام پین شد</b>
مجری: " . format_user_for_log($user) . "
چت: <code>" . h($targetChatId) . "</code>
پیام: <code>" . $messageId . "</code>");
            json_response(['ok'=>true, 'telegram'=>$res]);

        case 'save_activity_settings':
            Auth::requireAdmin($repo);
            $chatId = trim((string)($in['activity_log_chat_id'] ?? ''));
            $repo->setSetting('activity_log_chat_id', $chatId);
            $repo->audit((int)$user['id'], 'save_activity_settings', ['chat_id' => $chatId]);
            activity_notify($repo, $config, "📣 <b>گزارش فعالیت‌ها فعال/به‌روزرسانی شد</b>\nمجری: " . format_user_for_log($user) . "\nچت گزارش: <code>" . h($chatId ?: '-') . "</code>");
            json_response(['ok' => true]);

        case 'add_required_join':
            Auth::requireAdmin($repo);
            $join = $repo->saveRequiredJoin(trim((string)($in['title'] ?? '')), trim((string)($in['chat_id'] ?? '')), trim((string)($in['invite_url'] ?? '')), (string)($in['join_type'] ?? 'channel'));
            $repo->audit((int)$user['id'], 'add_required_join', $join);
            activity_notify($repo, $config, "🔒 <b>جوین اجباری اضافه شد</b>\nمجری: " . format_user_for_log($user) . "\nعنوان: <b>" . h((string)$join['title']) . "</b>\nChat ID: <code>" . h((string)$join['chat_id']) . "</code>");
            json_response(['ok' => true, 'join' => $join]);

        case 'required_join_status':
            Auth::requireAdmin($repo);
            $id = (int)($in['id'] ?? 0); $status = (string)($in['status'] ?? 'active'); if ($id <= 0) throw new RuntimeException('شناسه جوین اجباری معتبر نیست.');
            $repo->updateRequiredJoinStatus($id, $status); $repo->audit((int)$user['id'], 'required_join_status', ['id'=>$id,'status'=>$status]);
            activity_notify($repo, $config, "🔒 <b>وضعیت جوین اجباری تغییر کرد</b>\nمجری: " . format_user_for_log($user) . "\nشناسه: #" . $id . "\nوضعیت: <b>" . h($status) . "</b>");
            json_response(['ok' => true]);

        case 'delete_required_join':
            Auth::requireAdmin($repo);
            $id = (int)($in['id'] ?? 0); if ($id <= 0) throw new RuntimeException('شناسه جوین اجباری معتبر نیست.');
            $repo->deleteRequiredJoin($id); $repo->audit((int)$user['id'], 'delete_required_join', ['id'=>$id]);
            activity_notify($repo, $config, "🗑 <b>جوین اجباری حذف شد</b>\nمجری: " . format_user_for_log($user) . "\nشناسه: #" . $id);
            json_response(['ok' => true]);

        case 'rewebhook_main':
            Auth::requireAdmin($repo); $res = $service->registerMainWebhook(); activity_notify($repo, $config, "🔗 <b>Webhook ربات مادر دوباره ست شد</b>\nمجری: " . format_user_for_log($user)); json_response(['ok'=>true,'telegram'=>$res]);

        case 'support_ticket':
            $subject = trim((string)($in['subject'] ?? 'پشتیبانی')); $message = trim((string)($in['message'] ?? ''));
            if ($message === '') throw new RuntimeException('متن پیام را وارد کنید.');
            $repo->createTicket((int)$user['id'], $subject, $message); $repo->audit((int)$user['id'], 'support_ticket_panel', ['subject'=>$subject]);
            activity_notify($repo, $config, "🆘 <b>پیام پشتیبانی جدید از پنل</b>\nکاربر: " . format_user_for_log($user) . "\nموضوع: <b>" . h($subject) . "</b>\nمتن: " . h(mb_substr($message,0,500)));
            json_response(['ok' => true]);

        default: json_response(['ok' => false, 'error' => 'action نامعتبر است.'], 404);
    }
} catch (Throwable $e) {
    app_log('api error', ['error' => $e->getMessage()]);
    json_response(['ok' => false, 'error' => $e->getMessage()], 500);
}
