"""
Force-join subsystem.

Membership is enforced through the Telegram Bot API (get_chat_member).
IMPORTANT: membership checking only works when the bot has appropriate access
to the target chat - the bot must be an administrator of a channel, or a
member of a group. Without that access get_chat_member fails and the user is
treated as "not a member" (with a warning in the log).

This module also contains the admin management UI (add/list/remove/toggle).
"""

from __future__ import annotations

import logging
import re
from typing import Any, Optional

from telegram import Update
from telegram.error import TelegramError
from telegram.ext import ContextTypes

import config
import keyboards
import utils

logger = logging.getLogger(__name__)

FORCE_SETTINGS_KEY = "force_join_enabled"

_MEMBER_STATUSES = {"member", "administrator", "creator"}


# --------------------------------------------------------------------------- #
# Settings / data access
# --------------------------------------------------------------------------- #

async def is_enabled(db) -> bool:
    """Effective force-join state (DB override wins over the .env default)."""
    value = await db.get_setting(FORCE_SETTINGS_KEY)
    if value is None:
        return config.FORCE_JOIN_ENABLED
    return value == "1"


async def set_enabled(db, enabled: bool) -> None:
    await db.set_setting(FORCE_SETTINGS_KEY, "1" if enabled else "0")


async def get_active_entries(db) -> list[dict[str, Any]]:
    return await db.list_force_join(active_only=True)


def _chat_id_value(raw: str) -> Any:
    """Convert a textual chat id into an int when possible (Bot API needs int)."""
    try:
        return int(raw)
    except (TypeError, ValueError):
        return raw


# --------------------------------------------------------------------------- #
# Membership checking
# --------------------------------------------------------------------------- #

def _is_member(member: Any) -> bool:
    """Interpret a ChatMember object (statuses per Bot API)."""
    status = getattr(member, "status", "")
    if status == "restricted":
        return bool(getattr(member, "is_member", False))
    return status in _MEMBER_STATUSES


async def check_membership(
    bot, user_id: int, entries: list[dict[str, Any]]
) -> tuple[bool, list[dict[str, Any]]]:
    """Check membership of `user_id` for every required entry.

    Returns (all_members, missing_entries). Any API error (bot not in chat,
    user unknown to the chat, network problem) counts as "not a member" so
    access fails closed.
    """
    missing: list[dict[str, Any]] = []
    for entry in entries:
        chat_id = _chat_id_value(str(entry.get("chat_id") or ""))
        try:
            member = await bot.get_chat_member(chat_id, user_id)
            if not _is_member(member):
                missing.append(entry)
        except TelegramError as exc:
            logger.warning(
                "Force join check failed for chat %s (does the bot have access?): %s",
                entry.get("chat_id"),
                exc,
            )
            missing.append(entry)
    return (not missing), missing


def build_page_text(entries: list[dict[str, Any]]) -> str:
    """User-facing force-join page listing every missing channel/group."""
    lines = [
        "🔒 <b>عضویت اجباری</b>",
        "",
        "برای استفاده از ربات ابتدا در کانال/گروه‌های زیر عضو شوید:",
        "",
    ]
    for index, entry in enumerate(entries, start=1):
        title = entry.get("title") or entry.get("username") or entry.get("chat_id") or "—"
        lines.append(f"{index}️⃣ {utils.escape_html(str(title))}")
    lines += ["", "پس از عضویت، دکمه «✅ بررسی عضویت» را بزنید."]
    return "\n".join(lines)


# --------------------------------------------------------------------------- #
# Admin UI (callbacks under the "fja:" prefix)
# --------------------------------------------------------------------------- #

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


