"""
Admin panel: statistics, user management (search/ban/unban), broadcast,
force join (delegated to force_join.py) and editable settings.

Only the configured ADMIN_ID may access anything in this module; every entry
point re-verifies authorization.
"""

from __future__ import annotations

import asyncio
import logging
from typing import Any, Optional

from telegram import Update
from telegram.error import Forbidden, RetryAfter, TelegramError
from telegram.ext import ContextTypes, filters

import config
import force_join
import keyboards
import utils

logger = logging.getLogger(__name__)

ADMIN_PANEL_TEXT = "👑 <b>پنل مدیریت</b>\n\nیکی از گزینه‌های زیر را انتخاب کنید:"
DENY_TEXT = "❌ شما اجازه دسترسی به پنل مدیریت را ندارید."


# --------------------------------------------------------------------------- #
# Access control
# --------------------------------------------------------------------------- #

class _PendingAdminFilter(filters.MessageFilter):
    """Matches text/media messages only while the admin has a pending action.

    Registered in the same handler group *before* the public URL handler, so a
    pending admin input never leaks into the download flow.
    """

    name = "PendingAdminFilter"

    def filter(self, message) -> bool:  # type: ignore[override]
        user = message.from_user
        return bool(user and utils.is_admin(user.id) and user.id in utils.ADMIN_PENDING)


pending_filter = _PendingAdminFilter()


def _guard(update: Update) -> bool:
    user = update.effective_user
    return bool(user and utils.is_admin(user.id))


# --------------------------------------------------------------------------- #
# Commands
# --------------------------------------------------------------------------- #

async def admin_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Handle /admin — open the admin panel (admin only)."""
    if update.message is None:
        return
    if not _guard(update):
        await update.message.reply_html(DENY_TEXT)
        return
    await update.message.reply_html(ADMIN_PANEL_TEXT, reply_markup=keyboards.admin_panel_kb())
    logger.info("Admin action: panel opened by %s", update.effective_user.id)


async def stats_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Handle /stats — show global statistics (admin only)."""
    if update.message is None:
        return
    if not _guard(update):
        await update.message.reply_html(DENY_TEXT)
        return
    db = context.bot_data["db"]
    await update.message.reply_html(await _stats_text(db), reply_markup=keyboards.admin_back_kb())


# --------------------------------------------------------------------------- #
# Text builders
# --------------------------------------------------------------------------- #

async def _stats_text(db) -> str:
    s = await db.stats_overview()
    return (
        "📊 <b>آمار کلی ربات</b>\n\n"
        f"👥 تعداد کاربران: {s['total_users']}\n"
        f"📥 تعداد دانلودها: {s['total_downloads']}\n"
        f"✅ دانلودهای موفق: {s['completed']}\n"
        f"❌ دانلودهای ناموفق: {s['failed']}\n"
        f"↩️ دانلودهای لغوشده: {s['cancelled']}\n"
        f"🚫 کاربران مسدود: {s['banned']}\n"
        f"📈 کاربران فعال (۷ روز اخیر): {s['active_week']}\n"
        f"🔗 مقصدهای Force Join فعال: {s['force_join']}"
    )


def _user_card_text(user_id: int, info: Optional[dict[str, Any]]) -> str:
    if info:
        username = f"@{info['username']}" if info.get("username") else "—"
        status = "🚫 مسدود" if info.get("is_banned") else "✅ فعال"
        return (
            "👤 <b>اطلاعات کاربر</b>\n\n"
            f"🆔 شناسه: <code>{user_id}</code>\n"
            f"🔤 نام کاربری: {utils.escape_html(str(username))}\n"
            f"📛 نام: {utils.escape_html(str(info.get('first_name') or '—'))}\n"
            f"📥 دانلودها: {info.get('total_downloads', 0)}\n"
            f"📅 عضویت: {info.get('joined_at') or '—'}\n"
            f"وضعیت: {status}"
        )
    return (
        "👤 <b>اطلاعات کاربر</b>\n\n"
        f"🆔 شناسه: <code>{user_id}</code>\n"
        "⚠️ این کاربر هنوز ربات را استارت نکرده است."
    )


async def _settings_text(db) -> str:
    force_join_on = await force_join.is_enabled(db)
    size = await db.get_setting_int("max_file_size_mb", config.MAX_FILE_SIZE_MB)
    concurrency = await db.get_setting_int("max_concurrent_downloads", config.MAX_CONCURRENT_DOWNLOADS)
    return (
        "⚙️ <b>تنظیمات ربات</b>\n\n"
        f"🔗 Force Join: {'✅ فعال' if force_join_on else '⛔️ غیرفعال'}\n"
        f"📦 حداکثر حجم فایل: {size} MB\n"
        f"⚡ حداکثر دانلودهای همزمان: {concurrency}\n\n"
        "💡 تغییر دانلودهای همزمان پس از ری‌استارت ربات اعمال می‌شود."
    )


