from __future__ import annotations

import logging
import re
import base64
from io import BytesIO
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ParseMode
from telegram.ext import (
    Application,
    CallbackQueryHandler,
    CommandHandler,
    ContextTypes,
    ConversationHandler,
    MessageHandler,
    filters,
)

from config import BOT_TOKEN, SUPPORT_LINK
from database import (
    ads_today_count,
    ensure_model,
    get_active_packages,
    get_setting,
    save_ad,
    spend_one_credit,
    update_model_cpf_cnpj,
    upsert_group,
)
from payments import create_pix_charge

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

Q_VIDEO, Q_SEXTING, Q_PACKS, Q_AVAILABLE, Q_MEDIA, Q_CPF = range(6)


def yes_no_keyboard(key: str) -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup([
        [
            InlineKeyboardButton("Sim ✅", callback_data=f"answer:{key}:sim"),
            InlineKeyboardButton("Não ❌", callback_data=f"answer:{key}:nao"),
        ]
    ])


def main_menu(model) -> InlineKeyboardMarkup:
    return InlineKeyboardMarkup([
        [InlineKeyboardButton("Comprar créditos 💳", callback_data="menu:buy")],
        [InlineKeyboardButton("Criar anúncio 📢", callback_data="menu:create_ad")],
        [InlineKeyboardButton("Meu saldo 💰", callback_data="menu:balance")],
        [InlineKeyboardButton("Suporte", url=SUPPORT_LINK)],
    ])


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.effective_user
    model = ensure_model(user.id, user.username, user.full_name)

    if model["status"] == "blocked":
        await update.message.reply_text("Seu acesso ao bot está bloqueado.")
        return ConversationHandler.END

    if model["status"] != "approved":
        await update.message.reply_text(
            "Seu cadastro ainda não está liberado para divulgação.\n\n"
            "Fale com o suporte ou aguarde aprovação da administração.",
            reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Suporte", url=SUPPORT_LINK)]])
        )
        return ConversationHandler.END

    await update.message.reply_text(
        f"Olá, @{model.get('username') or user.username or 'modelo'} 👋\n\n"
        f"Seu saldo atual: <b>{model['credit_balance']} crédito(s)</b>.\n\n"
        "Escolha uma opção:",
        parse_mode=ParseMode.HTML,
        reply_markup=main_menu(model),
    )
    return ConversationHandler.END


async def menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()
    user = update.effective_user
    model = ensure_model(user.id, user.username, user.full_name)

    if model["status"] != "approved":
        await query.edit_message_text("Seu cadastro ainda não está aprovado.")
        return ConversationHandler.END

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

    if action == "balance":
        await query.edit_message_text(
            f"Seu saldo atual é: <b>{model['credit_balance']} crédito(s)</b>.",
            parse_mode=ParseMode.HTML,
            reply_markup=main_menu(model),
        )
        return ConversationHandler.END

    if action == "buy":
        packages = get_active_packages()
        buttons = []
        for p in packages:
            label = f"{p['name']} — R$ {float(p['price']):.2f}".replace(".", ",")
            buttons.append([InlineKeyboardButton(label, callback_data=f"buy:{p['id']}")])
        buttons.append([InlineKeyboardButton("Voltar", callback_data="menu:balance")])
        await query.edit_message_text("Escolha um pacote de créditos:", reply_markup=InlineKeyboardMarkup(buttons))
        return ConversationHandler.END

    if action == "create_ad":
        if int(model["credit_balance"]) <= 0:
            await query.edit_message_text(
                "Você não tem créditos para criar anúncio.\n\nCompre créditos para continuar.",
                reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Comprar créditos 💳", callback_data="menu:buy")]])
            )
            return ConversationHandler.END

        daily_limit = int(model["custom_daily_limit"] or get_setting("max_ads_per_model_per_day", "5"))
        if ads_today_count(int(model["id"])) >= daily_limit:
            await query.edit_message_text(
                f"Você já atingiu o limite de {daily_limit} anúncio(s) hoje.\n\nTente novamente amanhã."
            )
            return ConversationHandler.END

        context.user_data["ad_answers"] = {}
        context.user_data["model"] = model
        await query.edit_message_text("Você faz vídeo chamada?", reply_markup=yes_no_keyboard("video_call"))
        return Q_VIDEO


