from __future__ import annotations

import html
import logging
import sqlite3
from datetime import datetime

from telebot.apihelper import ApiTelegramException

from src import keyboards, state
from src.config import BASE_DIR, settings
from src.repositories import repo
from src.services import media_service, system_service
from src.telegram_text import preview_html

logger = logging.getLogger(__name__)


def admin(callback) -> bool:
    """Periksa apakah pengguna yang menekan tombol adalah admin."""
    return callback.from_user.id in settings.admin_ids


def _is_owner(user_id: int) -> bool:
    """Owner adalah admin dengan ID terkecil (pertama di daftar ADMIN_IDS)."""
    return bool(settings.admin_ids) and user_id == min(settings.admin_ids)


def answer_callback(bot, callback, text: str | None = None, show_alert: bool = False) -> None:
    """Menjawab callback tanpa membuat handler gagal bila callback sudah kedaluwarsa."""
    try:
        bot.answer_callback_query(callback.id, text=text, show_alert=show_alert)
    except ApiTelegramException:
        logger.debug("Callback query tidak dapat dijawab", exc_info=True)


def edit_panel(
    bot,
    callback,
    text: str,
    reply_markup=None,
    parse_mode: str = "HTML",
):
    """
    Mengganti isi pesan panel yang sama.

    Pesan baru hanya dibuat sebagai fallback jika pesan lama memang tidak dapat diedit,
    misalnya terlalu lama, sudah dihapus, atau jenis medianya tidak mendukung edit.
    """
    message = callback.message

    try:
        if getattr(message, "content_type", "text") == "text":
            return bot.edit_message_text(
                text=text,
                chat_id=message.chat.id,
                message_id=message.message_id,
                parse_mode=parse_mode,
                reply_markup=reply_markup,
                disable_web_page_preview=True,
            )

        return bot.edit_message_caption(
            caption=text,
            chat_id=message.chat.id,
            message_id=message.message_id,
            parse_mode=parse_mode,
            reply_markup=reply_markup,
        )

    except ApiTelegramException as error:
        error_text = str(error).lower()

        # Tidak perlu melakukan apa pun jika tampilan memang sama.
        if "message is not modified" in error_text:
            return None

        logger.warning("Panel tidak dapat diedit, memakai fallback pesan baru: %s", error)

        return bot.send_message(
            message.chat.id,
            text,
            parse_mode=parse_mode,
            reply_markup=reply_markup,
            disable_web_page_preview=True,
        )


def show_home(bot, callback):
    state.clear(callback.message.chat.id)
    return edit_panel(
        bot,
        callback,
        "👑 <b>Panel Admin</b>",
        reply_markup=keyboards.admin_home(),
    )




def restore_cancel_destination(bot, callback):
    """Batalkan input dan kembali ke panel asal sesuai sesi aktif."""
    chat_id = callback.message.chat.id
    session = state.get(chat_id)

    if not session:
        return show_home(bot, callback)

    flow = session.get("flow")
    step = session.get("step")
    data = session.get("data") or {}
    state.clear(chat_id)

    if flow == "edit_media":
        return media_detail_panel(bot, callback, int(data["mid"]))

    if flow == "category":
        if step == "rename" and data.get("cid"):
            return category_detail_panel(bot, callback, int(data["cid"]), page=0)
        return categories_panel(bot, callback)

    if flow == "template_edit" and data.get("template_id"):
        template_id = int(data["template_id"])
        template = repo.template(template_id)
        if template:
            rows = [[
                ("✏️ Ubah Isi", f"tpl:edit:{template_id}"),
                ("✅ Jadikan Default", f"tpl:default:{template_id}"),
            ]]
            if template["is_system"]:
                rows.append([("♻️ Reset Caption Bawaan", f"tpl:reset:{template_id}")])
            else:
                rows.append([("🗑 Hapus", f"tpl:delete:{template_id}")])
            rows.append([("⬅️ Kembali", "adm:templates")])
            return edit_panel(
                bot, callback,
                f"📝 <b>{html.escape(template['name'])}</b>\n\n"
                f"Status: {'Default aktif' if template['is_default'] else 'Tidak aktif'}\n"
                f"Jenis: {'Bawaan sistem' if template['is_system'] else 'Buatan admin'}\n\n"
                f"{template['content']}",
                reply_markup=keyboards.kb(rows),
            )
        return templates_panel(bot, callback)

    if flow == "template":
        return templates_panel(bot, callback)

    if flow == "fsub_edit" and data.get("fsub_id"):
        force_sub_id = int(data["fsub_id"])
        channel = repo.one(
            "SELECT * FROM force_sub_channels WHERE id=?",
            (force_sub_id,),
        )
        if channel:
            markup = keyboards.kb([
                [("✏️ Nama", f"fs:edit:title:{force_sub_id}"), ("🔗 Link", f"fs:edit:invite_link:{force_sub_id}")],
                [("🆔 Chat ID", f"fs:edit:chat_id:{force_sub_id}"), ("🔘 Tombol", f"fs:edit:button_text:{force_sub_id}")],
                [(("❌ Nonaktifkan" if channel["active"] else "✅ Aktifkan"), f"fs:toggle:{force_sub_id}")],
                [("🗑 Hapus", f"fs:delete:{force_sub_id}")],
                [("⬅️ Kembali", "adm:fsubs")],
            ])
            text = (
                f"📢 <b>{html.escape(channel['title'])}</b>\n\n"
                f"Chat ID: <code>{html.escape(str(channel['chat_id']))}</code>\n"
                f"Invite link:\n{html.escape(channel['invite_link'])}"
            )
            return edit_panel(bot, callback, text, reply_markup=markup)
        return fsubs_panel(bot, callback)

    if flow == "fsub":
        return fsubs_panel(bot, callback)

    if flow == "setting" and data.get("key"):
        return setting_detail_panel(bot, callback, str(data["key"]))

    if flow == "links":
        return settings_panel(bot, callback)

    if flow == "button_edit" and data.get("button_id"):
        return button_detail_panel(bot, callback, int(data["button_id"]))

    if flow == "button":
        return buttons_panel(bot, callback)

    if flow in ("broadcast_tpl_add", "broadcast_tpl_edit_name", "broadcast_tpl_edit_content"):
        return edit_panel(
            bot, callback,
            "📋 <b>Kelola Template Broadcast</b>",
            reply_markup=keyboards.broadcast_template_list(repo.broadcast_templates()),
        )

    if flow == "broadcast":
        templates = repo.broadcast_templates()
        return edit_panel(
            bot, callback,
            "📣 <b>PANEL BROADCAST</b>",
            reply_markup=keyboards.broadcast_home(templates),
        )

    if flow == "upload" and step == "new_category":
        # Kembali ke pemilihan kategori pada proses upload, bukan ke panel utama.
        state.set(chat_id, flow, "category", **data)
        categories = repo.categories()
        rows = [[(row["name"], f"media:uploadcat:{row['id']}")] for row in categories]
        rows.append([("➕ Buat Kategori Baru", "cat:add_upload")])
        rows.append([("❌ Batal Upload", "adm:cancel")])
        return edit_panel(
            bot, callback,
            "📂 <b>Pilih Kategori Media</b>",
            reply_markup=keyboards.kb(rows),
        )

    # Upload utama dan broadcast memang berasal dari panel utama.
    return show_home(bot, callback)


def begin_input(bot, callback, flow: str, step: str, text: str, **data):
    """Mulai input dengan mengubah panel yang sedang ditekan, bukan membuat pesan baru."""
    chat_id = callback.message.chat.id
    state.set(
        chat_id,
        flow,
        step,
        panel_message_id=callback.message.message_id,
        **data,
    )
    return edit_panel(
        bot,
        callback,
        text,
        reply_markup=keyboards.cancel(),
    )


