import asyncio
import logging
import random
import os
import datetime
import httpx
import io
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ForceReply
from telegram.constants import ParseMode
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, CallbackQueryHandler, ContextTypes, filters
from app.config import *
from app import database as db
from app.services import locket, nextdns

logger = logging.getLogger(__name__)

request_queue = asyncio.Queue()
pending_items = []
queue_lock = asyncio.Lock()

class Clr:
    HEADER = '\033[95m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'

async def update_pending_positions(app):
    for i, item in enumerate(pending_items):
        position = i + 1
        ahead = i
        try:
            # Update position text
            await app.bot.edit_message_text(
                chat_id=item['chat_id'],
                message_id=item['message_id'],
                text=T("queued", item['lang']).format(item['username'], position, ahead),
                parse_mode=ParseMode.HTML
            )
            
            # Notify if almost turn (ahead == 2)
            if ahead == 2:
                try:
                    await app.bot.send_message(
                        chat_id=item['chat_id'],
                        text=T("queue_almost", item['lang']),
                        parse_mode=ParseMode.HTML
                    )
                except:
                    pass
        except:
            pass

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    lang = db.get_lang(user_id) or DEFAULT_LANG
    
    if not db.get_user_usage(user_id):
        pass 

    await update.message.reply_text(
        T("welcome", lang),
        parse_mode=ParseMode.HTML,
        reply_markup=get_main_menu_keyboard(lang)
    )

async def setlang_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await show_language_select(update)

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    lang = db.get_lang(user_id) or DEFAULT_LANG
    
    help_text = T("help_msg", lang)
    if user_id == ADMIN_ID:
        help_text += (
            f"\n\n<b>👑 Admin Control:</b>\n"
            f"/noti [msg] - Broadcast message\n"
            f"/rs [id] - Reset usage limit\n"
            f"/setdonate - Set success photo\n"
            f"/stats - View detailed statistics"
        )
        
    await update.message.reply_text(help_text, parse_mode=ParseMode.HTML)

async def stats_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id != ADMIN_ID: return

    stats = db.get_stats()
    msg = (
        f"{E_STAT} <b>THỐNG KÊ HỆ THỐNG</b>\n"
        f"━━━━━━━━━━━━━━━━━━━\n"
        f"{E_USER} <b>Người dùng</b>: {stats['unique_users']}\n"
        f"{E_GLOBE} <b>Tổng yêu cầu</b>: {stats['total']}\n"
        f"{E_SUCCESS} <b>Thành công</b>: {stats['success']}\n"
        f"{E_ERROR} <b>Thất bại</b>: {stats['fail']}\n"
        f"━━━━━━━━━━━━━━━━━━━\n"
        f"{E_ANDROID} <b>Workers đang chạy</b>: {NUM_WORKERS}\n"
        f"🔑 <b>Bộ Token</b>: {len(TOKEN_SETS)}\n"
        f"⏳ <b>Hàng chờ</b>: {request_queue.qsize()}\n"
    )
    await update.message.reply_text(msg, parse_mode=ParseMode.HTML)

# --- Admin Commands ---
async def broadcast_worker(bot, users, text, chat_id, message_id):
    success = 0
    fail = 0
    total = len(users)
    
    for i, uid in enumerate(users):
        try:
            await bot.send_message(chat_id=uid, text=f"📢 <b>ADMIN NOTIFICATION</b>\n\n{text}", parse_mode=ParseMode.HTML)
            success += 1
        except Exception:
            fail += 1
            
        # Update progress every 5 users or at the end
        if (i + 1) % 5 == 0 or (i + 1) == total:
            try:
                await bot.edit_message_text(
                    chat_id=chat_id,
                    message_id=message_id,
                    text=(
                        f"{E_LOADING} <b>Broadcasting...</b>\n"
                        f"━━━━━━━━━━━━━━━━━━━\n"
                        f"🔄 <b>Progress</b>: {i+1}/{total}\n"
                        f"{E_SUCCESS} <b>Success</b>: {success}\n"
                        f"{E_ERROR} <b>Failed</b>: {fail}"
                    ),
                    parse_mode=ParseMode.HTML
                )
            except:
                pass
        
        await asyncio.sleep(0.05) # Prevent flood limits

    # Final completion message
    try:
        await bot.edit_message_text(
            chat_id=chat_id,
            message_id=message_id,
            text=(
                f"{E_SUCCESS} <b>Broadcast Complete!</b>\n"
                f"━━━━━━━━━━━━━━━━━━━\n"
                f"👥 <b>Total</b>: {total}\n"
                f"{E_SUCCESS} <b>Success</b>: {success}\n"
                f"{E_ERROR} <b>Failed</b>: {fail}"
            ),
            parse_mode=ParseMode.HTML
        )
    except:
        pass