async def render_admin_menu(query, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Render the Force Join admin submenu (callback `ad:fj`)."""
    db = context.bot_data["db"]
    enabled = await is_enabled(db)
    count = await db.count_force_join(active_only=True)
    text = (
        "🔗 <b>مدیریت Force Join</b>\n\n"
        f"وضعیت سیستم: {'✅ فعال' if enabled else '⛔️ غیرفعال'}\n"
        f"تعداد مقصدهای فعال: {count}\n\n"
        "⚠️ <i>نکته: برای بررسی عضویت، ربات باید به چت هدف دسترسی داشته باشد — "
        "در کانال‌ها ربات باید ادمین باشد و در گروه‌ها عضو.</i>"
    )
    await utils.safe_edit(query.message, text, keyboards.force_join_admin_kb())


def _is_chat_id(text: str) -> bool:
    if text.startswith("@") and len(text) >= 5:
        return True
    return bool(re.fullmatch(r"-?\d+", text))


async def handle_admin_input(update: Update, context: ContextTypes.DEFAULT_TYPE, action: str) -> None:
    """Handle the two-step 'add force join' text flow (called by admin module)."""
    user = update.effective_user
    message = update.message
    if user is None or message is None:
        return
    uid = user.id
    text = (message.text or "").strip()
    db = context.bot_data["db"]

    if action == "fj_add_id":
        if not _is_chat_id(text):
            await message.reply_html(
                "⚠️ فرمت نامعتبر است. یک یوزرنیم (<code>@channel</code>) یا شناسه عددی "
                "(<code>-1001234567890</code>) ارسال کنید. برای لغو /cancel را بزنید."
            )
            return
        utils.PENDING_DATA[uid] = {"chat_id": text}
        utils.ADMIN_PENDING[uid] = "fj_add_link"
        await message.reply_html("🔗 حالا لینک دعوت/عضویت را ارسال کنید (باید با https://t.me/ شروع شود):")

    elif action == "fj_add_link":
        data = utils.PENDING_DATA.get(uid) or {}
        chat_id = data.get("chat_id")
        if not chat_id:
            utils.ADMIN_PENDING.pop(uid, None)
            await message.reply_html("❌ خطای وضعیت. لطفاً دوباره از منوی Force Join تلاش کنید.")
            return
        if not text.startswith(("https://t.me/", "http://t.me/", "https://telegram.me/", "http://telegram.me/")):
            await message.reply_html("⚠️ لینک باید با https://t.me/ شروع شود. برای لغو /cancel را بزنید.")
            return

        title: Optional[str] = None
        username: Optional[str] = None
        chat_type: Optional[str] = None
        warning = ""
        try:
            chat = await context.bot.get_chat(_chat_id_value(chat_id))
            title = chat.title or chat.username or str(chat_id)
            username = chat.username
            chat_type = chat.type
        except TelegramError as exc:
            logger.warning("Could not fetch chat info for %s: %s", chat_id, exc)
            warning = (
                "\n\n⚠️ <b>توجه:</b> ربات نتوانست اطلاعات این چت را دریافت کند. "
                "برای اینکه بررسی عضویت کار کند، ربات باید در کانال ادمین شود یا در گروه عضو باشد."
            )

        entry_id = await db.add_force_join(str(chat_id), username, title, text, chat_type)
        utils.ADMIN_PENDING.pop(uid, None)
        utils.PENDING_DATA.pop(uid, None)
        logger.info("Admin action: force join entry %s added (%s)", entry_id, chat_id)
        await message.reply_html(
            f"✅ مقصد Force Join با شناسه <code>{entry_id}</code> ثبت شد.{warning}",
            reply_markup=keyboards.admin_back_kb(),
        )


async def handle_admin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Route Force Join admin callbacks (pattern ^fja:)."""
    query = update.callback_query
    if query is None or not _guard_admin(update):
        if query is not None:
            await query.answer("❌ دسترسی غیرمجاز.", show_alert=True)
        return
    await query.answer()

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

    if action == "add":
        utils.ADMIN_PENDING[uid] = "fj_add_id"
        utils.PENDING_DATA.pop(uid, None)
        await utils.safe_edit(
            query.message,
            "➕ <b>افزودن Force Join</b>\n\nشناسه یا یوزرنیم کانال/گروه را ارسال کنید:\n"
            "(مثال: <code>@mychannel</code> یا <code>-1001234567890</code>)",
            keyboards.admin_cancel_kb(),
        )

    elif action == "list":
        entries = await db.list_force_join(active_only=False)
        if entries:
            await utils.safe_edit(
                query.message,
                f"📋 <b>لیست مقصدهای Force Join</b> ({len(entries)} مورد)\n\nبرای جزئیات روی هر مورد بزنید:",
                keyboards.force_join_list_kb(entries),
            )
        else:
            await utils.safe_edit(
                query.message, "📋 هیچ مقصدی ثبت نشده است.", keyboards.force_join_list_kb([])
            )

    elif action == "remove":
        entries = await db.list_force_join(active_only=False)
        if entries:
            await utils.safe_edit(
                query.message,
                "❌ <b>حذف Force Join</b>\n\nروی مورد موردنظر بزنید تا حذف شود:",
                keyboards.force_join_remove_kb(entries),
            )
        else:
            await utils.safe_edit(
                query.message, "❌ چیزی برای حذف وجود ندارد.", keyboards.force_join_admin_kb()
            )

    elif action.startswith("rm:"):
        try:
            entry_id = int(action.split(":")[1])
        except (IndexError, ValueError):
            return
        await db.remove_force_join(entry_id)
        logger.info("Admin action: force join entry %s removed", entry_id)
        entries = await db.list_force_join(active_only=False)
        if entries:
            await utils.safe_edit(query.message, "✅ حذف شد.", keyboards.force_join_remove_kb(entries))
        else:
            await utils.safe_edit(
                query.message, "✅ حذف شد. لیست اکنون خالی است.", keyboards.force_join_admin_kb()
            )

    elif action == "toggle":
        enabled = await is_enabled(db)
        entries = await db.list_force_join(active_only=False)
        await utils.safe_edit(
            query.message,
            "🔄 <b>فعال/غیرفعال کردن Force Join</b>\n\nوضعیت کلی سیستم یا هر مورد را جداگانه تغییر دهید:",
            keyboards.force_join_toggle_kb(enabled, entries),
        )

    elif action == "global":
        current = await is_enabled(db)
        await set_enabled(db, not current)
        logger.info("Admin action: force join system toggled to %s", not current)
        entries = await db.list_force_join(active_only=False)
        await utils.safe_edit(
            query.message,
            f"✅ سیستم Force Join اکنون {'فعال' if not current else 'غیرفعال'} است.",
            keyboards.force_join_toggle_kb(not current, entries),
        )

    elif action.startswith("en:"):
        try:
            entry_id = int(action.split(":")[1])
        except (IndexError, ValueError):
            return
        entry = await db.get_force_join(entry_id)
        if entry is not None:
            await db.set_force_join_active(entry_id, not entry["is_active"])
            logger.info("Admin action: force join entry %s active=%s", entry_id, not entry["is_active"])
        entries = await db.list_force_join(active_only=False)
        enabled = await is_enabled(db)
        await utils.safe_edit(
            query.message, "🔄 وضعیت به‌روزرسانی شد.", keyboards.force_join_toggle_kb(enabled, entries)
        )

    elif action.startswith("info:"):
        try:
            entry_id = int(action.split(":")[1])
        except (IndexError, ValueError):
            return
        entry = await db.get_force_join(entry_id)
        if entry is not None:
            await query.answer(
                f"شناسه چت: {entry.get('chat_id')}\n"
                f"عنوان: {entry.get('title') or '—'}\n"
                f"نوع: {entry.get('chat_type') or '—'}\n"
                f"وضعیت: {'فعال' if entry.get('is_active') else 'غیرفعال'}",
                show_alert=True,
            )