def media_list_panel(bot, callback, page: int = 0):
    page = max(0, page)
    per_page = 8
    total = repo.media_count()
    max_page = max(0, (total - 1) // per_page)
    page = min(page, max_page)
    rows = repo.media_list(per_page, page * per_page)

    if rows:
        items = "\n".join(
            f"{index}. {html.escape(row['title'])} — "
            f"{html.escape(row['category'] or 'masvir')} — {row['views']} view"
            for index, row in enumerate(rows, start=1)
        )
        text = f"📁 <b>Kelola Media</b>\n\n{items}"
    else:
        text = "📁 <b>Kelola Media</b>\n\nBelum ada media."

    nav = []
    if page > 0:
        nav.append(("⬅️", f"adm:media_page:{page - 1}"))
    if page < max_page:
        nav.append(("➡️", f"adm:media_page:{page + 1}"))
    markup_rows = [[(row["title"][:35], f"media:view:{row['id']}")] for row in rows]
    if nav:
        markup_rows.append(nav)
    markup_rows.append([("⬅️ Kembali", "adm:home")])
    markup = keyboards.kb(markup_rows)

    return edit_panel(bot, callback, text, reply_markup=markup)


def categories_panel(bot, callback):
    rows = repo.categories()
    markup = keyboards.kb(
        [[("➕ Tambah", "cat:add")]]
        + [[(row["name"], f"cat:view:{row['id']}")] for row in rows]
        + [[("⬅️ Kembali", "adm:home")]]
    )
    return edit_panel(
        bot,
        callback,
        "📂 <b>Kelola Kategori</b>",
        reply_markup=markup,
    )


def category_detail_panel(
    bot,
    callback,
    category_id: int,
    page: int = 0,
    notice: str | None = None,
):
    category = repo.category(category_id)
    if not category:
        answer_callback(bot, callback, "Kategori tidak ditemukan.", show_alert=True)
        return categories_panel(bot, callback)

    page = max(0, page)
    per_page = 8
    total = repo.category_media_count(category_id)
    max_page = max(0, (total - 1) // per_page)
    page = min(page, max_page)
    media_rows = repo.category_media_list(
        category_id,
        limit=per_page,
        offset=page * per_page,
    )

    prefix = f"{notice}\n\n" if notice else ""
    if media_rows:
        media_text = "\n".join(
            f"{index}. <b>{html.escape(row['title'])}</b> — {row['views']} view"
            for index, row in enumerate(media_rows, start=(page * per_page) + 1)
        )
    else:
        media_text = "<i>Belum ada video dalam kategori ini.</i>"

    text = (
        f"{prefix}📂 <b>Kategori: {html.escape(category['name'])}</b>\n\n"
        f"🎬 Total video: <b>{total}</b>\n\n"
        f"{media_text}"
    )

    markup_rows = [
        [(f"🎬 {row['title'][:32]}", f"media:viewcat:{row['id']}:{category_id}:{page}")]
        for row in media_rows
    ]
    navigation = []
    if page > 0:
        navigation.append(("⬅️", f"cat:page:{category_id}:{page - 1}"))
    if page < max_page:
        navigation.append(("➡️", f"cat:page:{category_id}:{page + 1}"))
    if navigation:
        markup_rows.append(navigation)

    action_row = [("✏️ Ubah Nama", f"cat:rename:{category_id}")]
    if category["name"].lower() != "masvir":
        action_row.append(("🗑 Hapus", f"cat:delete:{category_id}"))
    markup_rows.append(action_row)
    markup_rows.append([("⬅️ Kembali", "adm:categories")])

    return edit_panel(
        bot,
        callback,
        text,
        reply_markup=keyboards.kb(markup_rows),
    )


def templates_panel(bot, callback):
    rows = repo.templates()
    markup = keyboards.kb(
        [[("➕ Tambah", "tpl:add")]]
        + [
            [
                (
                    ("✅ " if row["is_default"] else "") + row["name"],
                    f"tpl:view:{row['id']}",
                )
            ]
            for row in rows
        ]
        + [[("⬅️ Kembali", "adm:home")]]
    )
    return edit_panel(
        bot,
        callback,
        "📝 <b>Template Caption Video</b>",
        reply_markup=markup,
    )


def fsubs_panel(bot, callback):
    rows = repo.fsubs(include_inactive=True)
    markup = keyboards.kb(
        [[("➕ Tambah", "fs:add")]]
        + [[(("✅ " if row["active"] else "❌ ") + row["title"], f"fs:view:{row['id']}")] for row in rows]
        + [[("⬅️ Kembali", "adm:home")]]
    )
    text = (
        "📢 <b>Force Subscribe</b>\n\n"
        "Channel private gunakan ID <code>-100...</code> dan invite link private."
    )
    return edit_panel(bot, callback, text, reply_markup=markup)


def users_panel(bot, callback, page: int = 0):
    page = max(0, page)
    per_page = 10
    total = repo.user_count()
    max_page = max(0, (total - 1) // per_page)
    page = min(page, max_page)
    rows = repo.users(per_page, page * per_page)
    markup_rows = [
        [
            (
                ("🚫 " if row["is_banned"] else "")
                + (row["full_name"] or str(row["id"])),
                f"usr:view:{row['id']}",
            )
        ]
        for row in rows
    ]
    nav = []
    if page > 0:
        nav.append(("⬅️", f"adm:users_page:{page - 1}"))
    if page < max_page:
        nav.append(("➡️", f"adm:users_page:{page + 1}"))
    if nav:
        markup_rows.append(nav)
    markup_rows.append([("⬅️ Kembali", "adm:home")])
    markup = keyboards.kb(markup_rows)
    return edit_panel(
        bot,
        callback,
        "👥 <b>Kelola User</b>",
        reply_markup=markup,
    )


class _SafePreview(dict):
    def __missing__(self, key):
        return "{" + key + "}"


def _preview_values(callback) -> _SafePreview:
    user = callback.from_user
    username = f"@{user.username}" if getattr(user, "username", None) else "-"
    return _SafePreview(
        name=html.escape(getattr(user, "first_name", None) or "Admin"),
        username=html.escape(username),
        user_id=str(user.id),
        start_time="17-06-2026 15:43 WIB",
        title="Contoh Judul Media",
        category="Contoh Kategori",
        link="https://t.me/NamaBot?start=contoh",
    )


def _render_preview(value: str, callback) -> str:
    value = value or "-"
    try:
        rendered = value.format_map(_preview_values(callback))
    except (ValueError, IndexError, KeyError):
        rendered = value
    try:
        return preview_html(rendered)
    except Exception:
        logger.warning("Gagal merender pratinjau caption", exc_info=True)
        return html.escape(rendered)


# <<<<<<< HEAD
# def _build_settings_panel(from_user):
#     """Mengembalikan (text, markup) panel pengaturan tanpa bergantung pada callback."""
#     welcome = repo.setting("welcome_text")
#     channel_caption = repo.setting("channel_caption_template")
#     video_caption = repo.setting("default_video_caption")
#     main_link = repo.setting("main_channel_link")
#     owner_link = repo.setting("owner_link")
#     donation_enabled = repo.setting("donation_enabled") != "0"
#     donation_status = "✅ Aktif" if donation_enabled else "❌ Nonaktif"
#     try:
#         donation_data = repo.donation_stats()
#         total_stars = int(donation_data.get("total_stars", 0) or 0)
#         total_donations = int(donation_data.get("total_donations", 0) or 0)
#     except (AttributeError, TypeError, ValueError):
#         total_stars = total_donations = 0

#     stats = repo.stats()
#     active_users = stats["users"] - stats["banned"]

#     class _A:
#         pass
#     adapter = _A()
#     adapter.from_user = from_user

#     text = (
#         "⚙️ <b>Pengaturan Bot</b>\n\n"
#         "Semua nilai di bawah ini adalah nilai yang sedang aktif. "
#         "Perubahan langsung digunakan tanpa restart bot.\n\n"
#         "👋 <b>Welcome</b>\n"
#         f"<blockquote>{_render_preview(welcome, adapter)}</blockquote>\n"
#         "📣 <b>Caption channel</b>\n"
#         f"<blockquote>{_render_preview(channel_caption, adapter)}</blockquote>\n"
#         "🎬 <b>Caption video</b>\n"
#         f"<blockquote>{_render_preview(video_caption, adapter)}</blockquote>\n"
#         f"📢 <b>Channel:</b> {html.escape(main_link or '-')}\n"
#         f"👤 <b>Admin:</b> {html.escape(owner_link or '-')}\n\n"
#         f"⭐ <b>Donasi Bintang:</b> {donation_status}\n"
#         f"├ Bintang diterima: <b>{total_stars:,} ⭐</b>\n"
#         f"└ Total transaksi: <b>{total_donations:,}</b>\n\n"
#         f"👥 <b>Statistik Pengguna</b>\n"
#         f"├ 👤 Total pengguna: <b>{stats['users']:,}</b>\n"
#         f'├ <tg-emoji emoji-id="5240241223632954241">✅</tg-emoji> Pengguna aktif: <b>{active_users:,}</b>\n'
#         f'├ <tg-emoji emoji-id="5206607081334906820">🚫</tg-emoji> Diblokir: <b>{stats["banned"]:,}</b>\n'
#         f'├ <tg-emoji emoji-id="5210956306952758910">👁</tg-emoji> View hari ini: <b>{stats["today"]:,}</b>\n'
#         f'└ <tg-emoji emoji-id="5231200819986047254">📊</tg-emoji> Total view: <b>{stats["views"]:,}</b>'
# =======
def settings_panel(bot, callback):
    text = (
        "⚙️ <b>Panel Pengaturan Bot</b>\n\n"
        "Pilih menu di bawah untuk mengatur konfigurasi bot. "
        "Setiap perubahan langsung berlaku tanpa perlu me-restart bot."
# >>>>>>> 8677ab1
    )
    markup = keyboards.kb([
        [("👋 Welcome", "set:view:welcome_text"), ("📣 Caption Channel", "set:view:channel_caption_template")],
        [("🎬 Caption Video", "set:view:default_video_caption"), ("🔗 Link Utama", "set:links")],
        [("🔘 Kelola Tombol", "btn:list"), ("➕ Tombol Tambahan", "btn:add")],
        [("♻️ Reset Semua", "set:reset_all")],
        [("⬅️ Kembali", "adm:home")],
    ])
    return text, markup


def settings_panel(bot, callback):
    text, markup = _build_settings_panel(callback.from_user)
    return edit_panel(bot, callback, text, reply_markup=markup)


def setting_detail_panel(bot, callback, key: str, notice: str | None = None):
    labels = {
        "welcome_text": "👋 Pesan Welcome",
        "channel_caption_template": "📣 Caption Postingan Channel",
        "default_video_caption": "🎬 Caption Video Default",
    }
    row = repo.setting_detail(key)
    if not row:
        answer_callback(bot, callback, "Pengaturan tidak ditemukan.", show_alert=True)
        return settings_panel(bot, callback)
    prefix = f"{notice}\n\n" if notice else ""
    text = (
        f"{prefix}<b>{labels.get(key, key)}</b>\n\n"
        f"<b>Pratinjau nilai aktif:</b>\n"
        f"<blockquote>{_render_preview(row['value'], callback)}</blockquote>\n"
        f"<b>Pratinjau nilai bawaan:</b>\n"
        f"<blockquote>{_render_preview(row['default_value'], callback)}</blockquote>\n"
        "Tekan <b>Ubah</b> untuk melihat dan mengedit syntax aslinya."
    )
    markup = keyboards.kb([
        [("✏️ Ubah", f"set:edit:{key}"), ("♻️ Reset Default", f"set:reset:{key}")],
        [("⬅️ Kembali", "adm:settings")],
    ])
    return edit_panel(bot, callback, text, reply_markup=markup)


def buttons_panel(bot, callback, notice: str | None = None):
    rows = repo.buttons(include_inactive=True)
    prefix = f"{notice}\n\n" if notice else ""
    if rows:
        lines = []
        for row in rows:
            status = "Aktif" if row['active'] else "Nonaktif"
            kind = "Bawaan" if row['is_system'] else "Tambahan"
            lines.append(
                f"• <b>{html.escape(row['label'])}</b> — {status} — {kind}\n"
                f"  {html.escape(row['resolved_url'] or row['url'] or '-')}"
            )
        body = "\n".join(lines)
    else:
        body = "Belum ada tombol."
    markup = keyboards.kb(
        [[(row['label'][:35], f"btn:view:{row['id']}")] for row in rows]
        + [[("➕ Tambah Tombol", "btn:add")], [("⬅️ Kembali", "adm:settings")]]
    )
    return edit_panel(bot, callback, prefix + "🔘 <b>Kelola Tombol User</b>\n\n" + body, reply_markup=markup)


def button_detail_panel(bot, callback, button_id: int, notice: str | None = None):
    row = repo.button(button_id)
    if not row:
        answer_callback(bot, callback, "Tombol tidak ditemukan.", show_alert=True)
        return buttons_panel(bot, callback)
    prefix = f"{notice}\n\n" if notice else ""
    text = (
        f"{prefix}🔘 <b>Detail Tombol</b>\n\n"
        f"Nama: <b>{html.escape(row['label'])}</b>\n"
        f"Link: {html.escape(row['resolved_url'] or row['url'] or '-')}\n"
        f"Status: {'Aktif' if row['active'] else 'Nonaktif'}\n"
        f"Jenis: {'Bawaan sistem' if row['is_system'] else 'Tambahan'}"
    )
    buttons = [
        [("✏️ Ubah Teks", f"btn:edit_label:{button_id}"), ("🔗 Ubah Link", f"btn:edit_url:{button_id}")],
        [("🚫 Nonaktifkan" if row['active'] else "✅ Aktifkan", f"btn:toggle:{button_id}")],
    ]
    if row['is_system']:
        buttons.append([("♻️ Reset Default", f"btn:reset:{button_id}")])
    else:
        buttons.append([("🗑 Hapus", f"btn:delete:{button_id}")])
    buttons.append([("⬅️ Kembali", "btn:list")])
    return edit_panel(bot, callback, text, reply_markup=keyboards.kb(buttons))

def media_detail_panel(
    bot,
    callback,
    media_id: int,
    notice: str | None = None,
    back_callback: str = "adm:media",
):
    media = repo.media_by_id(media_id)
    if not media:
        answer_callback(bot, callback, "Media tidak ditemukan.", show_alert=True)
        return media_list_panel(bot, callback)

    prefix = f"{notice}\n\n" if notice else ""
    text = (
        f"{prefix}🎬 <b>{html.escape(media['title'])}</b>\n\n"
        f"Kode: <code>{html.escape(media['code'])}</code>\n"
        f"Kategori: {html.escape(media['category'] or 'masvir')}\n"
        f"Nama file: {html.escape(media['filename'] or '-')}"
    )
    markup = keyboards.kb([
        [("✏️ Judul", f"media:edit:title:{media_id}"), ("📝 Caption", f"media:edit:user_caption:{media_id}")],
        [("🖼 Thumbnail", f"media:edit:thumbnail_file_id:{media_id}"), ("📄 Nama File", f"media:edit:filename:{media_id}")],
        [("📂 Kategori", f"media:category:{media_id}"), ("🔄 Posting Ulang", f"media:repost:{media_id}")],
        [("🗑 Hapus", f"media:delete:{media_id}")],
        [("⬅️ Kembali", back_callback)],
    ])
    return edit_panel(bot, callback, text, reply_markup=markup)


def user_detail_panel(bot, callback, user_id: int, notice: str | None = None):
    user = repo.user(user_id)
    if not user:
        answer_callback(bot, callback, "User tidak ditemukan.", show_alert=True)
        return users_panel(bot, callback)

    banned = bool(user["is_banned"])
    prefix = f"{notice}\n\n" if notice else ""
    text = (
        f"{prefix}👤 <b>{html.escape(user['full_name'] or '-')}</b>\n\n"
        f"ID: <code>{user_id}</code>\n"
        f"Username: @{html.escape(user['username']) if user['username'] else '-'}\n"
        f"Status: <b>{'Diblokir' if banned else 'Aktif'}</b>"
    )
    rows = []
    if user_id not in settings.admin_ids:
        rows.append([
            (
                "✅ Buka Blokir" if banned else "🚫 Blokir",
                f"usr:toggle:{user_id}",
            )
        ])
    rows.append([("⬅️ Kembali", "adm:users")])
    markup = keyboards.kb(rows)
    return edit_panel(bot, callback, text, reply_markup=markup)



def _build_system_panel(notice: str | None = None):
    """Mengembalikan (text, markup) panel sistem tanpa bergantung pada callback."""
    status = system_service.status_data()
    maintenance = repo.setting("maintenance_mode", "0") == "1"
    auto_backup = repo.setting("auto_backup_enabled", "1") != "0"
    retention = repo.setting("backup_retention", "7")
    prefix = f"{notice}\n\n" if notice else ""
    text = (
        f"{prefix}🛡 <b>Sistem & Hosting</b>\n\n"
        f"🤖 Status bot: <b>Aktif</b>\n"
        f"⏱ Uptime: <b>{status['uptime']}</b>\n"
        f"🐍 Python: <code>{status['python']}</code>\n"
        f"🗄 Database: <b>{status['database_size']}</b>\n"
        f"🧹 Job auto-delete: <b>{status['delete_jobs']}</b>\n"
        f"💾 Backup: <b>{status['backup_count']}</b> file ({status['backup_size']})\n"
        f"📦 Retensi backup: <b>{html.escape(str(retention))}</b> file\n"
        f"💽 Disk tersedia: <b>{status['disk_free']}</b> / {status['disk_total']}\n\n"
        f"🔧 Maintenance: <b>{'AKTIF' if maintenance else 'NONAKTIF'}</b>\n"
        f"🕒 Backup otomatis 03.00 WIB: <b>{'AKTIF' if auto_backup else 'NONAKTIF'}</b>"
    )
    markup = keyboards.kb([
        [("🔍 Periksa Konfigurasi", "adm:config_check")],
        [("🔧 Matikan Maintenance" if maintenance else "🔧 Aktifkan Maintenance", "adm:maintenance_toggle")],
        [("🕒 Matikan Backup Otomatis" if auto_backup else "🕒 Aktifkan Backup Otomatis", "adm:auto_backup_toggle")],
        [("💾 Backup Sekarang", "adm:backup"), ("🧹 Bersihkan Backup", "adm:backup_cleanup")],
        [("🔄 Segarkan", "adm:system")],
        [("⬅️ Kembali", "adm:home")],
    ])
    return text, markup


def system_panel(bot, callback, notice: str | None = None):
    text, markup = _build_system_panel(notice)
    return edit_panel(bot, callback, text, reply_markup=markup)

def register(bot):
    @bot.callback_query_handler(func=lambda callback: callback.data.startswith("adm:"))
    def adm(callback):
        if not admin(callback):
            return answer_callback(bot, callback, "Bukan admin.", show_alert=True)

        action = callback.data.split(":", 1)[1]
        chat_id = callback.message.chat.id

        if action == "home":
            answer_callback(bot, callback)
            return show_home(bot, callback)

        if action == "cancel":
            answer_callback(bot, callback, "Proses dibatalkan.")
            return restore_cancel_destination(bot, callback)

        if action == "upload":
            answer_callback(bot, callback)
            return begin_input(
                bot, callback, "upload", "video",
                "📤 <b>Upload Media</b>\n\nKirim file <b>video</b>.",
            )

        if action == "media":
            answer_callback(bot, callback)
            return media_list_panel(bot, callback)

        if action.startswith("media_page:"):
            answer_callback(bot, callback)
            return media_list_panel(bot, callback, int(action.split(":", 1)[1]))

        if action == "categories":
            answer_callback(bot, callback)
            return categories_panel(bot, callback)

        if action == "templates":
            answer_callback(bot, callback)
            return templates_panel(bot, callback)

        if action == "fsubs":
            answer_callback(bot, callback)
            return fsubs_panel(bot, callback)

        if action == "users":
            answer_callback(bot, callback)
            return users_panel(bot, callback)

        if action.startswith("users_page:"):
            answer_callback(bot, callback)
            return users_panel(bot, callback, int(action.split(":", 1)[1]))

        if action == "stats":
            answer_callback(bot, callback)
            stats = repo.stats()
            top_media = repo.top_media()
            top_text = "\n".join(
                f"• {html.escape(item['title'])}: "
                f"{item['views']} / {item['unique_views']} unik"
                for item in top_media
            ) or "Belum ada data view."

            text = (
                "📊 <b>Statistik</b>\n\n"
                f"User: {stats['users']}\n"
                f"Diblokir: {stats['banned']}\n"
                f"Media: {stats['media']}\n"
                f"Total view: {stats['views']}\n"
                f"Unique view: {stats['unique_views']}\n"
                f"View hari ini: {stats['today']}\n\n"
                f"🏆 <b>Teratas</b>\n{top_text}"
            )
            return edit_panel(
                bot,
                callback,
                text,
                reply_markup=keyboards.back(),
            )

        if action == "broadcast":
            answer_callback(bot, callback)
            templates = repo.broadcast_templates()
            text = "📣 <b>PANEL BROADCAST</b>"
            return edit_panel(
                bot, callback,
                text,
                reply_markup=keyboards.broadcast_home(templates),
            )

        if action == "settings":
            answer_callback(bot, callback)
            return settings_panel(bot, callback)

        if action == "system":
            answer_callback(bot, callback)
            return system_panel(bot, callback)

        if action == "maintenance_toggle":
            current = repo.setting("maintenance_mode", "0") == "1"
            repo.set_setting("maintenance_mode", "0" if current else "1")
            answer_callback(bot, callback, "Mode maintenance diperbarui.")
            return system_panel(bot, callback)

        if action == "auto_backup_toggle":
            current = repo.setting("auto_backup_enabled", "1") != "0"
            repo.set_setting("auto_backup_enabled", "0" if current else "1")
            answer_callback(bot, callback, "Backup otomatis diperbarui.")
            return system_panel(bot, callback)

        if action == "backup_cleanup":
            removed = system_service.cleanup_backups()
            answer_callback(bot, callback, f"{removed} backup lama dihapus.")
            return system_panel(bot, callback, notice=f"✅ {removed} backup lama dihapus.")

        if action == "config_check":
            answer_callback(bot, callback, "Memeriksa konfigurasi...")
            results = system_service.check_configuration(bot)
            text = "🔍 <b>Hasil Pemeriksaan Konfigurasi</b>\n\n" + "\n".join(
                html.escape(line) for line in results
            )
            return edit_panel(bot, callback, text, reply_markup=keyboards.back("adm:system"))

        if action == "backup":
            answer_callback(bot, callback, "Membuat backup...")
            destination = system_service.create_backup()
            system_service.cleanup_backups()
            with destination.open("rb") as backup_file:
                return bot.send_document(
                    chat_id,
                    backup_file,
                    caption="✅ Backup database",
                )

    @bot.callback_query_handler(
        func=lambda callback: callback.data.startswith("media:")
        and not callback.data.startswith("media:uploadcat:")
    )
    def media(callback):
        if not admin(callback):
            return answer_callback(bot, callback, "Bukan admin.", show_alert=True)

        parts = callback.data.split(":")
        action = parts[1]
        chat_id = callback.message.chat.id

        if action == "viewcat":
            media_id = int(parts[2])
            category_id = int(parts[3])
            page = int(parts[4])
            answer_callback(bot, callback)
            return media_detail_panel(
                bot,
                callback,
                media_id,
                back_callback=f"cat:page:{category_id}:{page}",
            )

        if action == "view":
            answer_callback(bot, callback)
            return media_detail_panel(bot, callback, int(parts[2]))

        if action == "edit":
            answer_callback(bot, callback)
            field = parts[2]
            media_id = int(parts[3])
            instruction = (
                "🖼 <b>Edit Thumbnail</b>\n\nKirim thumbnail baru sebagai <b>foto</b>."
                if field == "thumbnail_file_id"
                else "✏️ <b>Edit Media</b>\n\nKirim nilai baru."
            )
            return begin_input(
                bot, callback, "edit_media", "value", instruction,
                field=field, mid=media_id,
            )

        if action == "category":
            answer_callback(bot, callback)
            media_id = int(parts[2])
            rows = repo.categories()
            markup = keyboards.kb(
                [
                    [(row["name"], f"media:setcat:{media_id}:{row['id']}")]
                    for row in rows
                ]
                + [[("⬅️ Kembali", f"media:view:{media_id}")]]
            )
            return edit_panel(
                bot,
                callback,
                "📂 <b>Pilih Kategori</b>",
                reply_markup=markup,
            )

        if action == "setcat":
            media_id = int(parts[2])
            category_id = int(parts[3])
            try:
                repo.update_media(media_id, "category_id", category_id)
                media_service.publish(bot, media_id, True)
            except Exception as error:
                logger.exception("Gagal mengganti kategori media")
                return answer_callback(
                    bot,
                    callback,
                    f"Gagal: {str(error)[:150]}",
                    show_alert=True,
                )

            answer_callback(bot, callback, "Kategori berhasil diperbarui.")
            return media_detail_panel(
                bot,
                callback,
                media_id,
                notice="✅ <b>Kategori berhasil diperbarui.</b>",
            )

        if action == "repost":
            media_id = int(parts[2])
            try:
                media_service.publish(bot, media_id, True)
            except Exception as error:
                logger.exception("Gagal memperbarui postingan channel")
                return answer_callback(
                    bot,
                    callback,
                    f"Gagal repost: {str(error)[:150]}",
                    show_alert=True,
                )

            return answer_callback(
                bot,
                callback,
                "Postingan channel berhasil diperbarui.",
                show_alert=True,
            )

        if action == "delete":
            media_id = int(parts[2])
            try:
                media_service.remove(bot, media_id)
            except Exception as error:
                logger.exception("Gagal menghapus media")
                return answer_callback(
                    bot,
                    callback,
                    f"Gagal menghapus: {str(error)[:150]}",
                    show_alert=True,
                )

            answer_callback(bot, callback, "Media berhasil dihapus.")
            return media_list_panel(bot, callback)

    @bot.callback_query_handler(
        func=lambda callback: callback.data.startswith(
            ("cat:", "tpl:", "fs:", "usr:", "set:", "btn:")
        )
        and callback.data != "cat:add_upload"
    )
    def crud(callback):
        if not admin(callback):
            return answer_callback(bot, callback, "Bukan admin.", show_alert=True)

        parts = callback.data.split(":")
        kind, action = parts[0], parts[1]
        chat_id = callback.message.chat.id

        if kind == "cat":
            if action == "add":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "category", "add",
                    "📂 <b>Tambah Kategori</b>\n\nKirim nama kategori baru.",
                )

            category_id = int(parts[2])

            if action == "page":
                page = int(parts[3])
                answer_callback(bot, callback)
                return category_detail_panel(bot, callback, category_id, page=page)

            category = repo.category(category_id)
            if not category:
                return answer_callback(
                    bot,
                    callback,
                    "Kategori tidak ditemukan.",
                    show_alert=True,
                )

            if action == "view":
                answer_callback(bot, callback)
                return category_detail_panel(bot, callback, category_id, page=0)

            if action == "rename":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "category", "rename",
                    "✏️ <b>Ubah Nama Kategori</b>\n\nKirim nama baru.",
                    cid=category_id,
                )

            if action == "delete":
                try:
                    repo.delete_category(category_id)
                except Exception as error:
                    return answer_callback(
                        bot,
                        callback,
                        str(error)[:180],
                        show_alert=True,
                    )

                answer_callback(bot, callback, "Kategori berhasil dihapus.")
                return categories_panel(bot, callback)

        if kind == "tpl":
            if action == "add":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "template", "name",
                    "📝 <b>Tambah Template</b>\n\nKirim nama template.",
                )

            template_id = int(parts[2])
            template = repo.one(
                "SELECT * FROM caption_templates WHERE id=?",
                (template_id,),
            )
            if not template:
                return answer_callback(
                    bot,
                    callback,
                    "Template tidak ditemukan.",
                    show_alert=True,
                )

            if action == "view":
                answer_callback(bot, callback)
                rows = [
                    [("✏️ Ubah Isi", f"tpl:edit:{template_id}"), ("✅ Jadikan Default", f"tpl:default:{template_id}")],
                ]
                if template["is_system"]:
                    rows.append([("♻️ Reset Caption Bawaan", f"tpl:reset:{template_id}")])
                else:
                    rows.append([("🗑 Hapus", f"tpl:delete:{template_id}")])
                rows.append([("⬅️ Kembali", "adm:templates")])
                return edit_panel(
                    bot,
                    callback,
                    f"📝 <b>{html.escape(template['name'])}</b>\n\n"
                    f"Status: {'Default aktif' if template['is_default'] else 'Tidak aktif'}\n"
                    f"Jenis: {'Bawaan sistem' if template['is_system'] else 'Buatan admin'}\n\n"
                    f"<blockquote>{_render_preview(template['content'], callback)}</blockquote>",
                    reply_markup=keyboards.kb(rows),
                )


            if action == "edit":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "template_edit", "content",
                    "📝 <b>Ubah Isi Template</b>\n\nKirim isi caption baru.",
                    template_id=template_id,
                )

            if action == "reset":
                if not template["is_system"]:
                    return answer_callback(bot, callback, "Hanya template bawaan yang dapat di-reset.", show_alert=True)
                repo.reset_template(template_id)
                if template["is_default"]:
                    refreshed = repo.template(template_id)
                    repo.set_setting("default_video_caption", refreshed["content"])
                answer_callback(bot, callback, "Caption bawaan dikembalikan ke default.")
                refreshed = repo.template(template_id)
                return edit_panel(
                    bot, callback,
                    f"✅ <b>Caption bawaan berhasil di-reset.</b>\n\n<blockquote>{_render_preview(refreshed['content'], callback)}</blockquote>",
                    reply_markup=keyboards.kb([
                        [("✏️ Ubah Isi", f"tpl:edit:{template_id}"), ("✅ Jadikan Default", f"tpl:default:{template_id}")],
                        [("♻️ Reset Caption Bawaan", f"tpl:reset:{template_id}")],
                        [("⬅️ Kembali", "adm:templates")],
                    ]),
                )

            if action == "default":
                repo.set_default_template(template_id)
                answer_callback(
                    bot,
                    callback,
                    "Template berhasil dijadikan default.",
                    show_alert=True,
                )
                return templates_panel(bot, callback)

            if action == "delete":
                if template["is_default"]:
                    return answer_callback(
                        bot,
                        callback,
                        "Template default tidak dapat dihapus. Jadikan template lain sebagai default terlebih dahulu.",
                        show_alert=True,
                    )
                repo.delete_template(template_id)
                answer_callback(bot, callback, "Template berhasil dihapus.")
                return templates_panel(bot, callback)

        if kind == "fs":
            if action == "add":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "fsub", "chat_id",
                    "📢 <b>Tambah Force Subscribe</b>\n\nKirim username channel (@nama) atau ID private -100...",
                )

            force_sub_id = int(parts[2])
            channel = repo.one(
                "SELECT * FROM force_sub_channels WHERE id=?",
                (force_sub_id,),
            )
            if not channel:
                return answer_callback(
                    bot,
                    callback,
                    "Channel tidak ditemukan.",
                    show_alert=True,
                )

            if action == "view":
                answer_callback(bot, callback)
                markup = keyboards.kb(
                    [
                        [("✏️ Nama", f"fs:edit:title:{force_sub_id}"), ("🔗 Link", f"fs:edit:invite_link:{force_sub_id}")],
                        [("🆔 Chat ID", f"fs:edit:chat_id:{force_sub_id}"), ("🔘 Tombol", f"fs:edit:button_text:{force_sub_id}")],
                        [(("❌ Nonaktifkan" if channel["active"] else "✅ Aktifkan"), f"fs:toggle:{force_sub_id}")],
                        [("🗑 Hapus", f"fs:delete:{force_sub_id}")],
                        [("⬅️ Kembali", "adm:fsubs")],
                    ]
                )
                text = (
                    f"📢 <b>{html.escape(channel['title'])}</b>\n\n"
                    f"Chat ID: <code>{html.escape(str(channel['chat_id']))}</code>\n"
                    f"Invite link:\n{html.escape(channel['invite_link'])}"
                )
                return edit_panel(bot, callback, text, reply_markup=markup)

            if action == "edit":
                field = parts[2]
                force_sub_id = int(parts[3])
                labels = {
                    "title": "nama channel",
                    "invite_link": "invite link",
                    "chat_id": "chat ID / @username",
                    "button_text": "teks tombol",
                }
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "fsub_edit", "value",
                    f"✏️ <b>Edit Force Subscribe</b>\n\nKirim {labels.get(field, 'nilai')} baru.",
                    fsub_id=force_sub_id, field=field,
                )

            if action == "toggle":
                repo.toggle_fsub(force_sub_id)
                answer_callback(bot, callback, "Status channel diperbarui.")
                return fsubs_panel(bot, callback)

            if action == "delete":
                repo.delete_fsub(force_sub_id)
                answer_callback(bot, callback, "Channel berhasil dihapus.")
                return fsubs_panel(bot, callback)

        if kind == "usr":
            user_id = int(parts[2])

            if action == "view":
                answer_callback(bot, callback)
                return user_detail_panel(bot, callback, user_id)

            if action == "toggle":
                if user_id in settings.admin_ids:
                    return answer_callback(
                        bot,
                        callback,
                        "Administrator tidak dapat diblokir.",
                        show_alert=True,
                    )

                user = repo.user(user_id)
                if not user:
                    return answer_callback(
                        bot,
                        callback,
                        "User tidak ditemukan.",
                        show_alert=True,
                    )

                repo.ban_user(user_id, not bool(user["is_banned"]))
                answer_callback(bot, callback, "Status user berhasil diperbarui.")
                return user_detail_panel(
                    bot,
                    callback,
                    user_id,
                    notice="✅ <b>Status user berhasil diperbarui.</b>",
                )

        if kind == "set":
            if action == "view":
                answer_callback(bot, callback)
                return setting_detail_panel(bot, callback, parts[2])

            if action == "edit":
                key = parts[2]
                answer_callback(bot, callback)
                hint = (
                    "Kirim teks baru. Untuk caption channel gunakan "
                    "<code>{link}</code>; tersedia juga "
                    "<code>{title}</code> dan <code>{category}</code>."
                )
                return begin_input(
                    bot, callback, "setting", "value",
                    "⚙️ <b>Ubah Pengaturan</b>\n\n" + hint,
                    key=key,
                )

            if action == "reset":
                key = parts[2]
                repo.reset_setting(key)
                if key == "default_video_caption":
                    default_tpl = repo.default_template()
                    if default_tpl and default_tpl["is_system"]:
                        repo.update_template(default_tpl["id"], repo.setting(key))
                answer_callback(bot, callback, "Nilai bawaan diterapkan.")
                return setting_detail_panel(
                    bot, callback, key,
                    notice="✅ <b>Berhasil dikembalikan ke default.</b>",
                )

            if action == "reset_all":
                repo.reset_settings([
                    "welcome_text", "channel_caption_template", "default_video_caption",
                    "main_channel_link", "owner_link", "help_link",
                ])
                for row in repo.buttons(include_inactive=True):
                    if row["is_system"]:
                        repo.reset_button(row["id"])
                default_tpl = repo.default_template()
                if default_tpl and default_tpl["is_system"]:
                    repo.reset_template(default_tpl["id"])
                answer_callback(bot, callback, "Semua pengaturan dikembalikan ke default.")
                return settings_panel(bot, callback)

            if action == "links":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "links", "main",
                    "🔗 <b>Ubah Link</b>\n\nKirim link channel utama.",
                )

        if kind == "btn":
            if action == "add":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "button", "label",
                    "➕ <b>Tambah Tombol Welcome</b>\n\nKirim teks tombol.",
                )

            if action == "list":
                answer_callback(bot, callback)
                return buttons_panel(bot, callback)

            button_id = int(parts[2]) if len(parts) > 2 else 0

            if action == "view":
                answer_callback(bot, callback)
                return button_detail_panel(bot, callback, button_id)

            if action == "edit_label":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "button_edit", "label",
                    "✏️ <b>Ubah Teks Tombol</b>\n\nKirim teks tombol baru.",
                    button_id=button_id,
                )

            if action == "edit_url":
                answer_callback(bot, callback)
                return begin_input(
                    bot, callback, "button_edit", "url",
                    "🔗 <b>Ubah Link Tombol</b>\n\nKirim URL baru.",
                    button_id=button_id,
                )

            if action == "toggle":
                repo.toggle_button(button_id)
                answer_callback(bot, callback, "Status tombol diperbarui.")
                return button_detail_panel(bot, callback, button_id)

            if action == "reset":
                repo.reset_button(button_id)
                answer_callback(bot, callback, "Tombol dikembalikan ke default.")
                return button_detail_panel(bot, callback, button_id)

            if action == "delete":
                row = repo.button(button_id)
                if row and row["is_system"]:
                    return answer_callback(
                        bot, callback,
                        "Tombol bawaan tidak dapat dihapus. Nonaktifkan saja.",
                        show_alert=True,
                    )
                repo.delete_button(button_id)
                answer_callback(bot, callback, "Tombol berhasil dihapus.")
                return buttons_panel(bot, callback)