async def noti_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    lang = db.get_lang(user_id) or DEFAULT_LANG
    
    if user_id != ADMIN_ID:
        return
        
    msg = " ".join(context.args)
    if not msg:
        await update.message.reply_text("Usage: /noti {message}")
        return

    users = db.get_all_users()
    if not users:
        await update.message.reply_text("No users found.")
        return

    status_msg = await update.message.reply_text(
        f"{E_LOADING} <b>Starting broadcast to {len(users)} users...</b>",
        parse_mode=ParseMode.HTML
    )
    
    asyncio.create_task(broadcast_worker(context.bot, users, msg, status_msg.chat_id, status_msg.message_id))

async def reset_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    lang = db.get_lang(user_id) or DEFAULT_LANG
    
    if user_id != ADMIN_ID:
        return

    if not context.args:
        await update.message.reply_text("Usage: /rs {user_id}")
        return
        
    try:
        target_id = int(context.args[0])
        db.reset_usage(target_id)
        await update.message.reply_text(T("admin_reset", lang).format(target_id))
    except ValueError:
        await update.message.reply_text("Invalid User ID")

async def set_donate_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user_id = update.effective_user.id
    if user_id != ADMIN_ID:
        return

    photo = None
    if update.message.reply_to_message and update.message.reply_to_message.photo:
        photo = update.message.reply_to_message.photo[-1]
    elif update.message.photo:
        photo = update.message.photo[-1]
        
    if photo:
        file_id = photo.file_id
        db.set_config("donate_photo", file_id)
        await update.message.reply_text(f"✅ Updated Donate Photo ID:\n<code>{file_id}</code>", parse_mode=ParseMode.HTML)
    else:
        await update.message.reply_text("❌ Please reply to a photo with /setdonate to set it.")

async def show_language_select(update: Update):
    keyboard = [
        [InlineKeyboardButton("Tiếng Việt 🇻🇳", callback_data="setlang_VI")],
        [InlineKeyboardButton("English 🇺🇸", callback_data="setlang_EN")]
    ]
    text = T("lang_select", "EN")
    
    if update.callback_query:
        await update.callback_query.edit_message_text(text, reply_markup=InlineKeyboardMarkup(keyboard))
    else:
        await update.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard))

async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # Only process if it's a reply to the bot's prompt (ForceReply)
    if not update.message.reply_to_message or not update.message.reply_to_message.from_user.is_bot:
        return

    user_id = update.effective_user.id
    text = update.message.text.strip()
    lang = db.get_lang(user_id) or DEFAULT_LANG

    if "locket.cam/" in text:
        username = text.split("locket.cam/")[-1].split("?")[0]
    elif len(text) < 50 and " " not in text:
        username = text
    else:
        username = text

    msg = await update.message.reply_text(T("resolving", lang), parse_mode=ParseMode.HTML)
    
    uid = await locket.resolve_uid(username)
    if not uid:
        await msg.edit_text(T("not_found", lang), parse_mode=ParseMode.HTML)
        return
        
    # Admin bypass limit check
    if user_id != ADMIN_ID and not db.check_can_request(user_id):
        await msg.edit_text(T("limit_reached", lang), parse_mode=ParseMode.HTML)
        return
        
    await msg.edit_text(T("checking_status", lang), parse_mode=ParseMode.HTML)
    status = await locket.check_status(uid)
    
    safe_username = username[:30]
    
    # Nếu đã có Gold → thông báo và gửi DNS
    if status and status.get("active"):
        status_text = T("gold_active", lang).format(status['expires'])
        
        # Tạo DNS profile
        pid, link = await nextdns.create_profile(NEXTDNS_KEY)
        
        dns_text = ""
        if link:
            dns_text = T('dns_msg', lang).format(link, pid)
        
        await msg.edit_text(
            f"{E_SUCCESS} <b>Bạn đã có Gold rồi!</b>\n\n"
            f"{E_ID}: <code>{uid}</code>\n"
            f"{E_TAG}: <code>{username}</code>\n"
            f"{E_STAT} <b>Trạng thái</b>: {status_text}\n\n"
            f"{E_SHIELD} <b>Chỉ cần cài DNS để giữ Gold vĩnh viễn:</b>\n"
            f"{dns_text}",
            parse_mode=ParseMode.HTML,
            disable_web_page_preview=True
        )
        
        # Gửi file .mobileconfig
        if pid:
            try:
                import os
                mobileconfig_path = nextdns.generate_mobileconfig(pid, username)
                with open(mobileconfig_path, 'rb') as f:
                    await update.message.reply_document(
                        document=f,
                        filename=f"DNS_{username}.mobileconfig",
                        caption=f"{E_SHIELD} <b>File cài DNS cho iOS</b>\n📲 Tải file → Mở bằng Safari → Cài đặt Profile",
                        parse_mode=ParseMode.HTML
                    )
                os.remove(mobileconfig_path)
            except Exception as e:
                logger.error(f"Send mobileconfig error: {e}")
        return
    
    # Chưa có Gold → hiện nút kích hoạt
    status_text = T("free_status", lang)
    keyboard = [[InlineKeyboardButton(T("btn_upgrade", lang), callback_data=f"upg|{uid}|{safe_username}")]]
    reply_markup = InlineKeyboardMarkup(keyboard)
    
    await msg.edit_text(
        f"{T('user_info_title', lang)}\n"
        f"{E_ID}: <code>{uid}</code>\n"
        f"{E_TAG}: <code>{username}</code>\n"
        f"{E_STAT} <b>Trạng thái</b>: {status_text}\n\n"
        f"👇",
        parse_mode=ParseMode.HTML,
        reply_markup=reply_markup
    )

