import asyncio
import os
import time
import logging
import re
from aiogram import Bot, Dispatcher, types, BaseMiddleware, F
from aiogram.filters import Command
from aiogram.types import FSInputFile, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from aiogram.client.default import DefaultBotProperties
from aiogram.client.session.aiohttp import AiohttpSession
from dotenv import load_dotenv
from apscheduler.schedulers.asyncio import AsyncIOScheduler

from yt_engine import process_youtube_request, get_recommendations, search_youtube_list
from db_manager import db
from s3_manager import upload_audio, download_audio, delete_audio

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")

# --- KONFIGURASI ADMIN & USERS ---
ADMIN_ID = int(os.getenv("ADMIN_ID", 0))
auth_users_str = os.getenv("AUTHORIZED_USER_IDS", "")
AUTHORIZED_USER_IDS = {int(uid.strip()) for uid in auth_users_str.split(",") if uid.strip().isdigit()}

# Pastikan Admin selalu masuk whitelist
if ADMIN_ID != 0:
    AUTHORIZED_USER_IDS.add(ADMIN_ID)

# Penyimpanan sementara user yang sedang menunggu persetujuan (agar admin tidak di-spam)
pending_users = set()

session = AiohttpSession(timeout=300)
bot = Bot(token=BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=None))
dp = Dispatcher()
music_queue = asyncio.Queue()
user_searches = {}

async def queue_worker():
    while True:
        query, chat_id, msg_id = await music_queue.get()
        try:
            await process_music_request(query, chat_id, msg_id)
        except Exception as e:
            logger.error(f"Worker Error: {e}")
        finally:
            music_queue.task_done()

# --- SATPAM BOT DENGAN FITUR NOTIFIKASI ADMIN ---
class SecurityMiddleware(BaseMiddleware):
    async def __call__(self, handler, event: types.Message | CallbackQuery, data: dict):
        user_id = event.from_user.id
        
        if user_id not in AUTHORIZED_USER_IDS:
            # Jika user baru dan belum ada di antrean pending
            if user_id not in pending_users and ADMIN_ID != 0:
                pending_users.add(user_id)
                username = event.from_user.username or event.from_user.first_name or "Unknown"
                
                # 1. Kirim pesan ke Admin dengan tombol
                kb = InlineKeyboardMarkup(inline_keyboard=[
                    [
                        InlineKeyboardButton(text="✅ Izinkan", callback_data=f"approve_{user_id}"),
                        InlineKeyboardButton(text="❌ Tolak", callback_data=f"reject_{user_id}")
                    ]
                ])
                try:
                    await bot.send_message(
                        ADMIN_ID, 
                        f"🔔 **IZIN AKSES BARU!**\n\n👤 Nama: `{username}`\n🆔 ID: `{user_id}`\n\n_Apakah kamu mengizinkan orang ini menggunakan bot musikmu?_", 
                        reply_markup=kb, parse_mode="Markdown"
                    )
                except Exception as e:
                    logger.error(f"Gagal mengirim notif ke admin: {e}")

                # 2. Kirim pesan ke User asing
                if isinstance(event, types.Message):
                    await event.answer("🔒 **Akses Terkunci.**\nBot ini bersifat private. Permintaan izin telah dikirim ke Admin. Harap tunggu persetujuan.", parse_mode="Markdown")
                elif isinstance(event, types.CallbackQuery):
                    await event.answer("Akses terkunci! Permintaan izin dikirim ke Admin.", show_alert=True)
            
            # Abaikan semua perintah dari user asing ini
            return
            
        return await handler(event, data)

dp.message.middleware(SecurityMiddleware())
dp.callback_query.middleware(SecurityMiddleware())

# --- FUNGSI UPDATE .ENV OTOMATIS ---
def add_user_to_env(new_id):
    try:
        with open(".env", "r") as f:
            lines = f.readlines()
        with open(".env", "w") as f:
            for line in lines:
                if line.startswith("AUTHORIZED_USER_IDS="):
                    clean_line = line.strip().rstrip(',')
                    line = f"{clean_line},{new_id}\n"
                f.write(line)
        logger.info(f"User {new_id} berhasil ditambahkan permanen ke .env")
    except Exception as e:
        logger.error(f"Gagal update .env: {e}")

