#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
╔══════════════════════════════════════════╗
   ANARCHY RELAY BOT — ربات پیام‌رسان شخصی
╚══════════════════════════════════════════╝

کاربرها به‌جای پیوی زدن، تو ربات استارت می‌زنن و پیامشون مستقیم برای
ادمین (تو) می‌ره؛ همراه با مشخصات کامل فرستنده و دکمه‌های رنگی.
جواب دادن فقط با ریپلای‌کردن به پیامِ همون کاربر انجام می‌شه، و اون
هم دقیقاً به‌صورت ریپلای روی همون پیام کاربر تحویلش می‌گیره.

نصب (ترموکس/لینوکس/ویندوز):
    pip install "python-telegram-bot==22.8" --break-system-packages

دکمه‌های رنگی از قابلیت جدید Bot API 9.4 استفاده می‌کنن؛ حتماً همین
ورژن کتابخونه رو نصب کن وگرنه رنگ‌ها نمایش داده نمی‌شن.

قبل از اجرا این دو مقدار رو زیر پر کن:
    BOT_TOKEN -> از @BotFather بگیر
    ADMIN_ID  -> آیدی عددیِ خودت، از @userinfobot بگیر

برای جوین اجباری:
    /panel بزن، دکمه‌ی جوین اجباری رو روشن کن، و قبلش با
    /setchannel @channel_username کانال رو تنظیم کن.
    ربات باید ادمین همون کانال باشه تا بتونه عضویت رو چک کنه.

⚠️ توکن رو هیچ‌وقت جایی (حتی توی چت با هوش مصنوعی) شیر نکن.
⚠️ اگه نسخه‌ی قبلی رو اجرا کرده بودی، فایل anarchy_relay.db رو پاک کن
   چون ساختار دیتابیس عوض شده.