# <<<<<<< HEAD
#     @bot.message_handler(
#         func=lambda m: (
#             m.from_user.id in settings.admin_ids
#             and m.text in keyboards.ADMIN_REPLY_TEXTS
#         )
#     )
#     def reply_menu(message):
#         chat_id = message.chat.id
#         text = message.text

#         def send(body, markup=None):
#             bot.send_message(
#                 chat_id, body,
#                 parse_mode="HTML",
#                 reply_markup=markup,
#                 disable_web_page_preview=True,
#             )

#         if text == "📤 Upload":
#             state.set(chat_id, "upload", "video")
#             send("📤 <b>Upload Media</b>\n\nKirim file <b>video</b>.", keyboards.cancel())

#         elif text == "📁 Media":
#             per_page = 8
#             total = repo.media_count()
#             max_page = max(0, (total - 1) // per_page)
#             rows = repo.media_list(per_page, 0)
#             if rows:
#                 items = "\n".join(
#                     f"{i}. {html.escape(r['title'])} — "
#                     f"{html.escape(r['category'] or 'masvir')} — {r['views']} view"
#                     for i, r in enumerate(rows, 1)
#                 )
#                 body = f"📁 <b>Kelola Media</b>\n\n{items}"
#             else:
#                 body = "📁 <b>Kelola Media</b>\n\nBelum ada media."
#             markup_rows = [[(r["title"][:35], f"media:view:{r['id']}")] for r in rows]
#             if max_page > 0:
#                 markup_rows.append([("➡️", "adm:media_page:1")])
#             markup_rows.append([("⬅️ Kembali", "adm:home")])
#             send(body, keyboards.kb(markup_rows))

