// app.js (versi optimasi anti-lag, semua fitur tetap ada)

const TelegramBot = require('node-telegram-bot-api');
const axios = require('axios');
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql2');

// ===========================
// 1) MySQL POOL (anti bottleneck)
// ===========================
const db = mysql.createPool({
  host: 'localhost',            // Ganti dengan host MySQL Anda
  user: 'infoo_barudak',         // Ganti dengan username MySQL Anda
  password: '@Makemoney99',     // Ganti dengan password MySQL Anda
  database: 'infoo_barudak',     // Ganti dengan nama database Anda
  waitForConnections: true,
  connectionLimit: 10,          // Sesuaikan dengan limit server (10–20 aman)
  queueLimit: 0
});

// ===========================
// 2) HTTP client dengan timeout
// ===========================
const http = axios.create({ timeout: 8000 }); // 8s timeout default untuk semua request eksternal

// ===========================
// Helper kecil
// ===========================
const sleep = (ms) => new Promise(res => setTimeout(res, ms));

// batasi paralel worker (untuk cek domain)
async function mapWithLimit(items, limit, worker) {
  const out = [];
  let i = 0;
  const runners = Array.from({ length: limit }, async () => {
    while (i < items.length) {
      const idx = i++;
      try {
        out[idx] = await worker(items[idx], idx);
      } catch (e) {
        out[idx] = undefined;
      }
    }
  });
  await Promise.all(runners);
  return out;
}

// kirim pesan panjang ke Telegram secara aman (chunk)
async function sendChunked(bot, chatId, text, opts = {}) {
  const MAX = 4000; // buffer dari 4096
  for (let i = 0; i < text.length; i += MAX) {
    await bot.sendMessage(chatId, text.slice(i, i + MAX), opts);
  }
}

// ===========================
// Init utama (tanpa db.connect())
// ===========================
(async function init() {
  try {
    const [rows] = await db.promise().query(
      'SELECT token, group_id, `interval`, max_domain FROM setting LIMIT 1'
    );
    if (!rows.length) throw new Error('Token not found in database.');

    const TOKEN = rows[0].token;
    const GROUP_CHAT_ID = rows[0].group_id;
    const RAW_INTERVAL = Number(rows[0].interval) || (3 * 60 * 1000); // kompatibel default lama (3 menit dalam ms)
    const MAX_DOMAIN = rows[0].max_domain || 10;

    // Normalisasi INTERVAL: deteksi menit atau milidetik
    // - Jika < 1e4 (10000 ms ≈ 10s), anggap itu menit -> convert
    // - Jika >= 1e4, anggap ms apa adanya
    const INTERVAL = RAW_INTERVAL < 10_000
      ? Math.max(60_000, RAW_INTERVAL * 60_000)   // menit -> ms, min 60s
      : Math.max(60_000, RAW_INTERVAL);          // ms, min 60s

    const bot = new TelegramBot(TOKEN, { polling: true });
    startBot(bot, GROUP_CHAT_ID, INTERVAL, MAX_DOMAIN);
  } catch (e) {
    console.error('Init error:', e);
    process.exit(1);
  }
})();