async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    data = query.data
    user_id = query.from_user.id
    lang = db.get_lang(user_id) or DEFAULT_LANG

    if data.startswith("setlang_"):
        new_lang = data.split("_")[1]
        db.set_lang(user_id, new_lang)
        lang = new_lang
        await query.answer(f"Language: {new_lang}")
        await query.message.edit_text(
            T("menu_msg", lang),
            parse_mode=ParseMode.HTML,
            reply_markup=get_main_menu_keyboard(lang)
        )
        return

    if data == "menu_lang":
        await show_language_select(update)
        return
        
    if data == "menu_help":
        help_text = T("help_msg", lang)
        if user_id == ADMIN_ID:
            help_text += (
                f"\n\n<b>👑 Admin Control:</b>\n"
                f"/noti [msg] - Broadcast message\n"
                f"/rs [id] - Reset usage limit\n"
                f"/setdonate - Set success photo\n"
                f"/stats - View detailed statistics"
            )
            
        await query.edit_message_text(
            help_text,
            parse_mode=ParseMode.HTML,
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("🔙 Back", callback_data="menu_back")]])
        )
        return

    if data == "menu_back":
        await query.message.edit_text(
            T("menu_msg", lang),
            parse_mode=ParseMode.HTML,
            reply_markup=get_main_menu_keyboard(lang)
        )
        return

    if data == "menu_input":
        try:
            await query.answer()
        except:
            pass
        await query.message.reply_text(
            T("prompt_input", lang),
            parse_mode=ParseMode.HTML,
            reply_markup=ForceReply(selective=True, input_field_placeholder="Username...")
        )
        return

    if data.startswith("upg|"):
        parts = data.split("|")
        uid = parts[1]
        username = parts[2] if len(parts) > 2 else uid
        
        # Admin bypass limit check
        if user_id != ADMIN_ID and not db.check_can_request(user_id):
            try:
                await query.answer(T("limit_reached", lang), show_alert=True)
            except:
                pass
            return
            
        try:
            await query.answer("🚀 Queue...")
        except:
            pass
        
        item = {
            'user_id': user_id,
            'uid': uid,
            'username': username,
            'chat_id': query.message.chat_id,
            'message_id': query.message.message_id,
            'lang': lang
        }
        
        async with queue_lock:
            pending_items.append(item)
            position = len(pending_items)
            ahead = position - 1
        
        await query.edit_message_text(
            T("queued", lang).format(username, position, ahead),
            parse_mode=ParseMode.HTML
        )
        
        await request_queue.put(item)
        return