#         elif text == "📂 Kategori":
#             rows = repo.categories()
#             markup = keyboards.kb(
#                 [[("➕ Tambah", "cat:add")]]
#                 + [[(r["name"], f"cat:view:{r['id']}")] for r in rows]
#                 + [[("⬅️ Kembali", "adm:home")]]
#             )
#             send("📂 <b>Kelola Kategori</b>", markup)

#         elif text == "📝 Template":
#             rows = repo.templates()
#             markup = keyboards.kb(
#                 [[("➕ Tambah", "tpl:add")]]
#                 + [[( ("✅ " if r["is_default"] else "") + r["name"], f"tpl:view:{r['id']}")] for r in rows]
#                 + [[("⬅️ Kembali", "adm:home")]]
#             )
#             send("📝 <b>Template Caption Video</b>", markup)

#         elif text == "📢 Force Subscribe":
#             rows = repo.fsubs(include_inactive=True)
#             markup = keyboards.kb(
#                 [[("➕ Tambah", "fs:add")]]
#                 + [[( ("✅ " if r["active"] else "❌ ") + r["title"], f"fs:view:{r['id']}")] for r in rows]
#                 + [[("⬅️ Kembali", "adm:home")]]
#             )
#             send(
#                 "📢 <b>Force Subscribe</b>\n\n"
#                 "Channel private gunakan ID <code>-100...</code> dan invite link private.",
#                 markup,
#             )