"""

import html
import logging
import sqlite3
from datetime import datetime

from telegram import (
    Update,
    InlineKeyboardButton,
    InlineKeyboardMarkup,
    ReplyParameters,
    ReactionTypeEmoji,
)
from telegram.constants import ParseMode
from telegram.ext import (
    Application,
    CommandHandler,
    MessageHandler,
    CallbackQueryHandler,
    ContextTypes,
    filters,
)


import inspect as _inspect
_orig_IKB = InlineKeyboardButton
if "style" not in _inspect.signature(_orig_IKB.__init__).parameters:
    class InlineKeyboardButton(_orig_IKB):
        def __init__(self, *args, style=None, **kwargs):
            super().__init__(*args, **kwargs)

# ══════════════════════════ تنظیمات ══════════════════════════
BOT_TOKEN = "733605177:AAH140lKjygQdCXmYLTLK2ucsFx1rz05UyQ"
ADMIN_ID = 403618630                   # آیدی عددی خودت رو اینجا بذار
ADMIN_USERNAME = "HaMaGhT"
ADMIN_DISPLAY = "Anarchy ✨"
DB_PATH = "anarchy_relay.db"

MESSAGE_EFFECT_FIRE = "5104841245755180586"   # افکت 🔥 روی پیام تحویل (Bot API)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s",
)
logger = logging.getLogger("anarchy-relay")


# ══════════════════════ فونت شیک (یونیکد) ══════════════════════
def fancy(text: str) -> str:
    """تبدیل متن انگلیسی به فونت Sans-Serif Bold یونیکد."""
    out = []
    for ch in text:
        if "A" <= ch <= "Z":
            out.append(chr(0x1D5D4 + (ord(ch) - ord("A"))))
        elif "a" <= ch <= "z":
            out.append(chr(0x1D5EE + (ord(ch) - ord("a"))))
        elif "0" <= ch <= "9":
            out.append(chr(0x1D7EC + (ord(ch) - ord("0"))))
        else:
            out.append(ch)
    return "".join(out)


def esc(value) -> str:
    return html.escape(str(value)) if value is not None else ""


# ══════════════════════════ دیتابیس ══════════════════════════
db = sqlite3.connect(DB_PATH, check_same_thread=False)
db.row_factory = sqlite3.Row
db.execute("PRAGMA journal_mode=WAL")
db.execute("""
CREATE TABLE IF NOT EXISTS users (
    user_id INTEGER PRIMARY KEY,
    first_name TEXT,
    last_name TEXT,
    username TEXT,
    language_code TEXT,
    is_premium INTEGER DEFAULT 0,
    is_banned INTEGER DEFAULT 0,
    first_seen TEXT,
    message_count INTEGER DEFAULT 0
)
""")
db.execute("""
CREATE TABLE IF NOT EXISTS message_map (
    admin_message_id INTEGER PRIMARY KEY,
    user_id INTEGER NOT NULL,
    source_message_id INTEGER NOT NULL
)
""")
db.execute("""
CREATE TABLE IF NOT EXISTS settings (
    key TEXT PRIMARY KEY,
    value TEXT
)
""")
db.commit()


def upsert_user(tg_user):
    db.execute(
        """
        INSERT INTO users (user_id, first_name, last_name, username, language_code, is_premium, first_seen)
        VALUES (?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(user_id) DO UPDATE SET
            first_name=excluded.first_name,
            last_name=excluded.last_name,
            username=excluded.username,
            language_code=excluded.language_code,
            is_premium=excluded.is_premium
        """,
        (
            tg_user.id,
            tg_user.first_name,
            tg_user.last_name,
            tg_user.username,
            tg_user.language_code,
            1 if getattr(tg_user, "is_premium", False) else 0,
            datetime.now().strftime("%Y-%m-%d %H:%M"),
        ),
    )
    db.commit()
    return get_user(tg_user.id)


def get_user(user_id: int):
    cur = db.execute("SELECT * FROM users WHERE user_id=?", (user_id,))
    return cur.fetchone()


def set_banned(user_id: int, banned: bool):
    db.execute("UPDATE users SET is_banned=? WHERE user_id=?", (1 if banned else 0, user_id))
    db.commit()


def bump_message_count(user_id: int):
    db.execute("UPDATE users SET message_count = message_count + 1 WHERE user_id=?", (user_id,))
    db.commit()


def map_message(admin_message_id: int, user_id: int, source_message_id: int):
    db.execute(
        "INSERT OR REPLACE INTO message_map (admin_message_id, user_id, source_message_id) VALUES (?, ?, ?)",
        (admin_message_id, user_id, source_message_id),
    )
    db.commit()


def resolve_source(admin_message_id: int):
    cur = db.execute(
        "SELECT user_id, source_message_id FROM message_map WHERE admin_message_id=?",
        (admin_message_id,),
    )
    return cur.fetchone()


def list_banned():
    return db.execute("SELECT * FROM users WHERE is_banned=1").fetchall()


def get_setting(key: str, default=None):
    row = db.execute("SELECT value FROM settings WHERE key=?", (key,)).fetchone()
    return row["value"] if row else default


def set_setting(key: str, value: str):
    db.execute(
        "INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value",
        (key, value),
    )
    db.commit()


# ══════════════════════════ جوین اجباری ══════════════════════════
async def is_channel_member(bot, user_id: int) -> bool:
    if get_setting("force_join_enabled", "0") != "1":
        return True
    channel = get_setting("force_join_channel", "")
    if not channel:
        return True
    try:
        member = await bot.get_chat_member(chat_id=channel, user_id=user_id)
        return member.status in ("member", "administrator", "creator")
    except Exception as e:
        logger.warning("بررسی عضویت شکست خورد: %s", e)
        return True  # اگه تنظیمات خراب بود، ربات رو کامل قفل نکن


def force_join_keyboard(channel: str) -> InlineKeyboardMarkup:
    handle = channel.lstrip("@")
    return InlineKeyboardMarkup([[
        InlineKeyboardButton("📢 عضویت در کانال", url=f"https://t.me/{handle}", style="primary"),
        InlineKeyboardButton("✅ بررسی مجدد", callback_data="checkjoin", style="success"),
    ]])


async def send_join_gate(update: Update, context: ContextTypes.DEFAULT_TYPE):
    channel = get_setting("force_join_channel", "")
    text = (
        f"<b>🔒 {fancy('Locked')}</b>\n"
        "<i>برای استفاده از ربات باید عضو کانال بشی.</i>"
    )
    await update.effective_message.reply_html(text, reply_markup=force_join_keyboard(channel))


async def checkjoin_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    if await is_channel_member(context.bot, query.from_user.id):
        await query.answer("✅ عضو شدی، خوش اومدی!")
        await query.edit_message_text(
            f"<b>✅ {fancy('Verified')}</b>\n<i>حالا می‌تونی پیام بفرستی.</i>",
            parse_mode=ParseMode.HTML,
        )
    else:
        await query.answer("هنوز عضو نشدی! 🚫", show_alert=True)


# ══════════════════════════ کیبورد رنگی پیام‌ها ══════════════════════════
def build_card_keyboard(user_id: int, banned: bool, username) -> InlineKeyboardMarkup:
    row = []
    if username:
        row.append(InlineKeyboardButton("🔗 پروفایل", url=f"https://t.me/{username}", style="primary"))
    if banned:
        row.append(InlineKeyboardButton("✅ رفع بن", callback_data=f"unban:{user_id}", style="success"))
    else:
        row.append(InlineKeyboardButton("🚫 بن", callback_data=f"ban:{user_id}", style="danger"))
    return InlineKeyboardMarkup([row])


# ══════════════════════════ استارت ══════════════════════════
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.effective_user
    upsert_user(user)

    if user.id == ADMIN_ID:
        text = (
            f"<b>⚡️ {fancy('ANARCHY')} ⚡️</b>\n"
            "<i>سلام خودتی 😎 — پنل مدیریت:</i> /panel"
        )
        await update.message.reply_html(text)
        return

    if not await is_channel_member(context.bot, user.id):
        await send_join_gate(update, context)
        return

    text = (
        f"⚡️ <b>{fancy('ANARCHY')}</b> ⚡️\n"
        f"<i>پیامتو بفرست، مستقیم می‌رسه به</i> "
        f"<a href=\"https://t.me/{ADMIN_USERNAME}\">{esc(ADMIN_DISPLAY)}</a> 🔥"
    )
    await update.message.reply_html(text)


# ══════════════════════════ پیام کاربر → ادمین ══════════════════════════
async def relay_to_admin(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.effective_user

    if not await is_channel_member(context.bot, user.id):
        await send_join_gate(update, context)
        return

    row = upsert_user(user)
    if row["is_banned"]:
        await update.message.reply_text("🚫 دسترسی شما به این ربات مسدود شده است.")
        return

    bump_message_count(user.id)
    row = get_user(user.id)
    source_id = update.message.message_id

    full_name = esc(f"{user.first_name or ''} {user.last_name or ''}".strip())
    username_display = f"@{esc(user.username)}" if user.username else "ندارد"
    premium_display = "✅ بله" if row["is_premium"] else "❌ خیر"
    lang_display = esc(user.language_code) if user.language_code else "نامشخص"

    card = (
        "👤 <b>پیام جدید</b>\n"
        "━━━━━━━━━━━━\n"
        f"نام: <b>{full_name}</b>\n"
        f"یوزرنیم: {username_display}\n"
        f"آیدی عددی: <code>{user.id}</code>\n"
        f"زبان: {lang_display}\n"
        f"پرمیوم: {premium_display}\n"
        f"اولین پیام: {esc(row['first_seen'])}\n"
        f"تعداد پیام‌ها: {row['message_count']}\n"
        "━━━━━━━━━━━━\n"
        "<i>💬 برای پاسخ، روی این پیام یا پیام بعدی ریپلای کن.</i>"
    )

    keyboard = build_card_keyboard(user.id, bool(row["is_banned"]), user.username)

    card_msg = await context.bot.send_message(
        chat_id=ADMIN_ID,
        text=card,
        parse_mode=ParseMode.HTML,
        reply_markup=keyboard,
    )
    map_message(card_msg.message_id, user.id, source_id)

    content_msg = await context.bot.copy_message(
        chat_id=ADMIN_ID,
        from_chat_id=user.id,
        message_id=source_id,
    )
    map_message(content_msg.message_id, user.id, source_id)

    # واکنش سریع رو پیام کاربر، به‌جای اینکه فقط منتظر بمونه
    try:
        await context.bot.set_message_reaction(
            chat_id=user.id, message_id=source_id, reaction=[ReactionTypeEmoji(emoji="👀")]
        )
    except Exception as e:
        logger.warning("ریاکشن نگرفت: %s", e)

    # تاییدیه‌ی تحویل، به‌صورت ریپلای روی همون پیام کاربر
    try:
        await context.bot.send_message(
            chat_id=user.id,
            text=f"✅ {fancy('Delivered')}",
            reply_parameters=ReplyParameters(message_id=source_id, allow_sending_without_reply=True),
            message_effect_id=MESSAGE_EFFECT_FIRE,
        )
    except Exception as e:
        logger.warning("تاییدیه‌ی تحویل ارسال نشد: %s", e)


# ══════════════════════════ ریپلای ادمین → کاربر ══════════════════════════
async def admin_reply(update: Update, context: ContextTypes.DEFAULT_TYPE):
    reply_to = update.message.reply_to_message
    if not reply_to:
        await update.message.reply_text(
            "❕ برای پاسخ به یه کاربر، باید روی پیام همون کاربر ریپلای کنی."
        )
        return

    mapping = resolve_source(reply_to.message_id)
    if not mapping:
        await update.message.reply_text("⚠️ این پیام به هیچ کاربری وصل نیست.")
        return

    target_user_id = mapping["user_id"]
    source_message_id = mapping["source_message_id"]

    try:
        await context.bot.copy_message(
            chat_id=target_user_id,
            from_chat_id=ADMIN_ID,
            message_id=update.message.message_id,
            reply_parameters=ReplyParameters(
                message_id=source_message_id, allow_sending_without_reply=True
            ),
        )
        await update.message.reply_text("✅ ارسال شد.")
    except Exception as e:
        logger.warning("ارسال ریپلای شکست خورد: %s", e)
        await update.message.reply_text("❌ ارسال نشد؛ احتمالاً کاربر ربات رو بلاک کرده.")


# ══════════════════════════ دکمه‌های بن ══════════════════════════
async def ban_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    if query.from_user.id != ADMIN_ID:
        await query.answer("⛔ فقط ادمین.", show_alert=True)
        return

    action, uid_str = query.data.split(":", 1)
    user_id = int(uid_str)

    set_banned(user_id, action == "ban")
    row = get_user(user_id)

    await query.answer("🚫 بن شد." if action == "ban" else "✅ رفع بن شد.")
    await query.edit_message_reply_markup(
        reply_markup=build_card_keyboard(user_id, action == "ban", row["username"] if row else None)
    )


async def ban_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if update.effective_user.id != ADMIN_ID:
        return
    if not context.args:
        await update.message.reply_text("فرمت: /ban <آیدی عددی>")
        return
    try:
        uid = int(context.args[0])
    except ValueError:
        await update.message.reply_text("آیدی باید عدد باشه.")
        return
    set_banned(uid, True)
    await update.message.reply_text("🚫 کاربر بن شد.")


async def unban_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if update.effective_user.id != ADMIN_ID:
        return
    if not context.args:
        await update.message.reply_text("فرمت: /unban <آیدی عددی>")
        return
    try:
        uid = int(context.args[0])
    except ValueError:
        await update.message.reply_text("آیدی باید عدد باشه.")
        return
    set_banned(uid, False)
    await update.message.reply_text("✅ کاربر رفع بن شد.")


# ══════════════════════════ پنل مدیریت ══════════════════════════
def panel_main_keyboard() -> InlineKeyboardMarkup:
    fj_on = get_setting("force_join_enabled", "0") == "1"
    return InlineKeyboardMarkup([
        [
            InlineKeyboardButton("📊 آمار", callback_data="panel:stats", style="primary"),
            InlineKeyboardButton("🚫 بن‌شده‌ها", callback_data="panel:banlist", style="danger"),
        ],
        [
            InlineKeyboardButton(
                f"📢 جوین اجباری: {'روشن' if fj_on else 'خاموش'}",
                callback_data="panel:togglejoin",
                style="success" if fj_on else "danger",
            )
        ],
    ])


def panel_back_keyboard() -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup([[InlineKeyboardButton("🔙 بازگشت", callback_data="panel:back", style="primary")]])


async def panel_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if update.effective_user.id != ADMIN_ID:
        return
    text = f"<b>🎛 {fancy('PANEL')}</b>\n<i>مدیریت کامل ربات از همینجا</i>"
    await update.message.reply_html(text, reply_markup=panel_main_keyboard())


async def panel_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    if query.from_user.id != ADMIN_ID:
        await query.answer("⛔ فقط ادمین.", show_alert=True)
        return

    action = query.data.split(":", 1)[1]

    if action == "stats":
        total = db.execute("SELECT COUNT(*) c FROM users").fetchone()["c"]
        banned = db.execute("SELECT COUNT(*) c FROM users WHERE is_banned=1").fetchone()["c"]
        msgs = db.execute("SELECT COALESCE(SUM(message_count),0) c FROM users").fetchone()["c"]
        text = (
            f"<b>📊 {fancy('STATS')}</b>\n━━━━━━━━━━━━\n"
            f"👥 کاربران: <b>{total}</b>\n"
            f"🚫 بن‌شده: <b>{banned}</b>\n"
            f"💬 کل پیام‌ها: <b>{msgs}</b>"
        )
        await query.answer()
        await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=panel_back_keyboard())

    elif action == "banlist":
        banned = list_banned()
        if not banned:
            text = f"<i>{fancy('All clear')}</i> ✨\nهیچ کاربر بن‌شده‌ای نیست."
        else:
            lines = [f"<b>🚫 {fancy('BANNED')}</b>", "━━━━━━━━━━━━"]
            for u in banned:
                uname = f"@{esc(u['username'])}" if u["username"] else "بدون یوزرنیم"
                lines.append(f"• {esc(u['first_name'])} — {uname} — <code>{u['user_id']}</code>")
            text = "\n".join(lines)
        await query.answer()
        await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=panel_back_keyboard())

    elif action == "togglejoin":
        current = get_setting("force_join_enabled", "0") == "1"
        new_state = not current
        if new_state and not get_setting("force_join_channel", ""):
            await query.answer("اول با /setchannel کانال رو تنظیم کن.", show_alert=True)
            return
        set_setting("force_join_enabled", "1" if new_state else "0")
        await query.answer("✅ فعال شد." if new_state else "❌ غیرفعال شد.")
        await query.edit_message_reply_markup(reply_markup=panel_main_keyboard())

    elif action == "back":
        text = f"<b>🎛 {fancy('PANEL')}</b>\n<i>مدیریت کامل ربات از همینجا</i>"
        await query.answer()
        await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=panel_main_keyboard())


async def setchannel_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if update.effective_user.id != ADMIN_ID:
        return
    if not context.args:
        await update.message.reply_text("فرمت: /setchannel @channel_username")
        return
    channel = context.args[0]
    set_setting("force_join_channel", channel)
    await update.message.reply_html(
        f"✅ کانال تنظیم شد: <b>{esc(channel)}</b>\n"
        "<i>حواست باشه ربات باید ادمین همون کانال باشه.</i>"
    )


async def banlist_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if update.effective_user.id != ADMIN_ID:
        return
    banned = list_banned()
    if not banned:
        await update.message.reply_text("هیچ کاربر بن‌شده‌ای نیست. ✨")
        return
    lines = ["🚫 <b>لیست بن‌شده‌ها</b>", "━━━━━━━━━━━━"]
    for u in banned:
        uname = f"@{esc(u['username'])}" if u["username"] else "بدون یوزرنیم"
        lines.append(f"• {esc(u['first_name'])} — {uname} — <code>{u['user_id']}</code>")
    await update.message.reply_html("\n".join(lines))


async def error_handler(update, context: ContextTypes.DEFAULT_TYPE):
    logger.error("خطا: %s", context.error)


def main():
    if not BOT_TOKEN or BOT_TOKEN == "PUT_YOUR_BOT_TOKEN_HERE":
        raise SystemExit("توکن ربات رو بالای فایل جایگزین کن (BOT_TOKEN).")
    if not ADMIN_ID:
        raise SystemExit("آیدی عددی خودت رو بالای فایل جایگزین کن (ADMIN_ID).")

    app = Application.builder().token(BOT_TOKEN).build()

    app.add_handler(CommandHandler("start", start))
    app.add_handler(CommandHandler("panel", panel_command))
    app.add_handler(CommandHandler("setchannel", setchannel_command))
    app.add_handler(CommandHandler("ban", ban_command))
    app.add_handler(CommandHandler("unban", unban_command))
    app.add_handler(CommandHandler("banlist", banlist_command))

    app.add_handler(CallbackQueryHandler(ban_callback, pattern=r"^(ban|unban):\d+$"))
    app.add_handler(CallbackQueryHandler(checkjoin_callback, pattern=r"^checkjoin$"))
    app.add_handler(CallbackQueryHandler(panel_callback, pattern=r"^panel:"))

    app.add_handler(
        MessageHandler(filters.User(user_id=ADMIN_ID) & ~filters.COMMAND, admin_reply)
    )
    app.add_handler(
        MessageHandler(~filters.User(user_id=ADMIN_ID) & ~filters.COMMAND, relay_to_admin)
    )

    app.add_error_handler(error_handler)

    logger.info("ربات روشن شد ✨")
    app.run_polling(allowed_updates=Update.ALL_TYPES)


if __name__ == "__main__":
    main()