// ===========================
// Fungsi untuk memulai bot
// ===========================
const startBot = (bot, initialGroupId, initialInterval, initialMaxDomain) => {
  const app = express();
  const PORT = 1888;

  // state runtime yang bisa diubah tanpa restart lewat /update-settings & /cekid
  let TELEGRAM_GROUP_ID = initialGroupId;
  let CURRENT_INTERVAL = initialInterval;      // ms
  let MAX_DOMAIN = initialMaxDomain;

  app.use(bodyParser.json());
  app.use(express.static('public'));

  // Tolak akses file tertentu
  app.use((req, res, next) => {
    const forbiddenFiles = ['/user.txt', '/list.txt', '/otp.txt'];
    if (forbiddenFiles.includes(req.path)) {
      return res.status(403).send('Access denied');
    }
    next();
  });

  const YOURLS_API_URL = 'https://onlink.pro/yourls-api.php';
  const YOURLS_SIGNATURE = '2e6c7a68c7';

  // ================= CYBERNOTIF CONFIG =================
  const CYBERNOTIF_API_BASE = 'https://cybernotif.com/api/websites';
  const CYBERNOTIF_API_TOKEN = '4becca0edf69be06dd7db17b93d738a3'; // <== GANTI




  // ==================================
  // Cek status domain (pakai http + timeout)
  // ==================================
  const checkDomain = async (domain) => {
    const url = `https://check.notif.live/?domain=${domain}&json=true`;
    try {
      const { data } = await http.get(url);
      if (data && data[domain]) {
        return data[domain].blocked ? 'blocked' : 'safe';
      }
      return 'not_found';
    } catch (err) {
      console.error(`Error checking ${domain}:`, err.message);
      return 'error';
    }
  };

  // Lock untuk loop cek & rotasi agar tidak overlap
  let isChecking = false;
  let isRotating = false;

  async function safeUpdateShortlinksWithRotation() {
    if (isRotating) return;
    isRotating = true;
    try { await updateShortlinksWithRotation(); }
    catch (e) { console.error('Rotation error:', e.message); }
    finally { isRotating = false; }
  }

  // ===========================
  // Cek semua domain (PARALEL TERBATAS)
  // ===========================
  const checkAllDomains = async () => {
    console.log('🔍 Memulai pemeriksaan semua domain...');
    const [rows] = await db.promise().query(
      'SELECT domain_name, status, group_id, type FROM domains ORDER BY group_id, type ASC'
    );

    if (!rows.length) {
      console.log('❌ Tidak ada domain untuk diperiksa.');
      return;
    }

    const CONCURRENCY = 10; // angka aman
    await mapWithLimit(rows, CONCURRENCY, async (row) => {
      try {
        const status = await checkDomain(row.domain_name);
        const isBlocked = (status === 'blocked') ? 'blocked' : 'safe';

        if (row.status !== isBlocked) {
          await db.promise().query(
            'UPDATE domains SET status = ? WHERE domain_name = ?',
            [isBlocked, row.domain_name]
          );

          if (isBlocked === 'blocked') {
            console.log(`⚠️ Domain "${row.domain_name}" berubah status menjadi TERBLOKIR 🚫`);
          }
        }
      } catch (error) {
        console.error(`❌ Error checking domain "${row.domain_name}":`, error.message);
      }
    });

    // Ambil domain blocked + nama group
    const [blockedDomains] = await db.promise().query(
      `SELECT d.domain_name, g.name AS group_name
       FROM domains d
       LEFT JOIN groups g ON d.group_id = g.id
       WHERE d.status = 'blocked'
       ORDER BY g.name ASC`
    );

    if (blockedDomains.length > 0) {
      const blockedMessage = `🚨 Domain Terblokir:\n\n` +
        blockedDomains
          .map((row, index) => `${index + 1}. ${row.domain_name} --> ${row.group_name || 'Tidak diketahui'}`)
          .join('\n') +
        '\n\n♻️ Jika Domain yang Terblokir berada pada Shortlink yang tertera pada Group, Akan Automatis Berubah ke Domain yang Aman.\n\n';

      const groupNames = [...new Set(blockedDomains.map((d) => d.group_name))];
      const [backupCounts] = await db.promise().query(
        `SELECT g.name AS group_name, COUNT(d.id) as backup_count
         FROM domains d
         LEFT JOIN groups g ON d.group_id = g.id
         WHERE d.type = 'backup' AND d.status = 'safe' AND g.name IN (?)
         GROUP BY g.name`,
        [groupNames]
      );

      let warningMessage = '';
      for (const groupName of groupNames) {
        const backupGroup = backupCounts.find((x) => x.group_name === groupName);
        const backup_count = backupGroup ? backupGroup.backup_count : 0;
        if (backup_count === 0) {
          warningMessage += `⛔ Group ${groupName} : : tidak memiliki domain cadangan sama sekali.\nHarap tambahkan domain cadangan segera untuk menghindari gangguan layanan.\n\n`;
        } else if (backup_count === 1) {
          warningMessage += `⛔ Group ${groupName} ::: hanya memiliki (1) domain cadangan yang tersisa.\nHarap tambahkan domain cadangan untuk menghindari gangguan layanan.\n\n`;
        }
      }

      const finalMessage = `❗NOTIF TRUST NEGATIF❗\n\n${blockedMessage}${warningMessage}`;
      const safeMessage = finalMessage
        .replace(/_/g, '\\_').replace(/\*/g, '\\*')
        .replace(/\[/g, '\\[').replace(/`/g, '\\`');

      try {
        await sendChunked(bot, TELEGRAM_GROUP_ID, safeMessage, { parse_mode: 'Markdown' });
        console.log('✅ Pesan notifikasi dikirim ke Telegram.');
      } catch (error) {
        console.error('❌ Gagal mengirim pesan notifikasi ke Telegram:', error.message);
      }
    } else {
      console.log('✅ Tidak ada domain yang terblokir.');
    }

    // rotasi aman (no overlap)
    await safeUpdateShortlinksWithRotation();
  };

  // ===========================
  // Sinkron YOURLS <-> DB
  // ===========================
  const fetchYourlsData = async () => {
    try {
      console.log('📥 Mengambil data dari YOURLS...');
      const response = await http.get(YOURLS_API_URL, {
        params: {
          signature: YOURLS_SIGNATURE,
          action: 'stats',
          format: 'json'
        }
      });

      if (response.data && response.data.links) {
        console.log('✅ Data YOURLS berhasil diambil.');
        return response.data.links;
      }
      console.error('❌ Tidak ada data shortlink yang ditemukan.');
      return null;
    } catch (error) {
      console.error('❌ Error mengambil data dari YOURLS:', error.message);
      return null;
    }
  };

  const syncYourlsWithDatabase = async () => {
    try {
      const yourlsData = await fetchYourlsData();
      if (!yourlsData) {
        console.error('❌ Sinkronisasi gagal: Tidak ada data dari YOURLS.');
        return;
      }

      const [dbDomains] = await db.promise().query('SELECT id, shortlink, domain_name FROM domains');

      for (const domain of dbDomains) {
        const yourlsEntry = Object.values(yourlsData).find(
          (link) => link.shorturl.endsWith(domain.shortlink?.split('/').pop())
        );

        if (yourlsEntry) {
          await db.promise().query(
            'UPDATE domains SET domain_name = ? WHERE shortlink = ?',
            [yourlsEntry.url.replace(/https?:\/\//, '').replace(/\/$/, ''), domain.shortlink]
          );
          console.log(`✅ Sinkronisasi domain "${yourlsEntry.url}" berhasil untuk shortlink "${domain.shortlink}".`);
        } else {
          await db.promise().query(
            'UPDATE domains SET shortlink = NULL WHERE id = ?',
            [domain.id]
          );
          console.log(`⚠️ Shortlink "${domain.shortlink}" tidak ditemukan di YOURLS, dihapus dari database.`);
        }
      }
    } catch (error) {
      console.error('❌ Error saat sinkronisasi data:', error.message);
    }
  };

  // ===========================
  // Rotasi shortlink (dengan notifikasi chunked)
  // ===========================
  const updateGroupShortlinks = async (shortlink, newDomainId) => {
    try {
      console.log(`📊 Memulai update untuk shortlink "${shortlink}" dengan domain ID "${newDomainId}"`);
      const [existingRows] = await db.promise().query(
        'SELECT * FROM group_shortlinks WHERE shortlink = ?',
        [shortlink]
      );
      if (!existingRows.length) {
        console.log(`❌ Shortlink "${shortlink}" tidak ditemukan di tabel group_shortlinks.`);
        return;
      }
      const [result] = await db.promise().query(
        'UPDATE group_shortlinks SET domain_id = ? WHERE shortlink = ?',
        [newDomainId, shortlink]
      );
      if (result.affectedRows > 0) {
        console.log(`✅ Domain ID pada shortlink "${shortlink}" berhasil diperbarui menjadi ${newDomainId}.`);
      } else {
        console.log(`❌ Tidak ada baris yang diperbarui untuk shortlink "${shortlink}".`);
      }
    } catch (error) {
      console.error(`❌ Error memperbarui domain_id pada shortlink "${shortlink}":`, error.message);
    }
  };

  const ensureTrailingSlash = (url) => url.endsWith('/') ? url : `${url}/`;
  const ensureHttps = (url) => {
    let normalizedUrl = url.trim();
    if (!/^https?:\/\//i.test(normalizedUrl)) normalizedUrl = `https://${normalizedUrl}`;
    return normalizedUrl;
  };
  const removeProtocolAndTrailingSlash = (url) => {
    const urlWithoutProtocol = url.replace(/^https?:\/\//, '');
    return urlWithoutProtocol.endsWith('/') ? urlWithoutProtocol.slice(0, -1) : urlWithoutProtocol;
  };

  const updateYourls = async (shortlink, domain) => {
    try {
      const formattedDomain = ensureTrailingSlash(ensureHttps(domain));
      console.log(`📤 Mengirim perubahan ke YOURLS untuk shortlink "${shortlink}" dengan domain baru "${formattedDomain}"`);
      const response = await http.post(YOURLS_API_URL, null, {
        params: {
          signature: YOURLS_SIGNATURE,
          action: 'update',
          shorturl: shortlink.split('/').pop(),
          url: formattedDomain,
          format: 'json'
        }
      });
      if (response.data && response.data.status === 'success') {
        console.log(`✅ YOURLS berhasil diperbarui untuk shortlink "${shortlink}" dengan domain "${formattedDomain}".`);
      } else {
        console.error(`❌ Gagal memperbarui YOURLS untuk shortlink "${shortlink}". Response:`, response.data);
      }
    } catch (error) {
      console.error(`❌ Error mengirim ke YOURLS untuk shortlink "${shortlink}":`, error.message);
    }
  };

  const updateShortlinksWithRotation = async () => {
    console.log('🔄 Memulai rotasi shortlink...');
    const updatedShortlinksByGroup = {};
    const updatedDomains = {};
    const failedGroups = {};

    try {
      const [domains] = await db.promise().query(
        'SELECT ' +
        'd.id, d.domain_name, d.shortlink, d.status, d.group_id, d.type, ' +
        'd.cybernotif_website_id, d.cybernotif_last_host, ' +
        'g.name AS group_name ' +
        'FROM domains d ' +
        'LEFT JOIN groups g ON d.group_id = g.id ' +
        'ORDER BY d.group_id, d.type ASC'
      );

      const groups = [...new Set(domains.map(domain => domain.group_id))];

      for (const groupId of groups) {
        const groupDomains = domains.filter(domain => domain.group_id === groupId);
        if (!groupDomains.length) continue;

        const groupName = groupDomains[0]?.group_name || `Group ${groupId}`;

        const primaryDomains = groupDomains.filter(domain => domain.type === 'primary');
        const backupDomains = groupDomains.filter(domain => domain.type === 'backup' && domain.status === 'safe');

        // 🔎 cari siapa pun di group ini yang punya cybernotif_website_id
        let cybernotifOwner = groupDomains.find(d => d.cybernotif_website_id);

        for (const primary of primaryDomains) {
          if (primary.status !== 'blocked') continue;

          console.log(`⚠️ Domain "${primary.domain_name}" di Group "${groupName}" terblokir.`);

          const nextBackupDomain = backupDomains.shift();
          if (!nextBackupDomain) {
            console.log(`❌ Tidak ada domain backup yang tersisa untuk Group "${groupName}".`);
            if (!failedGroups[groupName]) failedGroups[groupName] = [];
            failedGroups[groupName].push(`${primary.shortlink} ➡ Tidak ada domain cadangan`);
            continue;
          }

          console.log(`🔄 Mengganti shortlink "${primary.shortlink}" dengan domain "${nextBackupDomain.domain_name}".`);

          // 1) tukar shortlink & type di tabel domains
          await db.promise().query(
            'UPDATE domains SET shortlink = ?, type = "primary" WHERE id = ?',
            [primary.shortlink, nextBackupDomain.id]
          );
          await db.promise().query(
            'UPDATE domains SET shortlink = NULL, type = "backup" WHERE id = ?',
            [primary.id]
          );

          // =====================
          // 2) Pindahin Cybernotif (FIX: biar PATCH + clear cache kepanggil)
          // =====================
          // sumbernya: pemilik cybernotif di group ini (kalau ada),
          // bukan cuma "primary" yang lagi diproses
          if (cybernotifOwner && cybernotifOwner.cybernotif_website_id) {
            const websiteId = cybernotifOwner.cybernotif_website_id;
            const newHost = removeProtocolAndTrailingSlash(nextBackupDomain.domain_name);

            // set ke domain yang baru jadi primary
            // NOTE: cybernotif_last_host sengaja dibuat NULL,
            // supaya updateCybernotifWebsiteByDomainId yang set + clear cache
            await db.promise().query(
              'UPDATE domains ' +
              'SET cybernotif_website_id = ?, cybernotif_last_host = NULL ' +
              'WHERE id = ?',
              [websiteId, nextBackupDomain.id]
            );

            // kosongkan di domain lama (kalau beda row)
            if (cybernotifOwner.id !== nextBackupDomain.id) {
              await db.promise().query(
                'UPDATE domains ' +
                'SET cybernotif_website_id = NULL, cybernotif_last_host = NULL ' +
                'WHERE id = ?',
                [cybernotifOwner.id]
              );
            }

            // update object lokal
            nextBackupDomain.cybernotif_website_id = websiteId;
            nextBackupDomain.cybernotif_last_host = null;

            if (cybernotifOwner.id !== nextBackupDomain.id) {
              cybernotifOwner.cybernotif_website_id = null;
              cybernotifOwner.cybernotif_last_host = null;
            }

            // setelah ini, owner resminya adalah primary baru
            cybernotifOwner = nextBackupDomain;

            // PATCH ke API Cybernotif + clear cache via helper
            try {
              console.log(
                `[Cybernotif] Rotasi -> update host website_id=${websiteId} ke "${newHost}" (via updateCybernotifWebsiteByDomainId)`
              );
              await updateCybernotifWebsiteByDomainId(nextBackupDomain.id, nextBackupDomain.domain_name);
            } catch (err) {
              console.error('[Cybernotif] Error saat rotasi:', err.response?.data || err.message);
            }
          }


          // 3) Update YOURLS
          await updateYourls(primary.shortlink, nextBackupDomain.domain_name);

          // 4) Update relasi di group_shortlinks
          await updateGroupShortlinks(primary.shortlink, nextBackupDomain.id);

          // 5) kumpulin log
          if (!updatedShortlinksByGroup[groupName]) updatedShortlinksByGroup[groupName] = [];
          updatedShortlinksByGroup[groupName].push(
            `${primary.shortlink} ➡ ${nextBackupDomain.domain_name}`
          );

          if (!updatedDomains[groupName]) updatedDomains[groupName] = [];
          updatedDomains[groupName].push(
            `${primary.domain_name} ➡ ${nextBackupDomain.domain_name}`
          );
        }
      }

      // Kirim report sukses
      if (Object.keys(updatedShortlinksByGroup).length > 0 || Object.keys(updatedDomains).length > 0) {
        let message = '✅ Pembaruan Shortlink dan Domain:\n\n';

        if (Object.keys(updatedShortlinksByGroup).length > 0) {
          message += '📌 Shortlink yang diperbarui:\n';
          for (const [groupName, shortlinks] of Object.entries(updatedShortlinksByGroup)) {
            message += `📍 ${groupName}:\n${shortlinks.map(link => `- ${link}`).join('\n')}\n\n`;
          }
        }

        if (Object.keys(updatedDomains).length > 0) {
          message += '📌 Domain yang diperbarui:\n';
          for (const [groupName, domains] of Object.entries(updatedDomains)) {
            message += `📍 ${groupName}:\n${domains.map(domain => `- ${domain}`).join('\n')}\n\n`;
          }
        }

        message += '🟢 Silahkan cek kembali untuk memastikan semua shortlink dan domain berfungsi dengan baik.';
        const safeMessage = message
          .replace(/_/g, '\\_').replace(/\*/g, '\\*')
          .replace(/\[/g, '\\[').replace(/`/g, '\\`');

        try {
          await sendChunked(bot, TELEGRAM_GROUP_ID, safeMessage, { parse_mode: 'Markdown' });
          console.log('✅ Pesan berhasil dikirim ke Telegram.');
        } catch (error) {
          console.error('❌ Gagal mengirim pesan ke Telegram:', error.message);
        }
      }

      // Kirim report gagal
      if (Object.keys(failedGroups).length > 0) {
        let failureMessage = '❗NOTIF TRUST NEGATIF❗\n\n🚨 Gagal Memperbarui Shortlink untuk Group Berikut:\n\n';
        for (const [groupName, shortlinks] of Object.entries(failedGroups)) {
          failureMessage += `📍 ${groupName}:\n${shortlinks.map(shortlink => `- ${shortlink}`).join('\n')}\n\n`;
        }
        failureMessage += '❌ Tidak ada domain backup yang tersisa untuk Group tersebut.\nHarap tambahkan domain cadangan untuk menghindari gangguan layanan.';

        const safeFailureMessage = failureMessage
          .replace(/_/g, '\\_').replace(/\*/g, '\\*')
          .replace(/\[/g, '\\[').replace(/`/g, '\\`');

        try {
          await sendChunked(bot, TELEGRAM_GROUP_ID, safeFailureMessage, { parse_mode: 'Markdown' });
          console.log('⚠️ Pesan kegagalan berhasil dikirim ke Telegram.');
        } catch (error) {
          console.error('❌ Gagal mengirim pesan kegagalan ke Telegram:', error.message);
        }
      }
      // Setelah semua rotasi selesai, sync lagi Cybernotif dari DB
      await syncCybernotifFromDb();
    } catch (error) {
      console.error('❌ Terjadi kesalahan saat memperbarui shortlink dan domain:', error.message);
    }
  };



  // ===========================
  // LOOP CEK TANPA OVERLAP (no setInterval)
  // ===========================
  async function runCheckLoop() {
    if (isChecking) return;  // hindari overlap
    isChecking = true;
    try {
      await checkAllDomains();
    } catch (e) {
      console.error('checkAllDomains error:', e);
    } finally {
      isChecking = false;
      const delay = Math.max(60_000, CURRENT_INTERVAL || 60_000);
      setTimeout(runCheckLoop, delay); // jalan ulang setelah selesai dengan interval terbaru
    }
  }
  runCheckLoop(); // start pertama

  // ================== FUNGSI UPDATE CYBERNOTIF (FINAL: SELALU CLEAR CACHE) ==================
  async function updateCybernotifWebsiteByDomainId(domainId, newHostRaw) {
    const newHost = removeProtocolAndTrailingSlash(newHostRaw.trim());
    console.log(`[Cybernotif] Req update untuk domainId=${domainId}, hostBaru="${newHost}"`);

    try {
      const [rows] = await db.promise().query(
        'SELECT cybernotif_website_id, cybernotif_last_host FROM domains WHERE id = ? LIMIT 1',
        [domainId]
      );

      if (!rows.length) {
        console.log(`[Cybernotif] Domain id=${domainId} tidak ditemukan (skip PATCH).`);
        return;
      }

      const { cybernotif_website_id, cybernotif_last_host } = rows[0];
      console.log(
        `[Cybernotif] Data DB -> website_id=${cybernotif_website_id}, last_host="${cybernotif_last_host}"`
      );

      if (!cybernotif_website_id) {
        console.log(`[Cybernotif] Domain id=${domainId} belum punya cybernotif_website_id (skip PATCH).`);
        return;
      }

      if (cybernotif_last_host === newHost) {
        console.log('[Cybernotif] Host baru sama dengan last_host (skip PATCH).');
        return;
      }

      console.log(
        `[Cybernotif] PATCH ke API: website_id=${cybernotif_website_id}, host_lama="${cybernotif_last_host}" -> host_baru="${newHost}"`
      );

      await http.patch(
        `${CYBERNOTIF_API_BASE}/${cybernotif_website_id}`,
        { scheme: 'https', host: newHost, path: '' },
        {
          headers: {
            Authorization: `Bearer ${CYBERNOTIF_API_TOKEN}`,
            'Content-Type': 'application/json'
          }
        }
      );

      await db.promise().query(
        'UPDATE domains SET cybernotif_last_host = ? WHERE id = ?',
        [newHost, domainId]
      );

      console.log(`[Cybernotif] BERHASIL update host untuk domainId=${domainId} -> ${newHost}`);
    } catch (err) {
      console.error('[Cybernotif] Error update:', err.response?.data || err.message);
    }
  }






  // ================== SYNC CYBERNOTIF DARI DB (BISA DIPANGGIL BERKALI-KALI) ==================
  async function syncCybernotifFromDb() {
    try {
      // Ambil semua group_id yang ada di tabel domains
      const [groupRows] = await db.promise().query(`
      SELECT DISTINCT group_id
      FROM domains
      WHERE group_id IS NOT NULL
    `);

      if (!groupRows.length) {
        console.log('[Cybernotif] Tidak ada group untuk disinkron (skip).');
        return;
      }

      for (const { group_id: groupId } of groupRows) {
        // Ambil semua domain di group ini
        const [domains] = await db.promise().query(`
        SELECT
          id,
          domain_name,
          status,
          type,
          cybernotif_website_id,
          cybernotif_last_host
        FROM domains
        WHERE group_id = ?
      `, [groupId]);

        if (!domains.length) continue;

        // Owner lama (siapa pun yang punya website_id)
        const owner = domains.find(d => d.cybernotif_website_id);
        if (!owner) {
          // group ini belum pernah dihubungkan ke Cybernotif, skip
          continue;
        }

        const websiteId = owner.cybernotif_website_id;

        // Cari target baru:
        // 1. primary + safe
        let target = domains.find(d => d.type === 'primary' && d.status === 'safe');

        // fallback: domain mana saja yang safe
        if (!target) target = domains.find(d => d.status === 'safe');

        if (!target) {
          console.log(
            `[Cybernotif] Group ${groupId}: tidak ada domain aman, website_id=${websiteId} dibiarkan di owner lama.`
          );
          continue;
        }

        // Kalau sudah nempel di domain yang tepat dan last_host sudah sama, nggak usah apa2
        const newHost = removeProtocolAndTrailingSlash(target.domain_name || '');
        if (owner.id === target.id && owner.cybernotif_last_host === newHost) {
          continue;
        }

        console.log(
          `[Cybernotif] Sync group ${groupId}: pindah website_id=${websiteId} ` +
          `dari domainId=${owner.id} -> domainId=${target.id} host="${newHost}"`
        );

        // Kosongkan semua di group ini
        await db.promise().query(
          'UPDATE domains SET cybernotif_website_id = NULL, cybernotif_last_host = NULL WHERE group_id = ?',
          [groupId]
        );

        // Set ke target baru
        await db.promise().query(
          'UPDATE domains SET cybernotif_website_id = ?, cybernotif_last_host = ? WHERE id = ?',
          [websiteId, newHost, target.id]
        );

        // PATCH ke Cybernotif
        // PATCH ke Cybernotif
        await http.patch(
          `${CYBERNOTIF_API_BASE}/${websiteId}`,
          { scheme: 'https', host: newHost, path: '' },
          {
            headers: {
              Authorization: `Bearer ${CYBERNOTIF_API_TOKEN}`,
              'Content-Type': 'application/json'
            }
          }
        );

        console.log(
          `[Cybernotif] BERHASIL sync group ${groupId} -> website_id=${websiteId} host="${newHost}"`
        );



        await sleep(300); // biar gak spam API

      }

      console.log('[Cybernotif] Sync dari DB selesai.');
    } catch (err) {
      console.error('[Cybernotif] Sync error:', err.response?.data || err.message);
    }
  }


  // panggil sekali saat start
  syncCybernotifFromDb();



  // ==================================================
  // ================== ROUTES & COMMANDS =============
  // ==================================================

  app.get('/get-groups', async (req, res) => {
    try {
      const [groups] = await db.promise().query('SELECT id, name FROM groups');
      res.json({ success: true, groups });
    } catch (error) {
      console.error('Error fetching groups:', error);
      res.status(500).json({ success: false, message: 'Terjadi kesalahan saat mengambil Group.' });
    }
  });

  app.get('/', (req, res) => {
    res.sendFile(__dirname + '/public/index.html');
  });

  const isUserAuthorized = async (userId) => {
    const [rows] = await db.promise().query('SELECT user_id FROM users WHERE user_id = ?', [userId]);
    return rows.length > 0;
  };

  const sendUnauthorizedMessage = (chatId) => {
    bot.sendMessage(chatId, '❌ Maaf, Anda tidak terdaftar sebagai tuan. Silakan hubungi admin @zal2311_aa untuk mendapatkan akses.');
  };

  const addUser = async (userId) => {
    const [existingUser] = await db.promise().query('SELECT user_id FROM users WHERE user_id = ?', [userId]);
    if (existingUser.length > 0) return false;
    const [result] = await db.promise().query('INSERT INTO users (user_id) VALUES (?)', [userId]);
    return result.affectedRows > 0;
  };

  const sendOTP = async (chatId) => {
    const otp = Math.floor(100000 + Math.random() * 900000);
    await bot.sendMessage(chatId, `Your OTP is: ${otp}`);
    await db.promise().query('INSERT INTO otps (otp) VALUES (?)', [otp]);
    return otp;
  };

  app.post('/login', async (req, res) => {
    const { userId } = req.body;
    if (await isUserAuthorized(userId)) {
      await sendOTP(userId);
      res.json({ success: true, message: 'OTP telah dikirim ke Telegram.' });
    } else {
      res.json({ success: false, message: 'User ID tidak ditemukan.' });
    }
  });

  app.post('/verify-otp', async (req, res) => {
    const { userId, otp } = req.body;
    const [rows] = await db.promise().query('SELECT otp FROM otps ORDER BY created_at DESC LIMIT 1');
    const savedOtp = rows.length ? rows[0].otp : null;
    if (await isUserAuthorized(userId) && String(savedOtp) === String(otp)) {
      await db.promise().query('DELETE FROM otps');
      res.json({ success: true, message: 'Login berhasil!' });
    } else {
      res.json({ success: false, message: 'OTP tidak valid atau tidak ditemukan.' });
    }
  });

  bot.onText(/\/start/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const startMessage = `
🌐 Selamat datang di *Bot Trust Negatif*! 🌐

Gunakan perintah berikut:
🔑 /help - untuk melihat semua perintah

🔑 /add - untuk menambahkan domain
(jika ingin menambahkan beberapa domain gunakan format berikut):
Contoh:
/add
example1.com
example2.com
example3.com

🔑 /addtogroup - Menambahkan domain ke Group
Contoh:
/addtogroup
<Nama-Group>
example1.com
example2.com
example3.com
   

🔑 /addshortlinks - Menambahkan shortlink Baru
Contoh:
/addshortlinks
Group 1
example/1-brand = example1.com

🔑 /updatelink - Memperbarui shortlink
Contoh:
 /updatelink
 Group 1
 example/1-brand = example1.com

🔑 /deleteshortlink - Menghapus shortlink
Contoh :
/deleteshortlink
Group 1
example/1-brand
example/2-brand

🔑 /addgroup - Menambah Group Baru
Contoh :
/addgroup
ExGroup 1
ExGroup 2
ExGroup 3

🔑 /deletegroup - Menghapus Group dan Semua data pada Group
Contoh :
/deletegroup
ExGroup 1
ExGroup 2
ExGroup 3

🔑 /editgroup - Mengubah Nama Group
Contoh :
/editgroup
ExGroup 1 = CiGroup 1
ExGroup 2 = CiGroup 2
ExGroup 3 = CiGroup 3

🔑 /showgroup - Menampilkan Semua Nama Group yang tersedia

🔑 /check - untuk memeriksa semua status domain

🔑 /checkfast <NAMA-DOMAIN> - untuk memeriksa beberapa status domain secara cepat
(Tanpa harus memasukkan domain ke dalam panel). Contoh format:
 /checkfast
 example1.com
 example2.com
 example3.com

🔑 /delete <NAMA-DOMAIN> - untuk menghapus domain
(jika ingin menghapus beberapa domain gunakan format berikut):
 /delete
 example1.com
 example2.com
 example3.com

🔑 /deleteblock - untuk menghapus semua domain yang terblokir

🔑 /show - untuk menampilkan daftar domain

🔑 /adduser <UserID> - untuk menambahkan pengguna (admin only)
(jika ingin menambahkan beberapa User gunakan format berikut):
/adduser
UserID1
UserID2
UserID3

➡️ *AKSES PANEL*: [Panel Link](https://barudak.infoo.one/)
`;
    await sendChunked(bot, msg.chat.id, startMessage, { parse_mode: 'Markdown' });
  });

  bot.onText(/\/help/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const helpMessage = `
📜 *Daftar Perintah Bot:*

🔑 /start - Memulai bot
🔑 /add - untuk menambahkan domain
(jika ingin menambahkan beberapa domain gunakan format berikut):
Contoh:
 /add
 example1.com
 example2.com
 example3.com

🔑 /addtogroup - Menambahkan domain ke Group
Contoh:
 /addtogroup
 <Nama-Group>
 example1.com
 example2.com
   

🔑 /addshortlinks - Menambahkan shortlink Baru
Contoh:
 /addshortlinks
 Group 1
 example/1-brand = example1.com

🔑 /updatelink - Memperbarui shortlink
Contoh:
 /updatelink
 Group 1
 example/1-brand = example1.com

🔑 /deleteshortlink - Menghapus shortlink
Contoh :
 /deleteshortlink
 Group 1
 example/1-brand
 example/2-brand

🔑 /addgroup - Menambah Group Baru
Contoh :
 /addgroup
 ExGroup 1
 ExGroup 2
 ExGroup 3

🔑 /deletegroup - Menghapus Group dan Semua data pada Group
Contoh :
 /deletegroup
 ExGroup 1
 ExGroup 2
 ExGroup 3

🔑 /editgroup - Mengubah Nama Group
Contoh :
 /editgroup
 ExGroup 1 = CiGroup 1
 ExGroup 2 = CiGroup 2
 ExGroup 3 = CiGroup 3

🔑 /showgroup - Menampilkan Semua Nama Group yang tersedia

🔑 /check - untuk memeriksa semua status domain

🔑 /checkfast <NAMA-DOMAIN> - untuk memeriksa beberapa status domain secara cepat
(Tanpa harus memasukkan domain ke dalam panel). Contoh format:
 /checkfast
 example1.com
 example2.com
 example3.com

🔑 /delete <NAMA-DOMAIN> - untuk menghapus domain
(jika ingin menghapus beberapa domain gunakan format berikut):
 /delete
 example1.com
 example2.com
 example3.com

🔑 /deleteblock - untuk menghapus semua domain yang terblokir

🔑 /show - untuk menampilkan daftar domain

🔑 /adduser <UserID> - untuk menambahkan pengguna (admin only)
(jika ingin menambahkan beberapa User gunakan format berikut):
 /adduser
 UserID1
 UserID2
 UserID3

🔑 ➡️ *AKSES PANEL*: [Panel Link](https://barudak.infoo.one/)
`;
    await sendChunked(bot, msg.chat.id, helpMessage, { parse_mode: 'Markdown' });
  });

  // /adduser
  bot.onText(/\/adduser\s/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const userIdList = msg.text.split('\n').slice(1)
      .map(x => x.trim()).filter(Boolean);

    if (!userIdList.length) {
      return bot.sendMessage(msg.chat.id, '🚫 Tidak ada User ID yang valid untuk ditambahkan.');
    }

    let responseMessage = '📋 Hasil Penambahan User:\n\n';
    const results = [];

    for (const userId of userIdList) {
      try {
        if (await addUser(userId)) {
          results.push(`✅ Pengguna dengan ID *${userId}* berhasil ditambahkan.`);
        } else {
          results.push(`⚠️ Pengguna dengan ID *${userId}* sudah ada dalam daftar.`);
        }
      } catch (error) {
        console.error(`Error adding user: ${userId}`, error);
        results.push(`❌ Terjadi kesalahan saat menambahkan pengguna dengan ID *${userId}*.`);
      }
    }
    responseMessage += results.join('\n');
    await sendChunked(bot, msg.chat.id, responseMessage, { parse_mode: 'Markdown' });
  });

  // Update group_id setting via /cekid
  const updateGroupId = async (groupId) => {
    const [result] = await db.promise().query('UPDATE setting SET group_id = ? WHERE id = 1', [groupId]);
    if (result.affectedRows > 0) {
      TELEGRAM_GROUP_ID = groupId; // langsung update runtime juga
      return true;
    }
    return false;
  };

  bot.onText(/\/cekid/, async (msg) => {
    if (msg.chat.type !== 'group' && msg.chat.type !== 'supergroup') {
      return bot.sendMessage(msg.chat.id, '❌ Perintah ini hanya dapat digunakan di dalam Group.');
    }
    const groupId = msg.chat.id;
    const success = await updateGroupId(groupId);
    if (success) {
      bot.sendMessage(msg.chat.id, `✅ ID Group telah diperbarui menjadi *${groupId}*.`);
    } else {
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat memperbarui ID Group. Silakan coba lagi.');
    }
  });

  // addDomains / addDomain helpers (tetap sama)
  const addDomains = async (domains, groupId) => {
    const [existingDomainsResult] = await db.promise().query('SELECT COUNT(*) AS total_domains FROM domains');
    const totalDomains = existingDomainsResult[0].total_domains;
    if (totalDomains + domains.length > MAX_DOMAIN) {
      return { success: false, message: `Maaf, jumlah domain maksimal adalah ${MAX_DOMAIN}. Anda hanya dapat menambahkan ${MAX_DOMAIN - totalDomains} domain lagi.` };
    }
    const [existingDomainNames] = await db.promise().query('SELECT domain_name FROM domains');
    const existingDomainSet = new Set(existingDomainNames.map(row => row.domain_name));

    const addedDomains = [];
    const duplicateDomains = [];
    for (const domain of domains) {
      if (existingDomainSet.has(domain)) duplicateDomains.push(domain);
      else addedDomains.push(domain);
    }
    if (!addedDomains.length) {
      return { success: false, message: `Domain sudah tersedia: ${duplicateDomains.join(', ')}.` };
    }
    const values = addedDomains.map(domain => [domain, groupId]);
    await db.promise().query('INSERT INTO domains (domain_name, group_id) VALUES ?', [values]);

    return {
      success: true,
      message: `✅ Domain berhasil ditambahkan: ${addedDomains.join(', ')}. ${duplicateDomains.length ? `Domain duplikat: ${duplicateDomains.join(', ')}.` : ''}`
    };
  };

  const addDomain = async (domain, groupId) => {
    const [existingDomains] = await db.promise().query('SELECT COUNT(*) AS total_domains FROM domains');
    const totalDomains = existingDomains[0].total_domains;
    if (totalDomains >= MAX_DOMAIN) return { success: false, message: `Maaf, jumlah domain maksimal adalah ${MAX_DOMAIN}.` };

    const [existingDomain] = await db.promise().query('SELECT domain_name FROM domains WHERE domain_name = ?', [domain]);
    if (existingDomain.length > 0) return { success: false, message: `Domain *${domain}* sudah ada dalam daftar.` };

    const [result] = await db.promise().query('INSERT INTO domains (domain_name, group_id) VALUES (?, ?)', [domain, groupId]);
    return { success: result.affectedRows > 0, message: `✅ Domain *${domain}* telah ditambahkan.` };
  };

  function isValidDomain(domain) {
    const domainRegex = /^(?!:\/\/)([a-zA-Z0-9-_]+\.)+[a-zA-Z]{2,}$/;
    return domainRegex.test(domain.trim());
  }

  async function addDomainsBatch(domains) {
    try {
      const [existingRows] = await db.promise().query(
        'SELECT domain_name FROM domains WHERE domain_name IN (?)',
        [domains]
      );
      const existingDomains = existingRows.map(row => row.domain_name);
      const newDomains = domains.filter(domain => !existingDomains.includes(domain));

      if (newDomains.length > 0) {
        const values = newDomains.map(domain => [domain]);
        await db.promise().query('INSERT IGNORE INTO domains (domain_name) VALUES ?', [values]);
      }

      return {
        success: true,
        addedDomains: newDomains,
        duplicateDomains: existingDomains,
        message: `✅ ${newDomains.length} domain berhasil ditambahkan.\n⚠️ ${existingDomains.length} domain duplikat diabaikan.`,
      };
    } catch (error) {
      console.error('Error adding domains:', error.message);
      return {
        success: false,
        addedDomains: [],
        duplicateDomains: [],
        message: `❌ Terjadi kesalahan saat menambahkan domain: ${error.message}`,
      };
    }
  }

  // /addtogroup
  bot.onText(/\/addtogroup/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const lines = msg.text.split('\n');
    const groupName = lines[1]?.trim();
    let domains = lines.slice(2).map(line => line.trim()).filter(Boolean);

    if (!groupName || !domains.length) {
      return bot.sendMessage(
        msg.chat.id,
        '🚫 Format tidak valid. Contoh penggunaan:\n\n/addtogroup\nGroup 1\nexample1.com\nexample2.com\nexample3.com'
      );
    }

    domains = domains.map(domain => {
      try { return new URL(domain).hostname; }
      catch { return domain; }
    });

    try {
      const [groupRows] = await db.promise().query('SELECT id FROM groups WHERE name = ?', [groupName]);
      if (!groupRows.length) return bot.sendMessage(msg.chat.id, `🚫 Group dengan nama "${groupName}" tidak ditemukan.`);
      const groupId = groupRows[0].id;

      let responseMessage = `📋 Hasil Penambahan Domain ke Group "${groupName}":\n\n`;

      for (const domain of domains) {
        try {
          const [existingDomains] = await db.promise().query(
            'SELECT * FROM domains WHERE domain_name = ?',
            [domain]
          );
          if (existingDomains.length > 0) {
            const existingDomain = existingDomains[0];
            if (existingDomain.group_id === groupId) {
              responseMessage += `⚠️ Domain "${domain}" sudah ada di Group "${groupName}".\n`;
            } else if (existingDomain.group_id === null) {
              await db.promise().query(
                'UPDATE domains SET group_id = ? WHERE domain_name = ?',
                [groupId, domain]
              );
              responseMessage += `✅ Domain "${domain}" berhasil ditambahkan ke Group "${groupName}".\n`;
            } else {
              responseMessage += `❌ Domain "${domain}" sudah terhubung dengan Group lain.\n`;
            }
            continue;
          }

          await db.promise().query(
            'INSERT INTO domains (domain_name, group_id, status) VALUES (?, ?, ?)',
            [domain, groupId, 'safe']
          );
          responseMessage += `✅ Domain "${domain}" berhasil ditambahkan ke Group "${groupName}".\n`;
        } catch (error) {
          console.error(`Error adding domain "${domain}" to group "${groupName}":`, error);
          responseMessage += `❌ Gagal menambahkan domain "${domain}".\n`;
        }
      }

      await sendChunked(
        bot,
        msg.chat.id,
        responseMessage.trim(),
        { parse_mode: 'Markdown', disable_web_page_preview: true }
      );
    } catch (error) {
      console.error('Error in /addtogroup command:', error);
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat menambahkan domain ke Group.');
    }
  });

  // /add (batch)
  bot.onText(/\/add\s/, async (msg) => {
    const commandText = msg.text.trim();
    if (!commandText.startsWith('/add')) return;
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const rawDomains = commandText.replace('/add', '').trim()
      .split(/\n+/).map(d => d.replace(/^-?\s*/, '').trim())
      .filter(Boolean);

    const domains = rawDomains
      .map(d => d.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''))
      .filter(isValidDomain);

    if (!domains.length) {
      return bot.sendMessage(
        msg.chat.id,
        "🚫 Tidak ada domain yang valid. Harap masukkan domain dengan format yang benar (contoh: `example.com`).",
        { parse_mode: 'Markdown' }
      );
    }

    const { success, addedDomains, duplicateDomains, message } = await addDomainsBatch(domains);

    let responseMessage = `📋 Hasil Penambahan Domain:\n\n${message}`;
    if (addedDomains.length) responseMessage += `\n\n✅ Domain Berhasil Ditambahkan:\n${addedDomains.map(d => `- ${d}`).join('\n')}`;
    if (duplicateDomains.length) responseMessage += `\n\n⚠️ Domain Duplikat:\n${duplicateDomains.map(d => `- ${d}`).join('\n')}`;

    await sendChunked(bot, msg.chat.id, responseMessage, { parse_mode: 'Markdown' });
  });

  // /deleteblock (VERSI AMAN DENGAN CYBERNOTIF PROTECT)
  bot.onText(/^\/deleteblock$/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    try {
      // 1) Pastikan dulu rotasi & sync Cybernotif sudah jalan
      console.log('[/deleteblock] Paksa rotasi & sync Cybernotif dulu...');
      await safeUpdateShortlinksWithRotation();
      await syncCybernotifFromDb();

      // 2) Ambil semua domain blocked + info cybernotif
      const [blockedRows] = await db.promise().query(`
      SELECT id, domain_name, group_id, cybernotif_website_id
      FROM domains
      WHERE status = 'blocked'
      ORDER BY domain_name ASC
    `);

      // Ambil juga domain yang masih aman (seperti versi lama)
      const [safeDomains] = await db.promise().query(`
      SELECT d.domain_name, g.name AS group_name
      FROM domains d
      LEFT JOIN groups g ON d.group_id = g.id
      WHERE d.status != 'blocked'
      ORDER BY g.name, d.domain_name
    `);

      if (!blockedRows.length) {
        let responseMessage = '❌ Tidak ada domain yang terblokir untuk dihapus.\n\n';

        if (safeDomains.length) {
          responseMessage += `✅ Berikut adalah domain yang masih aman (tidak terblokir):\n`;
          const groupedDomains = safeDomains.reduce((acc, domain) => {
            const groupName = domain.group_name || 'Tidak dalam Group';
            if (!acc[groupName]) acc[groupName] = [];
            acc[groupName].push(domain.domain_name);
            return acc;
          }, {});

          for (const [groupName, domains] of Object.entries(groupedDomains)) {
            responseMessage += `\n${groupName}:\n`;
            responseMessage += domains.map(domain => `- ${domain}`).join('\n');
          }
        } else {
          responseMessage += `❌ Tidak ada domain yang masih aman.`;
        }

        await sendChunked(bot, msg.chat.id, responseMessage.trim());
        return;
      }

      // 3) Pisahkan mana yang boleh dihapus & mana yang dilindungi (karena masih pegang Cybernotif)
      const deletableIds = [];
      const deletableDomains = [];
      const protectedDomains = [];

      for (const row of blockedRows) {
        if (row.cybernotif_website_id) {
          // Masih pegang website_id -> JANGAN dihapus
          protectedDomains.push(row.domain_name);
        } else {
          deletableIds.push(row.id);
          deletableDomains.push(row.domain_name);
        }
      }

      // 4) Hapus hanya yang aman untuk dihapus (tidak pegang cybernotif_website_id)
      if (deletableIds.length) {
        await db.promise().query(
          'DELETE FROM domains WHERE id IN (?)',
          [deletableIds]
        );
      }

      // 5) Build response ke Telegram
      let responseMessage = '';

      if (deletableDomains.length) {
        responseMessage += `🗑️ Berikut adalah domain yang terblokir dan TELAH dihapus:\n`;
        responseMessage += deletableDomains.map(d => `- ${d}`).join('\n') + '\n\n';
      } else {
        responseMessage += `⚠️ Tidak ada domain terblokir yang bisa dihapus (semua masih terikat ke Cybernotif).\n\n`;
      }

      if (protectedDomains.length) {
        responseMessage += `🛑 *PERINGATAN*: Domain di bawah ini *TIDAK dihapus* karena masih memegang *cybernotif_website_id*.\n`;
        responseMessage += `Jika ingin menghapusnya, pindahkan dulu Website Cybernotif ke domain lain (atau update host-nya), baru jalankan /deleteblock lagi.\n\n`;
        responseMessage += protectedDomains.map(d => `- ${d}`).join('\n') + '\n\n';
      }

      if (safeDomains.length) {
        responseMessage += `✅ Berikut adalah domain yang masih aman (tidak terblokir):\n`;
        const groupedDomains = safeDomains.reduce((acc, domain) => {
          const groupName = domain.group_name || 'Tidak dalam Group';
          if (!acc[groupName]) acc[groupName] = [];
          acc[groupName].push(domain.domain_name);
          return acc;
        }, {});
        for (const [groupName, domains] of Object.entries(groupedDomains)) {
          responseMessage += `\n${groupName}:\n`;
          responseMessage += domains.map(domain => `- ${domain}`).join('\n');
        }
      } else {
        responseMessage += `❌ Tidak ada domain yang masih aman.`;
      }

      await sendChunked(bot, msg.chat.id, responseMessage.trim());
    } catch (error) {
      console.error('Error deleting blocked domains (safe mode):', error);
      bot.sendMessage(msg.chat.id, `❌ Terjadi kesalahan saat menghapus domain yang terblokir (mode aman).`);
    }
  });


  // /delete
  bot.onText(/^\/delete(\s|$)/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const domainList = msg.text.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
    if (!domainList.length) return bot.sendMessage(msg.chat.id, '🚫 Tidak ada domain yang valid untuk dihapus.');

    try {
      const [groups] = await db.promise().query(`
        SELECT g.name AS group_name, d.domain_name
        FROM groups g
        LEFT JOIN domains d ON g.id = d.group_id
      `);

      const groupMap = groups.reduce((map, row) => {
        if (!map[row.group_name]) map[row.group_name] = [];
        if (row.domain_name) map[row.group_name].push(row.domain_name);
        return map;
      }, {});

      let responseMessage = '📋 Hasil Penghapusan:\n\n';

      for (const [groupName, domains] of Object.entries(groupMap)) {
        const domainsToDelete = domainList.filter(d => domains.includes(d));
        if (domainsToDelete.length) {
          await db.promise().query('DELETE FROM domains WHERE domain_name IN (?)', [domainsToDelete]);
          responseMessage += `${groupName}:\n`;
          domainsToDelete.forEach(d => { responseMessage += `✅ ${d} berhasil dihapus.\n`; });
          responseMessage += '\n';
        } else {
          responseMessage += `${groupName}:\n❌ Tidak ada domain yang dihapus.\n\n`;
        }
      }

      await sendChunked(
        bot,
        msg.chat.id,
        responseMessage.trim(),
        { parse_mode: 'Markdown', disable_web_page_preview: true }
      );
    } catch (error) {
      console.error('Error processing /delete command:', error);
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat memproses penghapusan domain.');
    }
  });

  // /show
  bot.onText(/^\/show$/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    try {
      const [domainRows] = await db.promise().query(`
        SELECT g.name AS group_name, d.domain_name
        FROM domains d
        LEFT JOIN groups g ON d.group_id = g.id
        ORDER BY g.name, d.domain_name
      `);
      const [shortlinkRows] = await db.promise().query(`
        SELECT g.name AS group_name, gs.shortlink, d.domain_name
        FROM group_shortlinks gs
        LEFT JOIN groups g ON gs.group_id = g.id
        LEFT JOIN domains d ON gs.domain_id = d.id
        ORDER BY g.name, gs.shortlink
      `);

      const domainMap = domainRows.reduce((map, row) => {
        const groupName = row.group_name || 'Tidak dalam Group';
        if (!map[groupName]) map[groupName] = [];
        if (row.domain_name) map[groupName].push(row.domain_name);
        return map;
      }, {});

      const shortlinkMap = shortlinkRows.reduce((map, row) => {
        const groupName = row.group_name || 'Tidak dalam Group';
        if (!map[groupName]) map[groupName] = [];
        if (row.shortlink) {
          map[groupName].push({ shortlink: row.shortlink, domain: row.domain_name || 'Tidak ada domain' });
        }
        return map;
      }, {});

      let response = '📋 Daftar Berdasarkan Group:\n\n';
      response += '🌐 *Domain:*\n\n';
      for (const [groupName, domains] of Object.entries(domainMap)) {
        response += `*${groupName}*:\n`;
        response += domains.length ? domains.map(d => `- ${d}`).join('\n') + '\n\n' : '❌ Tidak ada domain yang ditambahkan.\n\n';
      }

      response += '🔗 *Shortlink:*\n\n';
      for (const [groupName, shortlinks] of Object.entries(shortlinkMap)) {
        response += `*${groupName}*:\n`;
        if (shortlinks.length) {
          const sortedShortlinks = shortlinks.sort((a, b) => {
            const numA = parseInt(a.shortlink.match(/\/(\d+)-/)?.[1] || 0);
            const numB = parseInt(b.shortlink.match(/\/(\d+)-/)?.[1] || 0);
            return numA - numB;
          });
          response += sortedShortlinks.map(info => `- ${info.shortlink} ➡ ${info.domain}`).join('\n') + '\n\n';
        } else {
          response += '❌ Tidak ada shortlink yang ditambahkan.\n\n';
        }
      }

      await sendChunked(bot, msg.chat.id, response.trim(), { parse_mode: 'Markdown' });
    } catch (error) {
      console.error('Error fetching data:', error);
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat mengambil data.');
    }
  });

  // /checkfast (flag supaya /check tidak jalan bersamaan)
  let isCheckFastActive = false;

  bot.onText(/\/checkfast/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);
    isCheckFastActive = true;

    const rawDomainList = msg.text.trim().split('\n').slice(1)
      .map(d => d.trim()).filter(d => d.includes('.'));
    const domainList = rawDomainList
      .map(d => d.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, ''))
      .filter(Boolean);

    if (!domainList.length) {
      isCheckFastActive = false;
      return bot.sendMessage(msg.chat.id, '🚫 Tidak ada domain yang valid untuk diperiksa.');
    }

    let responseMessage = '📋 Status Domain:\n\n';
    const blockedDomains = [];
    const safeDomains = [];

    // Cek cepat paralel (pakai Promise.allSettled langsung, jumlah biasanya sedikit)
    const results = await Promise.allSettled(domainList.map(d => checkDomain(d)));
    results.forEach((result, idx) => {
      const domain = domainList[idx];
      if (result.status === 'fulfilled') {
        const label = result.value === 'blocked' ? 'Terblokir 🚫' : 'Aman ✅';
        if (label.startsWith('Terblokir')) blockedDomains.push(domain);
        else safeDomains.push(domain);
      } else {
        responseMessage += `*${domain}*: Error: ${result.reason}\n`;
      }
    });

    if (blockedDomains.length) {
      responseMessage += '🚫 Domain Terblokir:\n' + blockedDomains.map(d => `- ${d}`).join('\n') + '\n\n';
    } else {
      responseMessage += '✅ Tidak ada domain yang terblokir.\n\n';
    }
    if (safeDomains.length) {
      responseMessage += '✅ Domain Aman:\n' + safeDomains.map(d => `- ${d}`).join('\n');
    } else {
      responseMessage += '🚫 Tidak ada domain aman.';
    }

    await sendChunked(bot, msg.chat.id, responseMessage);
    isCheckFastActive = false;
  });

  bot.onText(/\/check/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);
    if (isCheckFastActive) {
      return bot.sendMessage(msg.chat.id, '⚠️ Perintah /checkfast sedang dijalankan, harap tunggu hingga selesai.');
    }

    try {
      const [rows] = await db.promise().query(`
        SELECT 
          g.name AS group_name, 
          d.domain_name, 
          d.status AS domain_status, 
          gs.shortlink 
        FROM domains d
        LEFT JOIN groups g ON d.group_id = g.id
        LEFT JOIN group_shortlinks gs ON d.id = gs.domain_id
        ORDER BY g.name, d.domain_name
      `);

      if (!rows.length) return bot.sendMessage(msg.chat.id, '🚫 Tidak ada domain yang terdaftar.');

      const groupMap = rows.reduce((map, row) => {
        const groupName = row.group_name || 'Tidak dalam Group';
        if (!map[groupName]) map[groupName] = [];
        map[groupName].push({
          domain: row.domain_name,
          shortlink: row.shortlink || 'Tidak ada Shortlink(Stok)',
          status: row.domain_status
        });
        return map;
      }, {});

      let responseMessage = '';
      for (const [groupName, domains] of Object.entries(groupMap)) {
        responseMessage += `*${groupName}*:\n`;
        const blockedDomains = domains.filter(d => d.status === 'blocked');
        const safeDomains = domains.filter(d => d.status !== 'blocked');

        if (blockedDomains.length) {
          responseMessage += '🚫 **Domain Terblokir**:\n';
          blockedDomains.forEach(({ domain }) => { responseMessage += `- ${domain}\n`; });
          responseMessage += '\n';
        }

        if (safeDomains.length) {
          responseMessage += '✅ **Domain Aman**:\n';
          const withShort = safeDomains.filter(d => d.shortlink !== 'Tidak ada Shortlink(Stok)');
          const withoutShort = safeDomains.filter(d => d.shortlink === 'Tidak ada Shortlink(Stok)');

          withShort.forEach(({ shortlink, domain }) => { responseMessage += `- ${shortlink} ➡ ${domain}\n`; });
          withoutShort.forEach(({ shortlink, domain }) => { responseMessage += `- ${shortlink} ➡ ${domain}\n`; });
          responseMessage += '\n';
        } else {
          responseMessage += '✅ Tidak ada domain aman.\n\n';
        }
      }

      await sendChunked(
        bot,
        msg.chat.id,
        responseMessage.trim(),
        { parse_mode: 'Markdown', disable_web_page_preview: true }
      );
    } catch (error) {
      console.error('Error processing /check command:', error);
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat memproses status domain.');
    }
  });

  // API data untuk panel
  app.get('/get-domains', async (req, res) => {
    try {
      // ambil optional query ?groupId=123 atau ?groupId=all
      const { groupId } = req.query;

      let sql = `
      SELECT 
        d.id,
        d.domain_name,
        d.status,
        IFNULL(d.type, 'primary') AS type,
        d.group_id,
        g.name AS group_name
      FROM domains d
      LEFT JOIN groups g ON d.group_id = g.id
    `;
      const params = [];

      if (groupId && groupId !== 'all') {
        sql += ' WHERE d.group_id = ?';
        params.push(groupId);
      }

      sql += ' ORDER BY g.name, d.domain_name';

      const [rows] = await db.promise().query(sql, params);

      res.json({
        success: true,
        domains: rows
      });
    } catch (error) {
      console.error('Error fetching domains:', error);
      res.status(500).json({
        success: false,
        message: 'Terjadi kesalahan saat mengambil domain.'
      });
    }
  });

  app.get('/get-users', async (req, res) => {
    const [rows] = await db.promise().query('SELECT user_id FROM users');
    return res.json({ users: rows.map(row => row.user_id) });
  });

  // ================== SETTINGS API ===================

  // Ambil setting (row id=1)
  app.get('/get-settings', async (req, res) => {
    try {
      const [rows] = await db.promise().query(
        'SELECT id, token, group_id, `interval`, max_domain FROM setting LIMIT 1'
      );

      if (!rows.length) {
        return res.json({ success: false, message: 'Setting belum ada.' });
      }

      const row = rows[0];

      // normalisasi ke menit untuk dipakai FE (mendukung data lama yg ms)
      let intervalMinutes = Number(row.interval) || 3;
      if (intervalMinutes >= 10_000) {
        intervalMinutes = Math.round(intervalMinutes / 60_000);
      }

      res.json({
        success: true,
        setting: {
          ...row,
          interval_minutes: intervalMinutes
        }
      });
    } catch (err) {
      console.error('Error get-settings:', err);
      res.status(500).json({ success: false, message: 'Gagal mengambil setting.' });
    }
  });

  // Update setting
  app.post('/update-settings', async (req, res) => {
    try {
      let { token, group_id, interval, max_domain } = req.body;

      if (!token) return res.status(400).json({ success: false, message: 'Token wajib diisi.' });
      if (!group_id) return res.status(400).json({ success: false, message: 'Group ID wajib diisi.' });

      // interval yang dikirim dari FE dalam "menit"
      interval = Number(interval);
      if (!interval || interval <= 0) {
        return res.status(400).json({ success: false, message: 'Interval tidak valid.' });
      }
      const intervalMs = interval * 60_000; // simpan di DB dalam milidetik

      max_domain = Number(max_domain) || 0;
      if (max_domain <= 0) {
        return res.status(400).json({ success: false, message: 'Max domain tidak valid.' });
      }

      const [result] = await db.promise().query(
        'UPDATE setting SET token = ?, group_id = ?, `interval` = ?, max_domain = ? WHERE id = 1',
        [token, group_id, intervalMs, max_domain]
      );

      if (!result.affectedRows) {
        return res.json({ success: false, message: 'Tidak ada setting yang diperbarui.' });
      }

      // langsung update config runtime (tanpa restart)
      const safeIntervalMs = Math.max(60_000, intervalMs);
      CURRENT_INTERVAL = safeIntervalMs;
      MAX_DOMAIN = max_domain;
      TELEGRAM_GROUP_ID = group_id;

      res.json({
        success: true,
        message: 'Setting berhasil diperbarui. Interval / limit / group langsung dipakai di loop berikutnya.'
      });
    } catch (err) {
      console.error('Error update-settings:', err);
      res.status(500).json({ success: false, message: 'Gagal memperbarui setting.' });
    }
  });

  app.post('/add-domain', async (req, res) => {
    const { domains, groupId } = req.body;
    if (!domains || !Array.isArray(domains) || !domains.length) {
      return res.status(400).json({ success: false, message: 'Domains tidak boleh kosong.' });
    }
    if (!groupId) {
      return res.status(400).json({ success: false, message: 'Group ID tidak boleh kosong.' });
    }

    const processDomain = (domain) => domain.replace(/^https?:\/\/(www\.)?/, '').replace(/\/$/, '');
    const valDomain = (domain) => /^(?!:\/\/)([a-zA-Z0-9-_]+\.)+[a-zA-Z]{2,}$/.test(domain.trim()) && !/^\d{10}$/.test(domain.trim());

    const processed = domains.map(processDomain).filter(valDomain);
    if (!processed.length) return res.status(400).json({ success: false, message: 'Tidak ada domain yang valid untuk ditambahkan.' });

    const results = [];
    for (const d of processed) {
      const pd = processDomain(d);
      try {
        const { success, message } = await addDomain(pd, groupId);
        if (!success && message.includes('duplicate')) {
          results.push({ domain: pd, success: false, message: `Domain '${pd}' sudah ada.` });
        } else {
          results.push({ domain: pd, success, message });
        }
      } catch (error) {
        results.push({ domain: pd, success: false, message: `Terjadi kesalahan: ${error.message}` });
      }
    }

    res.json({
      success: results.every(r => r.success),
      message: results.every(r => r.success) ? 'Semua domain berhasil ditambahkan.' : 'Beberapa domain gagal ditambahkan.',
      results
    });
  });

  const createShortlink = async (shortlink, longUrl) => {
    try {
      const urlForYourls = ensureTrailingSlash(ensureHttps(longUrl));
      const params = {
        signature: YOURLS_SIGNATURE,
        action: 'shorturl',
        keyword: shortlink.split('/').pop(),
        url: urlForYourls,
        format: 'json'
      };
      const response = await http.get(YOURLS_API_URL, { params });
      if (response.data && response.data.status === 'success') {
        console.log(`Shortlink "${shortlink}" berhasil dibuat.`);
        return response.data.shorturl;
      }
      console.error('Gagal membuat shortlink di YOURLS:', response.data.message);
      throw new Error(response.data.message || 'Unknown error');
    } catch (error) {
      console.error('Error creating shortlink:', error.message);
      throw new Error('Terjadi kesalahan saat membuat shortlink.');
    }
  };

  const updateShortlink = async (shortlink, newLongUrl, groupId, groupName) => {
    try {
      console.log(`Updating shortlink: ${shortlink}, Target URL: ${newLongUrl}, Group ID: ${groupId}`);
      const domainName = new URL(newLongUrl).hostname;

      let domainId;

      const [existingDomain] = await db.promise().query(
        'SELECT id FROM domains WHERE shortlink = ? AND group_id = ?',
        [shortlink, groupId]
      );

      if (!existingDomain.length) {
        console.log(`Inserting new domain: ${domainName}`);
        const [insertResult] = await db.promise().query(
          'INSERT INTO domains (shortlink, domain_name, group_id, status, type) VALUES (?, ?, ?, "safe", "primary")',
          [shortlink, domainName, groupId]
        );
        domainId = insertResult.insertId;
      } else {
        domainId = existingDomain[0].id;
        console.log(`Updating domain: ${domainName} (id=${domainId})`);
        await db.promise().query(
          'UPDATE domains SET domain_name = ? WHERE id = ?',
          [domainName, domainId]
        );
      }

      const urlForYourls = ensureTrailingSlash(ensureHttps(newLongUrl));
      const params = {
        signature: YOURLS_SIGNATURE,
        action: 'update',
        shorturl: shortlink.split('/').pop(),
        url: urlForYourls,
        format: 'json'
      };
      const response = await http.get(YOURLS_API_URL, { params });

      if (response.data && response.data.status === 'success') {
        console.log(`Shortlink "${shortlink}" berhasil diperbarui di YOURLS. Host baru: ${domainName}`);

        // 🔗 sinkron ke Cybernotif pakai domainId
        try {
          console.log(`[Cybernotif] PANGGIL updateCybernotifWebsiteByDomainId(${domainId}, "${domainName}")`);
          await updateCybernotifWebsiteByDomainId(domainId, domainName);
        } catch (err) {
          console.error(`[Cybernotif] GAGAL sinkron dari updateShortlink untuk domainId=${domainId}:`, err.response?.data || err.message);
        }

        return response.data;
      }

      console.error(`YOURLS API Error: ${response.data.message}`);
      throw new Error(response.data.message || 'Unknown error');
    } catch (error) {
      console.error(`Error in updateShortlink: ${error.message}`);
      throw new Error(error.message || 'Terjadi kesalahan saat memperbarui shortlink.');
    }
  };


  const deleteShortlink = async (shortlink) => {
    try {
      const keyword = shortlink.split('/').pop();
      const params = {
        signature: YOURLS_SIGNATURE,
        action: 'delete',
        shorturl: keyword,
        format: 'json'
      };
      const response = await http.get(YOURLS_API_URL, { params });
      if (response.data && response.data.status === 'success') {
        console.log(`Shortlink "${shortlink}" berhasil dihapus dari YOURLS.`);
        return true;
      }
      console.error(`Gagal menghapus shortlink di YOURLS: ${response.data.message}`);
      return false;
    } catch (error) {
      console.error(`Error deleting shortlink "${shortlink}" from YOURLS:`, error.message);
      return false;
    }
  };

  // /addshortlinks
  bot.onText(/\/addshortlinks/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const lines = msg.text.split('\n');
    const groupName = lines[1]?.trim();
    const mappings = lines.slice(2).map(l => l.trim()).filter(Boolean);

    if (!groupName || !mappings.length) {
      return bot.sendMessage(
        msg.chat.id,
        '🚫 Format tidak valid. Contoh penggunaan:\n\n/addshortlinks\nGroup 1\nexample.com/1-kw = domain.com'
      );
    }

    try {
      const [groupRows] = await db.promise().query('SELECT id FROM groups WHERE name = ?', [groupName]);
      if (!groupRows.length) return bot.sendMessage(msg.chat.id, `🚫 Group dengan nama "${groupName}" tidak ditemukan.`);
      const groupId = groupRows[0].id;

      let responseMessage = `📋 Hasil Penambahan Shortlink ke Group "${groupName}":\n\n`;

      for (const mapping of mappings) {
        const [shortlink, rawLongUrl] = mapping.split('=').map(s => s.trim());
        const longUrl = ensureHttps(rawLongUrl);
        const customKeyword = shortlink.split('/').pop();
        const domainName = new URL(longUrl).hostname;

        if (!shortlink || !rawLongUrl) {
          responseMessage += `❌ Format tidak valid: ${mapping}\n`;
          continue;
        }

        try {
          const [existingShortlink] = await db.promise().query(
            'SELECT g.name AS group_name FROM group_shortlinks gs JOIN groups g ON gs.group_id = g.id WHERE gs.shortlink = ? AND gs.group_id != ?',
            [shortlink, groupId]
          );
          if (existingShortlink.length) {
            responseMessage += `⚠️ Shortlink "${shortlink}" sudah digunakan di Group "${existingShortlink[0].group_name}".\n`;
            continue;
          }

          const [existingDomain] = await db.promise().query(
            'SELECT id, shortlink FROM domains WHERE domain_name = ? AND group_id = ?',
            [domainName, groupId]
          );

          let domainId;
          if (existingDomain.length) {
            domainId = existingDomain[0].id;
            if (!existingDomain[0].shortlink) {
              await db.promise().query(
                'UPDATE domains SET shortlink = ?, type = "primary" WHERE id = ?',
                [shortlink, domainId]
              );
            }
          } else {
            const [insertDomainResult] = await db.promise().query(
              'INSERT INTO domains (domain_name, status, group_id, shortlink, type) VALUES (?, ?, ?, ?, ?)',
              [domainName, 'safe', groupId, shortlink, 'primary']
            );
            domainId = insertDomainResult.insertId;
          }

          await createShortlink(customKeyword, ensureTrailingSlash(longUrl));

          await db.promise().query(
            'INSERT INTO group_shortlinks (group_id, shortlink, domain_id) VALUES (?, ?, ?)',
            [groupId, shortlink, domainId]
          );

          responseMessage += `✅ Shortlink "${shortlink}" berhasil ditambahkan dengan domain "${domainName}".\n`;
        } catch (error) {
          console.error(`Error processing shortlink "${shortlink}":`, error.message);
          responseMessage += `❌ Gagal memproses shortlink "${shortlink}". Error: ${error.message}\n`;
        }
      }

      await sendChunked(bot, msg.chat.id, responseMessage.trim());
    } catch (error) {
      console.error('Error in /addshortlinks command:', error.message);
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat menambahkan shortlink.');
    }
  });

  // /updatelink (tetap seperti semula, tapi push cache full setiap selesai)
  bot.onText(/\/updatelink/, async (msg) => {
    // ✅ cek user dulu
    if (!await isUserAuthorized(msg.from.id)) {
      return sendUnauthorizedMessage(msg.chat.id);
    }

    const lines = msg.text.split('\n');
    const groupName = lines[1]?.trim();
    const linkUpdates = lines.slice(2).map(line => line.trim()).filter(Boolean);

    if (!groupName || !linkUpdates.length) {
      return bot.sendMessage(
        msg.chat.id,
        '🚫 Format tidak valid. Contoh penggunaan:\n\n/updatelink\nGroup 1\nexample.com/1-kw = newdomain.com'
      );
    }

    try {
      const [groupRows] = await db.promise().query(
        'SELECT id FROM groups WHERE name = ?',
        [groupName]
      );
      if (!groupRows.length) {
        return bot.sendMessage(
          msg.chat.id,
          `🚫 Group dengan nama "${groupName}" tidak ditemukan.`
        );
      }
      const groupId = groupRows[0].id;

      let responseMessage = `📋 Hasil Pembaruan Shortlink di Group "${groupName}":\n\n`;

      for (const update of linkUpdates) {
        const [shortlink, newLongUrlRaw] = update.split('=').map(s => s.trim());
        if (!shortlink || !newLongUrlRaw) {
          responseMessage += `❌ Gagal memperbarui shortlink "${shortlink}". Format tidak valid.\n`;
          continue;
        }

        const formattedNewLongUrl = ensureTrailingSlash(ensureHttps(newLongUrlRaw));

        try {
          const [existingShortlink] = await db.promise().query(
            'SELECT shortlink FROM group_shortlinks WHERE shortlink = ? AND group_id = ?',
            [shortlink, groupId]
          );

          if (!existingShortlink.length) {
            responseMessage += `❌ Shortlink "${shortlink}" tidak ditemukan dalam Group "${groupName}".\n`;
            continue;
          }

          // ⬇️ kirim groupName juga (tidak wajib, cuma buat log/keperluan lain)
          await updateShortlink(shortlink, formattedNewLongUrl, groupId, groupName);

          responseMessage += `✅ Shortlink "${shortlink}" berhasil diperbarui.\n`;
        } catch (error) {
          console.error(`Error updating shortlink "${shortlink}":`, error.message);
          responseMessage += `❌ Gagal memperbarui shortlink "${shortlink}". Error: ${error.message}\n`;
        }
      }

      await sendChunked(bot, msg.chat.id, responseMessage.trim());

    } catch (error) {
      console.error(`Error in /updatelink command: ${error.message}`);
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat memperbarui shortlink.');
    }
  });


  // /deleteshortlink
  bot.onText(/\/deleteshortlink/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const lines = msg.text.split('\n');
    const groupName = lines[1]?.trim();
    const shortlinks = lines.slice(2).map(line => line.trim()).filter(Boolean);

    if (!groupName || !shortlinks.length) {
      return bot.sendMessage(
        msg.chat.id,
        '🚫 Format tidak valid. Contoh penggunaan:\n\n/deleteshortlink\nGroup 1\nonlink.pro/1-example\nonlink.pro/2-example'
      );
    }

    try {
      const [groupRows] = await db.promise().query('SELECT id FROM groups WHERE name = ?', [groupName]);
      if (!groupRows.length) return bot.sendMessage(msg.chat.id, `🚫 Group dengan nama "${groupName}" tidak ditemukan.`);
      const groupId = groupRows[0].id;

      let responseMessage = `📋 Hasil Penghapusan Shortlink dari Group "${groupName}":\n\n`;

      for (const shortlink of shortlinks) {
        try {
          const [existingShortlink] = await db.promise().query(
            'SELECT shortlink FROM group_shortlinks WHERE shortlink = ? AND group_id = ?',
            [shortlink, groupId]
          );
          if (!existingShortlink.length) {
            responseMessage += `❌ Shortlink "${shortlink}" tidak ditemukan di Group "${groupName}".\n`;
            continue;
          }

          const yourlsDeleted = await deleteShortlink(shortlink);
          if (!yourlsDeleted) {
            responseMessage += `❌ Gagal menghapus shortlink "${shortlink}" dari YOURLS.\n`;
            continue;
          }

          await db.promise().query(
            'DELETE FROM group_shortlinks WHERE shortlink = ? AND group_id = ?',
            [shortlink, groupId]
          );
          await db.promise().query(
            'DELETE FROM domains WHERE shortlink = ? AND group_id = ?',
            [shortlink, groupId]
          );

          responseMessage += `✅ Shortlink "${shortlink}" berhasil dihapus dari database dan YOURLS.\n`;
        } catch (error) {
          console.error(`Error deleting shortlink "${shortlink}":`, error.message);
          responseMessage += `❌ Gagal menghapus shortlink "${shortlink}". Error: ${error.message}\n`;
        }
      }

      await sendChunked(bot, msg.chat.id, responseMessage.trim());
    } catch (error) {
      console.error('Error in /deleteshortlink command:', error.message);
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat menghapus shortlink.');
    }
  });

  // /addgroup
  bot.onText(/\/addgroup/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const groupNames = msg.text.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
    if (!groupNames.length) {
      return bot.sendMessage(
        msg.chat.id,
        '🚫 Tidak ada Group yang valid untuk ditambahkan. Contoh penggunaan:\n\n/addgroup\nGroup Baru 1\nGroup Baru 2\nGroup Baru 3'
      );
    }

    let responseMessage = '📋 Hasil Penambahan Group:\n\n';
    for (const groupName of groupNames) {
      try {
        const [existingGroup] = await db.promise().query('SELECT id FROM groups WHERE name = ?', [groupName]);
        if (existingGroup.length) {
          responseMessage += `⚠️ Group "${groupName}" sudah ada.\n`;
          continue;
        }
        const [result] = await db.promise().query('INSERT INTO groups (name) VALUES (?)', [groupName]);
        responseMessage += result.affectedRows ? `✅ Group "${groupName}" berhasil ditambahkan.\n`
          : `❌ Gagal menambahkan Group "${groupName}".\n`;
      } catch (error) {
        console.error(`Error adding group "${groupName}":`, error.message);
        responseMessage += `❌ Terjadi kesalahan saat menambahkan Group "${groupName}".\n`;
      }
    }

    await sendChunked(bot, msg.chat.id, responseMessage.trim());
  });

  // /showgroup
  bot.onText(/\/showgroup/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    try {
      const [groups] = await db.promise().query(`
        SELECT g.id, g.name,
          (SELECT COUNT(*) FROM domains d WHERE d.group_id = g.id AND d.type = 'primary' AND d.status = 'safe') AS primary_count,
          (SELECT COUNT(*) FROM domains d WHERE d.group_id = g.id AND d.type = 'backup'  AND d.status = 'safe') AS backup_count
        FROM groups g
        ORDER BY g.name ASC
      `);

      if (!groups.length) return bot.sendMessage(msg.chat.id, '🚫 Tidak ada Group yang terdaftar.');

      let responseMessage = '📋 *Daftar Group Terdaftar:*\n\n';
      groups.forEach(group => {
        responseMessage += `- *${group.name}*\n`;
        responseMessage += `  Domain yang dipakai: ${group.primary_count}\n`;
        responseMessage += `  Total stok domain: ${group.backup_count}\n\n`;
      });

      await sendChunked(
        bot,
        msg.chat.id,
        responseMessage.trim(),
        { parse_mode: 'Markdown', disable_web_page_preview: true }
      );
    } catch (error) {
      console.error('Error fetching group list:', error.message);
      bot.sendMessage(msg.chat.id, '❌ Terjadi kesalahan saat mengambil daftar Group.');
    }
  });

  // /editgroup
  bot.onText(/\/editgroup/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const lines = msg.text.split('\n').slice(1).map(l => l.trim()).filter(Boolean);
    if (!lines.length) {
      return bot.sendMessage(
        msg.chat.id,
        '🚫 Format tidak valid. Contoh penggunaan:\n\n/editgroup\nNama Group Lama = Nama Group Baru\nGroup Lama 2 = Group Baru 2'
      );
    }

    let responseMessage = '📋 Hasil Pembaruan Nama Group:\n\n';
    for (const line of lines) {
      const [oldName, newName] = line.split('=').map(s => s.trim());
      if (!oldName || !newName) {
        responseMessage += `❌ Format tidak valid: ${line}\n`;
        continue;
      }
      try {
        const [result] = await db.promise().query('UPDATE groups SET name = ? WHERE name = ?', [newName, oldName]);
        responseMessage += result.affectedRows
          ? `✅ Nama Group *${oldName}* berhasil diperbarui menjadi *${newName}*.\n`
          : `❌ Group dengan nama *${oldName}* tidak ditemukan.\n`;
      } catch (error) {
        console.error(`Error updating group name "${oldName}":`, error.message);
        responseMessage += `❌ Gagal memperbarui Group *${oldName}*. Error: ${error.message}\n`;
      }
    }
    await sendChunked(
      bot,
      msg.chat.id,
      responseMessage.trim(),
      { parse_mode: 'Markdown', disable_web_page_preview: true }
    );
  });

  // /deletegroup
  bot.onText(/\/deletegroup/, async (msg) => {
    if (!await isUserAuthorized(msg.from.id)) return sendUnauthorizedMessage(msg.chat.id);

    const groupNames = msg.text.split('\n').slice(1).map(x => x.trim()).filter(Boolean);
    if (!groupNames.length) {
      return bot.sendMessage(
        msg.chat.id,
        '🚫 Tidak ada Group yang valid untuk dihapus. Contoh penggunaan:\n\n/deletegroup\nGroup 1\nGroup 2\nGroup 3'
      );
    }

    let responseMessage = '📋 Hasil Penghapusan Group:\n\n';
    for (const groupName of groupNames) {
      try {
        const [groupRows] = await db.promise().query('SELECT id FROM groups WHERE name = ?', [groupName]);
        if (!groupRows.length) {
          responseMessage += `❌ Group "${groupName}" tidak ditemukan.\n`;
          continue;
        }
        const groupId = groupRows[0].id;

        await db.promise().query('DELETE FROM groups WHERE id = ?', [groupId]);
        await db.promise().query('UPDATE domains SET group_id = NULL WHERE group_id = ?', [groupId]);
        await db.promise().query('DELETE FROM group_shortlinks WHERE group_id = ?', [groupId]);

        responseMessage += `✅ Group "${groupName}" berhasil dihapus, dan nilai terkait di tabel domains telah diatur ke NULL.\n`;
      } catch (error) {
        console.error(`Error deleting group "${groupName}":`, error.message);
        responseMessage += `❌ Terjadi kesalahan saat menghapus Group "${groupName}".\n`;
      }
    }
    await sendChunked(bot, msg.chat.id, responseMessage.trim());
  });

  // API tambah/hapus user & domain
  app.post('/add-user', async (req, res) => {
    const { userId } = req.body;
    if (!userId || typeof userId !== 'string') {
      return res.status(400).json({ success: false, message: 'ID pengguna tidak valid.' });
    }
    try {
      const added = await addUser(userId);
      res.json({ success: !!added, message: added ? 'Pengguna berhasil ditambahkan.' : 'Pengguna sudah ada.' });
    } catch (error) {
      console.error('Error adding user:', error);
      res.status(500).json({ success: false, message: 'Terjadi kesalahan saat menambahkan pengguna.' });
    }
  });

  app.delete('/delete-user', async (req, res) => {
    const { userId } = req.body;
    try {
      const [result] = await db.promise().query('DELETE FROM users WHERE user_id = ?', [userId]);
      res.json({ success: result.affectedRows > 0, message: result.affectedRows ? 'Pengguna berhasil dihapus.' : 'Pengguna tidak ditemukan.' });
    } catch (error) {
      console.error('Error deleting user:', error);
      res.status(500).json({ success: false, message: 'Terjadi kesalahan saat menghapus pengguna.' });
    }
  });

  app.delete('/delete-domain', async (req, res) => {
    const { domain } = req.body;
    try {
      const [result] = await db.promise().query('DELETE FROM domains WHERE domain_name = ?', [domain]);
      res.json({ success: result.affectedRows > 0, message: result.affectedRows ? 'Domain berhasil dihapus.' : 'Domain tidak ditemukan.' });
    } catch (error) {
      console.error('Error deleting domain:', error);
      res.status(500).json({ success: false, message: 'Terjadi kesalahan saat menghapus domain.' });
    }
  });

  // Start HTTP server
  app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
  });

  console.log('Bot is running...');

  // Shutdown rapi (opsional)
  process.on('SIGINT', async () => {
    try { await db.end(); } catch { }
    process.exit(0);
  });
};