async def send_charge_to_user(query, context: ContextTypes.DEFAULT_TYPE, package, charge: dict):
    text = (
        f"Pedido gerado ✅\n\n"
        f"Pacote: <b>{package['name']}</b>\n"
        f"Valor: <b>R$ {float(package['price']):.2f}</b>\n"
        f"Créditos: <b>{package['credits']}</b>\n\n"
    ).replace(".", ",")

    if charge.get("qr_code"):
        text += (
            "Pague usando o Pix copia e cola abaixo.\n\n"
            f"<code>{charge['qr_code']}</code>\n\n"
            "Após o pagamento aprovado, seus créditos serão liberados automaticamente."
        )

        await query.edit_message_text(text, parse_mode=ParseMode.HTML)

        if charge.get("encoded_image"):
            try:
                image_bytes = base64.b64decode(charge["encoded_image"])
                bio = BytesIO(image_bytes)
                bio.name = "qrcode_pix.png"
                await context.bot.send_photo(
                    chat_id=query.message.chat_id,
                    photo=bio,
                    caption="QR Code Pix para pagamento ✅"
                )
            except Exception as img_exc:
                await context.bot.send_message(
                    chat_id=query.message.chat_id,
                    text=f"QR Code não pôde ser enviado como imagem, mas o copia e cola acima está válido.\nErro: {img_exc}"
                )

        if charge.get("payment_url"):
            await context.bot.send_message(
                chat_id=query.message.chat_id,
                text="Também deixei o link da cobrança abaixo:",
                reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Abrir cobrança", url=charge["payment_url"])]])
            )

    elif charge.get("payment_url"):
        text += "Clique no botão abaixo para pagar."
        buttons = [[InlineKeyboardButton("Pagar agora 💳", url=charge["payment_url"])]]
        await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(buttons))
    else:
        text += (
            f"Referência: <code>{charge['external_reference']}</code>\n\n"
            "Modo manual ativo: após o pagamento, o admin aprova no painel e os créditos caem no saldo."
        )
        await query.edit_message_text(text, parse_mode=ParseMode.HTML)


async def buy_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()
    user = update.effective_user
    model = ensure_model(user.id, user.username, user.full_name)

    package_id = int(query.data.split(":")[1])
    package = next((p for p in get_active_packages() if int(p["id"]) == package_id), None)
    if not package:
        await query.edit_message_text("Pacote não encontrado.")
        return ConversationHandler.END

    if not model.get("cpf_cnpj"):
        context.user_data["pending_package_id"] = package_id
        await query.edit_message_text(
            "Para gerar o Pix pelo Asaas, preciso do CPF ou CNPJ do pagador.\n\n"
            "Envie apenas os números, sem ponto, traço ou barra.\n\n"
            "Exemplo CPF: 12345678900\n"
            "Exemplo CNPJ: 12345678000199\n\n"
            "Use /cancelar para cancelar."
        )
        return Q_CPF

    try:
        charge = create_pix_charge(int(model["id"]), package)
    except Exception as exc:
        await query.edit_message_text(
            "Não consegui gerar o Pix agora.\n\n"
            f"Erro técnico: <code>{str(exc)[:900]}</code>",
            parse_mode=ParseMode.HTML,
        )
        return ConversationHandler.END

    await send_charge_to_user(query, context, package, charge)
    return ConversationHandler.END