# --- HANDLER TOMBOL ADMIN (APPROVE / REJECT) ---
@dp.callback_query(F.data.startswith("approve_") | F.data.startswith("reject_"))
async def handle_admin_action(callback: CallbackQuery):
    if callback.from_user.id != ADMIN_ID:
        return await callback.answer("Kamu bukan Admin!", show_alert=True)
        
    action, target_id_str = callback.data.split("_")
    target_id = int(target_id_str)
    
    if target_id in pending_users:
        pending_users.remove(target_id)
        
    await callback.message.edit_reply_markup(reply_markup=None)
    
    if action == "approve":
        AUTHORIZED_USER_IDS.add(target_id)
        add_user_to_env(target_id)
        await callback.message.edit_text(f"✅ User `{target_id}` telah **DISETUJUI** dan ditambahkan permanen ke sistem.", parse_mode="Markdown")
        try:
            await bot.send_message(target_id, "🎉 **AKSES DISETUJUI!**\nAdmin telah memberikan izin. Silakan ketik /start atau /play <judul> untuk mulai mencari lagu.", parse_mode="Markdown")
        except: pass
    else:
        await callback.message.edit_text(f"❌ User `{target_id}` telah **DITOLAK**.", parse_mode="Markdown")
        try:
            await bot.send_message(target_id, "❌ **AKSES DITOLAK.**\nAdmin tidak memberikan izin untuk menggunakan bot ini.", parse_mode="Markdown")
        except: pass

def extract_video_id(query):
    match = re.search(r"(?:v=|\/)([0-9A-Za-z_-]{11}).*", query)
    return match.group(1) if match else None

def get_search_keyboard(chat_id, page=0):
    data = user_searches.get(chat_id)
    if not data: return None
    results = data['results']
    start_idx = page * 10
    end_idx = start_idx + 10
    page_results = results[start_idx:end_idx]
    
    kb = []
    for rec in page_results:
        display_text = f"🎵 {rec['title']} | {rec.get('uploader', 'YT')}"
        short_text = display_text[:55] + "..." if len(display_text) > 55 else display_text
        kb.append([InlineKeyboardButton(text=short_text, callback_data=f"play_{rec['id']}")])
        
    nav_buttons = []
    if page > 0: nav_buttons.append(InlineKeyboardButton(text="⬅️ Prev", callback_data=f"page_{page-1}"))
    if end_idx < len(results): nav_buttons.append(InlineKeyboardButton(text="Next ➡️", callback_data=f"page_{page+1}"))
    if nav_buttons: kb.append(nav_buttons)
        
    return InlineKeyboardMarkup(inline_keyboard=kb)

@dp.message(Command("start"))
async def cmd_start(message: types.Message):
    await message.answer("🎧 **Music Bot VIP Ready!**\nKirim format: `/play <judul lagu>`", parse_mode="Markdown")

@dp.message(Command("play"))
async def cmd_play(message: types.Message):
    query = message.text.replace("/play", "").strip()
    if not query: return await message.answer("⚠️ Masukkan judul lagu.")
    
    status_msg = await message.answer("🔍 *Mencari daftar lagu...*", parse_mode="Markdown")
    results = await search_youtube_list(query, max_results=30)
    
    if not results: return await status_msg.edit_text("❌ Lagu tidak ditemukan.")
        
    user_searches[message.chat.id] = {
        'header': f"🔎 Hasil pencarian: **{query}**",
        'results': results
    }
    
    kb = get_search_keyboard(message.chat.id, page=0)
    await status_msg.edit_text(text=f"{user_searches[message.chat.id]['header']}\n_Halaman 1_", reply_markup=kb, parse_mode="Markdown")