# --------------------------------------------------------------------------- #
# Admin callback router (pattern ^ad:)
# --------------------------------------------------------------------------- #

async def handle_admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Route every admin-panel callback."""
    query = update.callback_query
    if query is None:
        return
    if not _guard(update):
        await query.answer(DENY_TEXT, show_alert=True)
        return
    await query.answer()

    user = update.effective_user
    uid = user.id
    db = context.bot_data["db"]
    action = (query.data or "")[3:]  # strip "ad:"

    if action == "panel":
        await utils.safe_edit(query.message, ADMIN_PANEL_TEXT, keyboards.admin_panel_kb())

    elif action == "stats":
        await utils.safe_edit(
            query.message, await _stats_text(db), keyboards.admin_back_kb()
        )

    elif action == "users":
        rows = await db.recent_users(10)
        total = await db.count_users()
        lines = [f"👥 <b>کاربران اخیر</b> (کل: {total})\n"]
        for row in rows:
            name = f"@{row['username']}" if row.get("username") else (row.get("first_name") or "—")
            flag = " 🚫" if row.get("is_banned") else ""
            lines.append(
                f"• <code>{row['user_id']}</code> | {utils.escape_html(str(name))} "
                f"| 📥 {row['total_downloads']}{flag}"
            )
        if not rows:
            lines.append("هنوز کاربری ثبت نشده است.")
        lines.append("\n🔎 برای مدیریت کاربر از «🚫 مدیریت کاربران» استفاده کنید.")
        await utils.safe_edit(query.message, "\n".join(lines), keyboards.admin_back_kb())

    elif action == "uman":
        utils.ADMIN_PENDING[uid] = "user_search"
        await utils.safe_edit(
            query.message,
            "🆔 شناسه عددی تلگرام کاربر را ارسال کنید:",
            keyboards.admin_cancel_kb(),
        )

    elif action == "cancel":
        utils.ADMIN_PENDING.pop(uid, None)
        utils.PENDING_DATA.pop(uid, None)
        await utils.safe_edit(query.message, "❌ عملیات لغو شد.", keyboards.admin_panel_kb())

    elif action == "bcast":
        utils.ADMIN_PENDING[uid] = "broadcast"
        await utils.safe_edit(
            query.message,
            "📢 پیام موردنظر برای ارسال همگانی را بفرستید (متن، عکس، ویدیو و ...):",
            keyboards.admin_cancel_kb(),
        )

    elif action == "bcyes":
        payload = utils.PENDING_DATA.pop(uid, None)
        if not payload:
            await utils.safe_edit(
                query.message, "❌ پیامی برای ارسال پیدا نشد.", keyboards.admin_panel_kb()
            )
            return
        result = await _do_broadcast(context, payload)
        await utils.safe_edit(query.message, result, keyboards.admin_panel_kb())

    elif action == "bcno":
        utils.PENDING_DATA.pop(uid, None)
        await utils.safe_edit(query.message, "❌ ارسال همگانی لغو شد.", keyboards.admin_panel_kb())

    elif action == "fj":
        await force_join.render_admin_menu(query, context)

    elif action == "settings":
        await utils.safe_edit(
            query.message,
            await _settings_text(db),
            keyboards.admin_settings_kb(await force_join.is_enabled(db)),
        )

    elif action == "set:fj":
        current = await force_join.is_enabled(db)
        await force_join.set_enabled(db, not current)
        logger.info("Admin action: force join toggled to %s", not current)
        await utils.safe_edit(
            query.message,
            await _settings_text(db),
            keyboards.admin_settings_kb(not current),
        )

    elif action == "set:size":
        utils.ADMIN_PENDING[uid] = "set_size"
        await utils.safe_edit(
            query.message,
            "📦 حداکثر حجم فایل جدید را به <b>مگابایت</b> ارسال کنید:",
            keyboards.admin_cancel_kb(),
        )

    elif action == "set:conc":
        utils.ADMIN_PENDING[uid] = "set_conc"
        await utils.safe_edit(
            query.message,
            "⚡ حداکثر تعداد دانلودهای همزمان را ارسال کنید:",
            keyboards.admin_cancel_kb(),
        )

    elif action.startswith("ban:") or action.startswith("unban:"):
        try:
            target_id = int(action.split(":", 1)[1])
        except (IndexError, ValueError):
            return
        if action.startswith("ban:"):
            await db.ban_user(target_id, "Blocked by admin")
            logger.info("Admin action: user %s banned", target_id)
        else:
            await db.unban_user(target_id)
            logger.info("Admin action: user %s unbanned", target_id)
        info = await db.get_user(target_id)
        await utils.safe_edit(
            query.message,
            _user_card_text(target_id, info),
            keyboards.user_card_kb(target_id, bool(info and info.get("is_banned"))),
        )


# --------------------------------------------------------------------------- #
# Pending text input dispatcher (filter-gated; see _PendingAdminFilter)
# --------------------------------------------------------------------------- #

async def handle_admin_input(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Process the admin's reply while a pending action is active."""
    user = update.effective_user
    message = update.message
    if user is None or message is None or update.effective_chat is None:
        return
    uid = user.id
    action = utils.ADMIN_PENDING.get(uid)
    if action is None:
        return
    db = context.bot_data["db"]

    if action == "broadcast":
        utils.PENDING_DATA[uid] = {
            "chat_id": update.effective_chat.id,
            "message_id": message.message_id,
        }
        utils.ADMIN_PENDING.pop(uid, None)
        await message.reply_html(
            "⚠️ آیا از ارسال این پیام به همه کاربران مطمئن هستید؟",
            reply_markup=keyboards.broadcast_confirm_kb(),
        )
        return

    text = (message.text or "").strip()

    if action == "user_search":
        if not text.isdigit():
            await message.reply_html("⚠️ لطفاً یک شناسه عددی تلگرام ارسال کنید. برای لغو /cancel را بزنید.")
            return
        target_id = int(text)
        info = await db.get_user(target_id)
        utils.ADMIN_PENDING.pop(uid, None)
        if info is None:
            # Still show a card (with a ban button) for users who never started the bot.
            await message.reply_html(
                _user_card_text(target_id, None),
                reply_markup=keyboards.user_card_kb(target_id, await db.is_user_banned(target_id)),
            )
            return
        await message.reply_html(
            _user_card_text(target_id, info),
            reply_markup=keyboards.user_card_kb(target_id, bool(info.get("is_banned"))),
        )

    elif action == "set_size":
        if not text.isdigit() or int(text) <= 0:
            await message.reply_html("⚠️ لطفاً یک عدد صحیح و مثبت (بر حسب مگابایت) ارسال کنید.")
            return
        value = int(text)
        await db.set_setting("max_file_size_mb", str(value))
        utils.ADMIN_PENDING.pop(uid, None)
        logger.info("Admin action: max_file_size_mb set to %s", value)
        await message.reply_html(
            f"✅ حداکثر حجم فایل روی <b>{value} MB</b> تنظیم شد.",
            reply_markup=keyboards.admin_back_kb(),
        )

    elif action == "set_conc":
        if not text.isdigit() or not (1 <= int(text) <= 20):
            await message.reply_html("⚠️ لطفاً عددی بین ۱ تا ۲۰ ارسال کنید.")
            return
        value = int(text)
        await db.set_setting("max_concurrent_downloads", str(value))
        utils.ADMIN_PENDING.pop(uid, None)
        logger.info("Admin action: max_concurrent_downloads set to %s", value)
        await message.reply_html(
            f"✅ حداکثر دانلودهای همزمان روی <b>{value}</b> تنظیم شد (اعمال پس از ری‌استارت).",
            reply_markup=keyboards.admin_back_kb(),
        )

    elif action.startswith("fj_"):
        await force_join.handle_admin_input(update, context, action)