async def cpf_received(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.effective_user
    model = ensure_model(user.id, user.username, user.full_name)

    cpf_cnpj = re.sub(r"\D", "", update.message.text or "")

    if len(cpf_cnpj) not in (11, 14):
        await update.message.reply_text(
            "CPF/CNPJ inválido. Envie apenas os números.\n\n"
            "CPF precisa ter 11 números. CNPJ precisa ter 14 números."
        )
        return Q_CPF

    package_id = int(context.user_data.get("pending_package_id") or 0)
    package = next((p for p in get_active_packages() if int(p["id"]) == package_id), None)

    if not package:
        await update.message.reply_text("Pacote não encontrado. Clique em Comprar créditos novamente.")
        return ConversationHandler.END

    update_model_cpf_cnpj(int(model["id"]), cpf_cnpj)
    model = ensure_model(user.id, user.username, user.full_name)

    await update.message.reply_text("CPF/CNPJ salvo ✅\n\nGerando seu Pix agora...")

    class FakeQuery:
        def __init__(self, message):
            self.message = message

        async def edit_message_text(self, *args, **kwargs):
            return await self.message.reply_text(*args, **kwargs)

    fake_query = FakeQuery(update.message)

    try:
        charge = create_pix_charge(int(model["id"]), package)
    except Exception as exc:
        await update.message.reply_text(
            "Não consegui gerar o Pix agora.\n\n"
            f"Erro técnico: <code>{str(exc)[:900]}</code>",
            parse_mode=ParseMode.HTML,
        )
        return ConversationHandler.END

    await send_charge_to_user(fake_query, context, package, charge)
    return ConversationHandler.END

async def answer_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = update.callback_query
    await query.answer()
    _, key, value = query.data.split(":")
    context.user_data.setdefault("ad_answers", {})[key] = value

    if key == "video_call":
        await query.edit_message_text("Você faz sexting?", reply_markup=yes_no_keyboard("sexting"))
        return Q_SEXTING

    if key == "sexting":
        await query.edit_message_text("Você vende packs ou prévias?", reply_markup=yes_no_keyboard("packs"))
        return Q_PACKS

    if key == "packs":
        await query.edit_message_text("Você está disponível agora?", reply_markup=yes_no_keyboard("available"))
        return Q_AVAILABLE

    if key == "available":
        await query.edit_message_text("Agora envie a mídia do anúncio: foto ou vídeo curto.")
        return Q_MEDIA


def build_ad_text(username: str, answers: dict) -> str:
    services = []
    if answers.get("video_call") == "sim":
        services.append("✅ Vídeo chamada")
    if answers.get("sexting") == "sim":
        services.append("✅ Sexting")
    if answers.get("packs") == "sim":
        services.append("✅ Packs e prévias")

    available = "Estou disponível agora 🔥" if answers.get("available") == "sim" else "Me chama para consultar disponibilidade."
    services_text = "\n".join(services) if services else "Serviços sob consulta no privado."

    return (
        "🔥 Divulgação ativa 🔥\n\n"
        f"Eu sou a @{username}\n"
        f"{available}\n\n"
        "Serviços disponíveis:\n"
        f"{services_text}\n\n"
        f"Chama no privado: @{username}"
    )


async def media_received(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = update.effective_user
    model = ensure_model(user.id, user.username, user.full_name)

    if model["status"] != "approved":
        await update.message.reply_text("Seu cadastro não está aprovado.")
        return ConversationHandler.END

    if update.message.photo:
        media_type = "photo"
        media_file_id = update.message.photo[-1].file_id
    elif update.message.video:
        media_type = "video"
        media_file_id = update.message.video.file_id
    else:
        await update.message.reply_text("Envie uma foto ou vídeo.")
        return Q_MEDIA

    ok = spend_one_credit(int(model["id"]), "Criação de anúncio")
    if not ok:
        await update.message.reply_text("Você não tem créditos suficientes.")
        return ConversationHandler.END

    answers = context.user_data.get("ad_answers", {})
    username = (model.get("username") or user.username or "").lstrip("@")
    final_text = build_ad_text(username, answers)

    approval_mode = get_setting("approval_mode", "manual")
    status = "queued" if approval_mode == "auto" else "pending_approval"

    save_ad(model, answers, media_file_id, media_type, final_text, status)

    if status == "queued":
        msg = "Seu anúncio foi criado e entrou na fila de divulgação ✅"
    else:
        msg = "Seu anúncio foi criado e está aguardando aprovação do admin ✅"

    await update.message.reply_text(
        f"{msg}\n\n"
        "Foi descontado 1 crédito do seu saldo.\n\n"
        f"Prévia do texto:\n\n{final_text}"
    )
    return ConversationHandler.END


async def register_group(update: Update, context: ContextTypes.DEFAULT_TYPE):
    chat = update.effective_chat

    if chat.type not in ("group", "supergroup", "channel"):
        await update.message.reply_text("Esse comando deve ser usado dentro de um grupo/canal.")
        return

    upsert_group(chat.id, chat.title or str(chat.id), chat.type, "pending")
    await update.message.reply_text(
        "Grupo/canal registrado como pendente ✅\n\n"
        "Agora aprove esse grupo no painel admin para o bot poder postar nele."
    )


async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text("Operação cancelada.")
    return ConversationHandler.END


def run():
    if not BOT_TOKEN:
        raise RuntimeError("BOT_TOKEN não configurado no .env")

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

    conv = ConversationHandler(
        entry_points=[CallbackQueryHandler(menu_callback, pattern=r"^menu:create_ad$")],
        states={
            Q_VIDEO: [CallbackQueryHandler(answer_callback, pattern=r"^answer:video_call:")],
            Q_SEXTING: [CallbackQueryHandler(answer_callback, pattern=r"^answer:sexting:")],
            Q_PACKS: [CallbackQueryHandler(answer_callback, pattern=r"^answer:packs:")],
            Q_AVAILABLE: [CallbackQueryHandler(answer_callback, pattern=r"^answer:available:")],
            Q_MEDIA: [MessageHandler(filters.PHOTO | filters.VIDEO, media_received)],
        },
        fallbacks=[CommandHandler("cancelar", cancel)],
        allow_reentry=True,
    )

    payment_conv = ConversationHandler(
        entry_points=[CallbackQueryHandler(buy_callback, pattern=r"^buy:\d+$")],
        states={
            Q_CPF: [MessageHandler(filters.TEXT & ~filters.COMMAND, cpf_received)],
        },
        fallbacks=[CommandHandler("cancelar", cancel)],
        allow_reentry=True,
    )

    app.add_handler(CommandHandler("start", start))
    app.add_handler(CommandHandler("registrar_grupo", register_group))
    app.add_handler(CallbackQueryHandler(menu_callback, pattern=r"^menu:(buy|balance)$"))
    app.add_handler(payment_conv)
    app.add_handler(conv)

    logger.info("Bot iniciado.")
    app.run_polling(drop_pending_updates=True)


if __name__ == "__main__":
    import asyncio

    try:
        asyncio.get_running_loop()
    except RuntimeError:
        asyncio.set_event_loop(asyncio.new_event_loop())

    run()