@dp.callback_query(F.data.startswith("page_"))
async def handle_page_click(callback: CallbackQuery):
    chat_id = callback.message.chat.id
    if chat_id not in user_searches:
        return await callback.answer("Sesi kadaluarsa. Ketik /play lagi.", show_alert=True)
        
    page = int(callback.data.split("_")[1])
    kb = get_search_keyboard(chat_id, page)
    
    if kb:
        header = user_searches[chat_id]['header']
        await callback.message.edit_text(text=f"{header}\n_Halaman {page+1}_", reply_markup=kb, parse_mode="Markdown")
    await callback.answer()

@dp.callback_query(F.data.startswith("play_"))
async def handle_suggestion_click(callback: CallbackQuery):
    video_id = callback.data.split("_", 1)[1]
    await callback.message.edit_reply_markup(reply_markup=None)
    status_msg = await bot.send_message(callback.message.chat.id, "⏳ Mengunduh lagu pilihanmu...")
    query_url = f"https://www.youtube.com/watch?v={video_id}"
    await music_queue.put((query_url, callback.message.chat.id, status_msg.message_id))
    await callback.answer("Siapp! Diproses...")

async def process_music_request(query: str, chat_id: int, status_msg_id: int):
    local_file = None
    try:
        video_id = extract_video_id(query)
        cached_data = None
        
        if video_id:
            cached_data = await db.get_cache(video_id)
            if cached_data:
                title = cached_data.get('title', 'Lagu')
                s3_object_name = cached_data.get('s3_object_key', f"{video_id}.m4a")
                ext = s3_object_name.split('.')[-1]
                local_file = f"downloads/cache_{video_id}.{ext}"
                await bot.edit_message_text("⚡ Mengambil dari Cache...", chat_id, status_msg_id)
                await download_audio(s3_object_name, local_file)
        
        if not cached_data:
            await bot.edit_message_text("🔍 Mengunduh dari YouTube...", chat_id, status_msg_id)
            yt_result = await process_youtube_request(query)
            if yt_result["status"] == "error":
                return await bot.edit_message_text(f"❌ Gagal: {yt_result['message']}", chat_id, status_msg_id)
            video_id = yt_result["video_id"]
            title = yt_result["title"]
            local_file = yt_result["file_path"]
            s3_object_name = f"{video_id}.{yt_result['ext']}"
            await bot.edit_message_text("☁️ Menyimpan ke Server...", chat_id, status_msg_id)
            if local_file and os.path.exists(local_file):
                await upload_audio(local_file, s3_object_name)
                await db.save_cache(video_id, title, s3_object_name)

        await bot.edit_message_text("📤 Mengirim ke Telegram...", chat_id, status_msg_id)
        if local_file and os.path.exists(local_file):
            audio = FSInputFile(local_file)
            await bot.send_audio(chat_id=chat_id, audio=audio, title=title, performer="Music Bot", request_timeout=300)
            if status_msg_id: await bot.delete_message(chat_id, status_msg_id)
            
            # --- TAMPILKAN 30 REKOMENDASI DENGAN PAGING ---
            recs = await get_recommendations(video_id)
            if recs:
                user_searches[chat_id] = {
                    'header': f"💡 Rekomendasi lanjutan dari: **{title}**",
                    'results': recs
                }
                kb = get_search_keyboard(chat_id, page=0)
                if kb:
                    await bot.send_message(chat_id, text=f"{user_searches[chat_id]['header']}\n_Halaman 1_", reply_markup=kb, parse_mode="Markdown")

    except Exception as e:
        logger.error(f"Error: {e}")
    finally:
        if local_file and os.path.exists(local_file): os.remove(local_file)

async def cleanup_expired_cache():
    expired_items = await db.get_expired_cache(days=7)
    for item in expired_items:
        await delete_audio(item['s3_object_key'])
        await db.delete_cache(item['youtube_id'])

async def main():
    await db.connect()
    asyncio.create_task(queue_worker())
    scheduler = AsyncIOScheduler()
    scheduler.add_job(cleanup_expired_cache, 'cron', hour=3, minute=0)
    scheduler.start()
    logger.info("🚀 Bot Music V4 (Admin Approval & Super Paging) Berjalan!")
    await bot.delete_webhook(drop_pending_updates=True)
    await dp.start_polling(bot)

if __name__ == "__main__":
    asyncio.run(main())