async def queue_worker(app, worker_id):
    print(f"Worker #{worker_id} started...")
    
    while True:
        token_idx = None
        try:
            item = await request_queue.get()
            
            user_id = item['user_id']
            uid = item['uid']
            username = item['username']
            chat_id = item['chat_id']
            message_id = item['message_id']
            lang = item['lang']
            
            async with queue_lock:
                if item in pending_items:
                    pending_items.remove(item)
                await update_pending_positions(app) # Enabled queue updates
            
            async def edit(text):
                try:
                    await app.bot.edit_message_text(
                        chat_id=chat_id,
                        message_id=message_id,
                        text=text,
                        parse_mode=ParseMode.HTML,
                        disable_web_page_preview=True
                    )
                except Exception as e:
                    if "Message is not modified" in str(e):
                        pass
                    elif "Message to edit not found" in str(e):
                        pass
                    else:
                        logger.error(f"Edit msg error: {e}")

            # Double check limit before processing (unless admin)
            if user_id != ADMIN_ID and not db.check_can_request(user_id):
                await edit(T("limit_reached", lang))
                continue
            
            # Safely acquire an available token with cooldown enforcement
            token_idx, token_config = await TOKEN_MANAGER.acquire_token(cooldown_seconds=15)
            token_name = f"Token-{token_idx+1}"
            
            print(f"{Clr.BLUE}[Worker #{worker_id}][{token_name}] Processing:{Clr.ENDC} UID={uid} | UserID={user_id}")
            
            logs = [f"[Worker #{worker_id}][{token_name}] Processing Request..."]
            loop = asyncio.get_running_loop()
            
            def safe_log_callback(msg):
                clean_msg = msg.replace(Clr.BLUE, "").replace(Clr.GREEN, "").replace(Clr.WARNING, "").replace(Clr.FAIL, "").replace(Clr.ENDC, "").replace(Clr.BOLD, "")
                logs.append(clean_msg)
                asyncio.run_coroutine_threadsafe(update_log_ui(), loop)

            async def update_log_ui():
                display_logs = "\n".join(logs[-10:])
                text = (
                    f"{E_LOADING} <b>⚡ SYSTEM EXPLOIT RUNNING...</b>\n"
                    f"<pre>{display_logs}</pre>"
                )
                try:
                    await app.bot.edit_message_text(
                        chat_id=chat_id,
                        message_id=message_id,
                        text=text,
                        parse_mode=ParseMode.HTML,
                        disable_web_page_preview=True
                    )
                except:
                    pass

            await update_log_ui()
            
            # Use dynamic token config
            success, msg_result = await locket.inject_gold(uid, token_config, safe_log_callback)
            
            # Log request to DB
            db.log_request(user_id, uid, "SUCCESS" if success else "FAIL")
            
            if success:
                if user_id != ADMIN_ID:
                    db.increment_usage(user_id)
                    
                pid, link = await nextdns.create_profile(NEXTDNS_KEY, safe_log_callback)
                
                dns_text = ""
                if link:
                   dns_text = T('dns_msg', lang).format(link, pid)
                else:
                   dns_text = f"{E_ERROR} NextDNS Error: Check API Key"
                
                final_msg = (
                    f"{T('success_title', lang)}\n\n"
                    f"{E_TAG}: <code>{username}</code>\n"
                    f"{E_ID}: <code>{uid}</code>\n"
                    f"{E_CALENDAR} <b>Plan</b>: Gold (30d)\n"
                    f"{dns_text}"
                )
                
                await asyncio.sleep(2.0)
                
                # Delete progress message and send photo with caption
                try:
                    await app.bot.delete_message(chat_id=chat_id, message_id=message_id)
                except:
                    pass
                
                try:
                    current_photo = db.get_config("donate_photo", DONATE_PHOTO)
                    await app.bot.send_photo(
                        chat_id=chat_id,
                        photo=current_photo,
                        caption=final_msg,
                        parse_mode=ParseMode.HTML
                    )
                except Exception as e:
                    logger.error(f"Send photo error: {e}")
                    # Fallback to text if photo fails
                    await app.bot.send_message(
                        chat_id=chat_id,
                        text=final_msg,
                        parse_mode=ParseMode.HTML,
                        disable_web_page_preview=True
                    )
                
                # Gửi file .mobileconfig tùy chỉnh qua Telegram
                if pid:
                    try:
                        import os
                        mobileconfig_path = nextdns.generate_mobileconfig(pid, username)
                        with open(mobileconfig_path, 'rb') as f:
                            await app.bot.send_document(
                                chat_id=chat_id,
                                document=f,
                                filename=f"DNS_{username}.mobileconfig",
                                caption=f"{E_SHIELD} <b>File cài DNS cho iOS</b>\n📲 Tải file → Mở bằng Safari → Cài đặt Profile",
                                parse_mode=ParseMode.HTML
                            )
                        # Xóa file tạm
                        os.remove(mobileconfig_path)
                    except Exception as e:
                        logger.error(f"Send mobileconfig error: {e}")
            else:
                final_msg = f"{T('fail_title', lang)}\nInfo:\n<code>{msg_result}</code>"
                await edit(final_msg)
                
        except Exception as e:
            logger.error(f"Worker #{worker_id} Exception: {e}")
        finally:
            if token_idx is not None:
                TOKEN_MANAGER.release_token(token_idx)
            try:
                request_queue.task_done()
            except:
                pass