# --------------------------------------------------------------------------- #
# Broadcast
# --------------------------------------------------------------------------- #

async def _do_broadcast(context: ContextTypes.DEFAULT_TYPE, payload: dict[str, Any]) -> str:
    """Copy the confirmed message to every registered user.

    Blocked bots (Forbidden) are tracked and never crash the loop; flood
    limits are respected via RetryAfter handling and a small delay.
    """
    db = context.bot_data["db"]
    user_ids = await db.all_user_ids()
    total = len(user_ids)
    logger.info("Admin action: broadcast started for %d users", total)

    sent = failed = blocked = 0
    for target_id in user_ids:
        try:
            await context.bot.copy_message(
                chat_id=target_id, from_chat_id=payload["chat_id"], message_id=payload["message_id"]
            )
            sent += 1
        except RetryAfter as exc:
            await asyncio.sleep(float(exc.retry_after) + 1.0)
            try:
                await context.bot.copy_message(
                    chat_id=target_id, from_chat_id=payload["chat_id"], message_id=payload["message_id"]
                )
                sent += 1
            except TelegramError:
                failed += 1
        except Forbidden:
            blocked += 1
        except TelegramError:
            failed += 1
        await asyncio.sleep(0.05)

    logger.info("Admin action: broadcast done (sent=%s blocked=%s failed=%s)", sent, blocked, failed)
    return (
        "📢 <b>نتیجه پیام همگانی</b>\n\n"
        f"👥 کل کاربران: {total}\n"
        f"✅ ارسال‌شده: {sent}\n"
        f"🚫 ربات را بلاک کرده‌اند: {blocked}\n"
        f"❌ ناموفق: {failed}"
    )