#         elif text == "👥 User":
#             per_page = 10
#             total = repo.user_count()
#             max_page = max(0, (total - 1) // per_page)
#             rows = repo.users(per_page, 0)
#             markup_rows = [
#                 [( ("🚫 " if r["is_banned"] else "") + (r["full_name"] or str(r["id"])), f"usr:view:{r['id']}")]
#                 for r in rows
#             ]
#             if max_page > 0:
#                 markup_rows.append([("➡️", "adm:users_page:1")])
#             markup_rows.append([("⬅️ Kembali", "adm:home")])
#             send("👥 <b>Kelola User</b>", keyboards.kb(markup_rows))

#         elif text == "📊 Statistik":
#             stats = repo.stats()
#             top_media = repo.top_media()
#             top_text = "\n".join(
#                 f"• {html.escape(item['title'])}: {item['views']} / {item['unique_views']} unik"
#                 for item in top_media
#             ) or "Belum ada data view."
#             body = (
#                 "📊 <b>Statistik</b>\n\n"
#                 f"User: {stats['users']}\n"
#                 f"Diblokir: {stats['banned']}\n"
#                 f"Media: {stats['media']}\n"
#                 f"Total view: {stats['views']}\n"
#                 f"Unique view: {stats['unique_views']}\n"
#                 f"View hari ini: {stats['today']}\n\n"
#                 f"🏆 <b>Teratas</b>\n{top_text}"
#             )
#             send(body, keyboards.back())