def get_main_menu_keyboard(lang):
    return InlineKeyboardMarkup([
        [InlineKeyboardButton(T("btn_input", lang), callback_data="menu_input")],
        [InlineKeyboardButton(T("btn_lang", lang), callback_data="menu_lang"),
         InlineKeyboardButton(T("btn_help", lang), callback_data="menu_help")]
    ])

def get_seconds_until_midnight_vn():
    now_utc = datetime.datetime.now(datetime.timezone.utc)
    vn_tz = datetime.timezone(datetime.timedelta(hours=7))
    now_vn = now_utc.astimezone(vn_tz)
    tomorrow_vn = now_vn + datetime.timedelta(days=1)
    midnight_vn = datetime.datetime(
        tomorrow_vn.year, tomorrow_vn.month, tomorrow_vn.day,
        0, 0, 0, tzinfo=vn_tz
    )
    delta = midnight_vn - now_vn
    return int(delta.total_seconds())

async def perform_database_backup(app):
    laravel_url = os.environ.get('LARAVEL_API_URL', 'http://127.0.0.1:8000')
    api_key = os.environ.get('ADMIN_API_KEY', 'locket-gold-secret-2024')
    url = f"{laravel_url}/api/backup-mysql?token={api_key}"
    logger.info(f"[BACKUP SCHEDULER] Fetching database backup from {url}...")
    try:
        async with httpx.AsyncClient(timeout=120.0) as client:
            response = await client.get(url)
            if response.status_code == 200:
                file_content = response.content
                filename = f"locketgold_backup_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.sql"
                bio = io.BytesIO(file_content)
                bio.name = filename
                
                logger.info(f"[BACKUP SCHEDULER] Sending backup document to admin {ADMIN_ID}...")
                await app.bot.send_document(
                    chat_id=ADMIN_ID,
                    document=bio,
                    filename=filename,
                    caption=f"📦 <b>BẢN SAO LƯU DATABASE TỰ ĐỘNG</b>\n\n🕒 Thời gian: 00:00 (Giờ VN)\n🗄️ Tên file: {filename}\n⚡ Dung lượng: {len(file_content)} bytes",
                    parse_mode=ParseMode.HTML
                )
                logger.info("[BACKUP SCHEDULER] Scheduled backup sent successfully!")
            else:
                logger.error(f"[BACKUP SCHEDULER] Error fetching backup: HTTP {response.status_code}")
    except Exception as e:
        logger.error(f"[BACKUP SCHEDULER] Exception during backup process: {e}")

async def backup_scheduler(app):
    logger.info("[BACKUP SCHEDULER] Started background backup task.")
    while True:
        seconds = get_seconds_until_midnight_vn()
        logger.info(f"[BACKUP SCHEDULER] Next backup at 00:00 VN time. Sleeping for {seconds} seconds...")
        await asyncio.sleep(seconds)
        try:
            await perform_database_backup(app)
        except Exception as e:
            logger.error(f"[BACKUP SCHEDULER] Error during scheduled backup: {e}")
        await asyncio.sleep(60)

def run_bot():
    logging.basicConfig(
        format='%(message)s',
        level=logging.INFO
    )
    logging.getLogger("httpx").setLevel(logging.ERROR)
    logging.getLogger("telegram").setLevel(logging.ERROR)
    logging.getLogger("aiohttp").setLevel(logging.ERROR)

    app = ApplicationBuilder().token(BOT_TOKEN).build()
    
    app.add_handler(CommandHandler("start", start))
    app.add_handler(CommandHandler("setlang", setlang_command))
    app.add_handler(CommandHandler("help", help_command))
    app.add_handler(CommandHandler("noti", noti_command))
    app.add_handler(CommandHandler("rs", reset_command))
    app.add_handler(CommandHandler("setdonate", set_donate_command))
    app.add_handler(CommandHandler("stats", stats_command))
    
    app.add_handler(CallbackQueryHandler(callback_handler))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text))
    
    async def post_init(application):
        # Dynamically create workers based on config
        for i in range(1, NUM_WORKERS + 1):
            asyncio.create_task(queue_worker(application, i))
        
        # Start database backup scheduler task
        asyncio.create_task(backup_scheduler(application))

    app.post_init = post_init
    print(f"Bot is running... ({NUM_WORKERS} workers)")
    app.run_polling()
