#!/usr/bin/env python3
"""
Telegram APK Uploader Bot (restricted user IDs)
"""

import asyncio
import hashlib
import os
import re
import shutil
import tempfile
from typing import Dict, Tuple

from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, ContextTypes, filters

HTML_ROOT = "/var/www/html"
VALID_DIR_RE = re.compile(r"^[A-Za-z0-9._-]+$")

TELEGRAM_BOT_TOKEN = "7620960259:AAE6EnEOgk49xeCkDtkFxT2leReWfVLUVLM"
valid_ids = [7584587189,1974183073, 6085125219,5153735007,8165529903,5231134253,6423406587]
user_dirs: Dict[int, str] = {}

# ---------- Helpers ----------

def sha256_and_md5(path: str) -> Tuple[str, str]:
    sha256 = hashlib.sha256()
    md5 = hashlib.md5()
    with open(path, 'rb') as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b''):
            sha256.update(chunk)
            md5.update(chunk)
    return sha256.hexdigest(), md5.hexdigest()

def ensure_within_root(path: str) -> None:
    real_root = os.path.realpath(HTML_ROOT)
    real_path = os.path.realpath(path)
    if not real_path.startswith(real_root + os.sep) and real_path != real_root:
        raise PermissionError("Path escapes HTML_ROOT")

def fix_permissions(dir_path: str, file_path: str) -> None:
    try: os.chmod(dir_path, 0o755)
    except Exception: pass
    try: os.chmod(file_path, 0o644)
    except Exception: pass

async def check_access(update: Update) -> bool:
    if update.effective_user.id not in valid_ids:
        await update.effective_chat.send_message("⛔️ دسترسی غیرمجاز.")
        return False
    return True

# ---------- Command Handlers ----------

async def cmd_ls(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not await check_access(update): return
    try:
        entries = sorted(e.name for e in os.scandir(HTML_ROOT) if e.is_dir())
        if entries:
            msg = "📂 پوشه‌های موجود:\n" + "\n".join(f"▫️ {name}" for name in entries)
            await update.effective_chat.send_message(msg)
        else:
            await update.effective_chat.send_message("⚠️ هیچ پوشه‌ای یافت نشد.")
    except Exception as e:
        await update.effective_chat.send_message(f"❌ خطا در لیست پوشه‌ها: {e}")

async def cmd_set(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not await check_access(update): return
    if not context.args: 
        await update.effective_chat.send_message("📌 دستور: /set <پوشه>")
        return
    dirname = context.args[0].strip()
    if not VALID_DIR_RE.match(dirname):
        await update.effective_chat.send_message("❌ نام پوشه نامعتبر است.")
        return
    dir_path = os.path.join(HTML_ROOT, dirname)
    try:
        ensure_within_root(dir_path)
        if not os.path.isdir(dir_path):
            await update.effective_chat.send_message("❌ پوشه وجود ندارد.")
            return
        user_dirs[update.effective_user.id] = dirname
        await update.effective_chat.send_message(f"✅ پوشه مقصد روی '{dirname}' تنظیم شد.")
    except Exception as e:
        await update.effective_chat.send_message(f"❌ خطا: {e}")

# ---------- Document Handler ----------

async def on_document(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not await check_access(update): return
    doc = update.message.document
    if not doc or not doc.file_name.lower().endswith('.apk'):
        await update.effective_chat.send_message("❌ فقط فایل .apk پذیرفته می‌شود.")
        return

    user_id = update.effective_user.id
    dirname = user_dirs.get(user_id)
    if not dirname:
        await update.effective_chat.send_message("⚠️ ابتدا با /set پوشه مقصد را انتخاب کنید.")
        return

    target_dir = os.path.join(HTML_ROOT, dirname)
    target_file = os.path.join(target_dir, "base.apk")

    try:
        ensure_within_root(target_dir)
        tf = tempfile.NamedTemporaryFile(delete=False)
        tmp_path = tf.name
        tf.close()
        tg_file = await context.bot.get_file(doc.file_id)
        await tg_file.download_to_drive(tmp_path)

        new_sha256, new_md5 = sha256_and_md5(tmp_path)

        existed = os.path.exists(target_file)
        old_sha256 = old_md5 = None
        if existed:
            try: old_sha256, old_md5 = sha256_and_md5(target_file)
            except Exception: existed = False

        if existed and old_sha256 and old_md5:
            equal = (new_sha256==old_sha256) and (new_md5==old_md5)
            if equal:
                await update.effective_chat.send_message(
                    f"⚠️ فایل‌ها یکسان هستند. آپلود لغو شد.", parse_mode=None
                )
                os.unlink(tmp_path)
                return
            else:
                os.replace(tmp_path, target_file)
                fix_permissions(target_dir, target_file)
                await update.effective_chat.send_message(
                    f"✅ فایل جایگزین شد در {target_dir}/base.apk", parse_mode=None
                )
        else:
            os.makedirs(target_dir, exist_ok=True)
            os.replace(tmp_path, target_file)
            fix_permissions(target_dir, target_file)
            await update.effective_chat.send_message(
                f"✅ فایل ذخیره شد در {target_dir}/base.apk", parse_mode=None
            )

    except Exception as e:
        try: os.unlink(tmp_path)
        except Exception: pass
        await update.effective_chat.send_message(f"❌ خطا: {e}")

async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if not await check_access(update): return
    await update.effective_chat.send_message(
        "👋 سلام! من ربات آپلود APK هستم.\n/ls — لیست پوشه‌ها.\n/set <پوشه> — انتخاب پوشه.\nسپس فایل .apk بفرستید.", parse_mode=None
    )

def main() -> None:
    if not TELEGRAM_BOT_TOKEN:
        raise RuntimeError("توکن ربات را تنظیم کنید")
    app = ApplicationBuilder().token(TELEGRAM_BOT_TOKEN).build()
    app.add_handler(CommandHandler(["start","help"], cmd_start))
    app.add_handler(CommandHandler("ls", cmd_ls))
    app.add_handler(CommandHandler("set", cmd_set))
    app.add_handler(MessageHandler(filters.Document.ALL, on_document))
    app.run_polling(close_loop=False)

if __name__ == "__main__":
    main()