#         elif text == "📣 Broadcast":
#             state.set(chat_id, "broadcast", "content")
#             send(
#                 "📣 <b>Broadcast</b>\n\nKirim pesan, foto, atau video yang akan dibroadcast.",
#                 keyboards.cancel(),
#             )

#         elif text == "⚙️ Pengaturan Bot":
#             body, markup = _build_settings_panel(message.from_user)
#             send(body, markup)

#         elif text == "🛡 Sistem":
#             body, markup = _build_system_panel()
#             send(body, markup)

#         elif text == "💾 Backup":
#             destination = system_service.create_backup()
#             system_service.cleanup_backups()
#             with destination.open("rb") as backup_file:
#                 bot.send_document(chat_id, backup_file, caption="✅ Backup database")
# =======
    # ── Broadcast Template handler ────────────────────────────────────────────
    @bot.callback_query_handler(func=lambda callback: callback.data.startswith("bct:"))
    def bct(callback):
        if not admin(callback):
            return answer_callback(bot, callback, "Bukan admin.", show_alert=True)

        parts = callback.data.split(":")
        action = parts[1]
        chat_id = callback.message.chat.id

        if action == "home":
            answer_callback(bot, callback)
            templates = repo.broadcast_templates()
            text = "📣 <b>PANEL BROADCAST</b>"
            return edit_panel(
                bot, callback,
                text,
                reply_markup=keyboards.broadcast_home(templates),
            )

        if action == "templates":
            answer_callback(bot, callback)
            templates = repo.broadcast_templates()
            if not templates:
                text = "📋 <b>Kelola Template Broadcast</b>\n\nBelum ada template. Tambah template pertama."
            else:
                text = f"📋 <b>Kelola Template Broadcast</b>\n\n{len(templates)} template tersedia."
            return edit_panel(bot, callback, text, reply_markup=keyboards.broadcast_template_list(templates))

        if action == "add":
            answer_callback(bot, callback)
            return begin_input(
                bot, callback, "broadcast_tpl_add", "waiting_name",
                "➕ <b>Tambah Template Broadcast</b>\n\nKirim <b>nama</b> untuk template ini.",
            )

        if action == "view" and len(parts) > 2:
            answer_callback(bot, callback)
            tid = int(parts[2])
            tpl = repo.broadcast_template(tid)
            if not tpl:
                return answer_callback(bot, callback, "Template tidak ditemukan.", show_alert=True)
            type_label = {"text": "Teks", "photo": "Foto", "video": "Video", "document": "Dokumen"}.get(tpl["content_type"], tpl["content_type"])
            caption_preview = (tpl["caption"] or "")[:200]
            btn_info = f"\n🔘 Tombol: <b>{html.escape(tpl['button_text'])}</b> → {html.escape(tpl['button_url'])}" if tpl["button_text"] else "\n🔘 Tombol: <i>tidak ada</i>"
            text = (
                f"📄 <b>{html.escape(tpl['name'])}</b>\n\n"
                f"Tipe media: <b>{type_label}</b>\n"
                f"Dibuat: {tpl['created_at'][:10]}"
                f"{btn_info}\n\n"
                + (f"Caption:\n<blockquote>{html.escape(caption_preview)}</blockquote>" if caption_preview else "")
            )
            # Tombol hapus hanya untuk owner
            rows = [
                [("✏️ Edit Nama", f"bct:edit_name:{tid}"), ("📝 Edit Konten", f"bct:edit_content:{tid}")],
            ]
            if _is_owner(callback.from_user.id):
                rows.append([("🗑 Hapus", f"bct:delete:{tid}")])
            rows.append([("⬅️ Kembali", "bct:home")])
            return edit_panel(bot, callback, text, reply_markup=keyboards.kb(rows))

        if action == "edit_name" and len(parts) > 2:
            answer_callback(bot, callback)
            tid = int(parts[2])
            return begin_input(
                bot, callback, "broadcast_tpl_edit_name", "waiting_name",
                "✏️ <b>Edit Nama Template</b>\n\nKirim nama baru.",
                tpl_id=tid,
            )

        if action == "edit_content" and len(parts) > 2:
            answer_callback(bot, callback)
            tid = int(parts[2])
            return begin_input(
                bot, callback, "broadcast_tpl_edit_content", "waiting_media",
                "📝 <b>Edit Konten Template</b>\n\nKirim foto/video baru, atau lewati.",
                tpl_id=tid,
            )

        if action == "delete" and len(parts) > 2:
            if not _is_owner(callback.from_user.id):
                return answer_callback(bot, callback, "Hanya owner yang bisa menghapus template.", show_alert=True)
            answer_callback(bot, callback)
            tid = int(parts[2])
            tpl = repo.broadcast_template(tid)
            if not tpl:
                return answer_callback(bot, callback, "Template tidak ditemukan.", show_alert=True)
            return edit_panel(
                bot, callback,
                f"🗑 Hapus template <b>{html.escape(tpl['name'])}</b>?\n\nTindakan ini tidak dapat dibatalkan.",
                reply_markup=keyboards.broadcast_delete_confirm(tid),
            )

        if action == "delete_ok" and len(parts) > 2:
            if not _is_owner(callback.from_user.id):
                return answer_callback(bot, callback, "Hanya owner yang bisa menghapus template.", show_alert=True)
            tid = int(parts[2])
            repo.delete_broadcast_template(tid)
            answer_callback(bot, callback, "Template berhasil dihapus.")
            templates = repo.broadcast_templates()
            text = "📣 <b>PANEL BROADCAST</b>"
            return edit_panel(bot, callback, text, reply_markup=keyboards.broadcast_home(templates))

        if action == "delete_list":
            answer_callback(bot, callback)
            templates = repo.broadcast_templates()
            if not templates:
                return edit_panel(
                    bot, callback,
                    "🗑️ <b>Hapus Template</b>\n\nBelum ada template.",
                    reply_markup=keyboards.kb([[("⬅️ Kembali", "bct:home")]]),
                )
            return edit_panel(
                bot, callback,
                "🗑️ <b>Hapus Template</b>\n\nPilih template yang akan dihapus.",
                reply_markup=keyboards.broadcast_delete_list(templates),
            )

        if action == "skip_media":
            answer_callback(bot, callback)
            session = state.get(chat_id)
            if not session or session.get("flow") not in ("broadcast_tpl_add", "broadcast_tpl_edit_content"):
                return show_home(bot, callback)
            state.update(chat_id, "waiting_caption", content_type="text", file_id=None)
            return edit_panel(
                bot, callback,
                "📝 <b>Template Broadcast</b>\n\nKirim teks/caption broadcast.",
                reply_markup=keyboards.cancel(),
            )

        if action == "skip_button":
            answer_callback(bot, callback)
            session = state.get(chat_id)
            if not session or session.get("flow") not in ("broadcast_tpl_add", "broadcast_tpl_edit_content"):
                return show_home(bot, callback)
            flow = session["flow"]
            data = session.get("data", {})
            try:
                if flow == "broadcast_tpl_add":
                    repo.add_broadcast_template(
                        data["tpl_name"], data.get("content_type", "text"),
                        data.get("file_id"), data.get("caption", ""), "", "",
                    )
                    msg = f"✅ <b>Template <i>{html.escape(data['tpl_name'])}</i> berhasil disimpan.</b>"
                else:
                    repo.update_broadcast_template_content(
                        int(data["tpl_id"]), data.get("content_type", "text"),
                        data.get("file_id"), data.get("caption", ""), "", "",
                    )
                    msg = "✅ <b>Konten template berhasil diperbarui.</b>"
                state.clear(chat_id)
                templates = repo.broadcast_templates()
                return edit_panel(bot, callback, msg, reply_markup=keyboards.broadcast_template_list(templates))
            except Exception as exc:
                return edit_panel(bot, callback, f"❌ Error: <code>{html.escape(str(exc))}</code>", reply_markup=keyboards.cancel())

        if action == "send":
            answer_callback(bot, callback)
            templates = repo.broadcast_templates()
            if not templates:
                return edit_panel(
                    bot, callback,
                    "🚀 <b>Kirim Broadcast</b>\n\nBelum ada template. Buat template dulu atau tulis konten baru.",
                    reply_markup=keyboards.broadcast_send_select([]),
                )
            return edit_panel(
                bot, callback,
                "🚀 <b>Kirim Broadcast</b>\n\nPilih template atau tulis konten baru.",
                reply_markup=keyboards.broadcast_send_select(templates),
            )

        if action == "send_new":
            answer_callback(bot, callback)
            return begin_input(
                bot, callback, "broadcast", "content",
                "📣 <b>Broadcast</b>\n\nKirim pesan, foto, atau video yang akan dibroadcast.",
            )

        if action == "use" and len(parts) > 2:
            answer_callback(bot, callback)
            tid = int(parts[2])
            tpl = repo.broadcast_template(tid)
            if not tpl:
                return answer_callback(bot, callback, "Template tidak ditemukan.", show_alert=True)
            total = repo.user_count()
            content_type = tpl["content_type"]
            file_id = tpl["file_id"] or None
            caption = tpl["caption"] or ""

            from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton

            if content_type in ("photo", "video", "document") and file_id:
                try:
                    send_fn = getattr(bot, f"send_{content_type}")
                    send_fn(chat_id, file_id, caption=caption, parse_mode="HTML")
                except Exception as exc:
                    logger.warning("Gagal mengirim preview broadcast: %s", exc)
                    return edit_panel(
                        bot, callback,
                        f"❌ <b>Preview gagal</b>\n\n<code>{html.escape(str(exc))}</code>\n\n"
                        "Template memiliki file_id yang sudah tidak valid.\n"
                        "Edit konten template dengan mengirim file <b>foto/video baru</b>.",
                        reply_markup=keyboards.kb([[("⬅️ Kembali", "bct:home")]]),
                    )
                confirm_markup = InlineKeyboardMarkup(row_width=2)
                confirm_markup.row(
                    InlineKeyboardButton("✅ Kirim", callback_data=f"bct:confirm:{tid}"),
                    InlineKeyboardButton("❌ Batal", callback_data="bct:home"),
                )
                return edit_panel(
                    bot, callback,
                    f"📋 <b>Preview di atas adalah tampilan yang akan diterima user.</b>\n\n"
                    f"Template: <b>{html.escape(tpl['name'])}</b>\n"
                    f"Akan dikirim ke <b>{total}</b> pengguna aktif.\n\nLanjutkan?",
                    reply_markup=confirm_markup,
                )

            markup = InlineKeyboardMarkup(row_width=2)
            markup.row(
                InlineKeyboardButton("✅ Kirim", callback_data=f"bct:confirm:{tid}"),
                InlineKeyboardButton("❌ Batal", callback_data="bct:home"),
            )
            text = f"<b>Preview: {html.escape(tpl['name'])}</b>\n\n{caption}"
            return edit_panel(
                bot, callback,
                text,
                reply_markup=markup,
            )

        if action == "confirm" and len(parts) > 2:
            answer_callback(bot, callback, "Memulai broadcast...")
            tid = int(parts[2])
            tpl = repo.broadcast_template(tid)
            if not tpl:
                templates = repo.broadcast_templates()
                return edit_panel(bot, callback, "❌ Template tidak ditemukan.", reply_markup=keyboards.broadcast_home(templates))

            all_users = [u for u in repo.users() if not u["is_banned"]]
            total = len(all_users)
            success = failed = blocked = 0
            sent = 0

            bot.edit_message_text(
                chat_id=chat_id,
                message_id=callback.message.message_id,
                text=f"⏳ Sedang mengirim broadcast...\n📤 Terkirim: 0 / {total}",
            )

            content_type = tpl["content_type"]
            file_id = tpl["file_id"] or None
            text = tpl["caption"] or None

            btn_markup = None
            if tpl["button_text"] and tpl["button_url"]:
                from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
                btn_markup = InlineKeyboardMarkup()
                btn_markup.add(InlineKeyboardButton(tpl["button_text"], url=tpl["button_url"]))

            for idx, user in enumerate(all_users, 1):
                attempts = 0
                while attempts < 2:
                    attempts += 1
                    try:
                        uid = user["id"]
                        if content_type == "text":
                            bot.send_message(uid, text, parse_mode="HTML", reply_markup=btn_markup)
                        elif content_type == "photo":
                            bot.send_photo(uid, file_id, caption=text, parse_mode="HTML", reply_markup=btn_markup)
                        elif content_type == "video":
                            bot.send_video(uid, file_id, caption=text, parse_mode="HTML", reply_markup=btn_markup)
                        elif content_type == "document":
                            bot.send_document(uid, file_id, caption=text, parse_mode="HTML", reply_markup=btn_markup)
                        success += 1
                        break
                    except ApiTelegramException as exc:
                        payload = getattr(exc, "result_json", {}) or {}
                        retry_after = (payload.get("parameters") or {}).get("retry_after")
                        if retry_after and attempts < 2:
                            import time as _time
                            _time.sleep(float(retry_after) + 0.2)
                            continue
                        error_text = str(exc).lower()
                        if any(m in error_text for m in ("bot was blocked", "chat not found", "user is deactivated")):
                            repo.ban_user(user["id"], True)
                            blocked += 1
                        failed += 1
                        break
                    except Exception:
                        failed += 1
                        break
                import time as _time
                _time.sleep(settings.broadcast_delay)

                sent += 1
                try:
                    bot.edit_message_text(
                        chat_id=chat_id,
                        message_id=callback.message.message_id,
                        text=f"⏳ Sedang mengirim broadcast...\n📤 Terkirim: {sent} / {total}",
                    )
                except Exception:
                    pass

            from src.services import log_service as _log
            _log.send(
                bot, "📣 Broadcast selesai",
                f"Template: {tpl['name']}\nBerhasil: {success}\nGagal: {failed}\nTidak aktif: {blocked}",
            )

            try:
                bot.edit_message_text(
                    chat_id=chat_id,
                    message_id=callback.message.message_id,
                    text=(
                        f"✅ Broadcast selesai\n\n"
                        f"Berhasil: {success}\n"
                        f"Gagal: {failed}\n"
                        f"Tidak aktif: {blocked}"
                    ),
                )
            except Exception:
                pass

            templates = repo.broadcast_templates()
            return edit_panel(
                bot, callback,
                f"✅ <b>Broadcast selesai.</b>\n\nTemplate: <b>{html.escape(tpl['name'])}</b>\nBerhasil: {success}\nGagal: {failed}\nTidak aktif: {blocked}",
                reply_markup=keyboards.broadcast_home(templates),
            )





# >>>>>>> 8677ab1
