from dataclasses import dataclass, field

from telegram import Update
from telegram.constants import ChatType
from telegram.ext import ContextTypes

from bot.core.logger import logger

PARTY_MAX = 5
GATHER_EXPIRY_SECONDS = 30 * 60


@dataclass
class GatherSession:
    inviter_name: str
    inviter_id: int
    chat_id: int
    status_message_id: int
    time_minutes: int | None
    accepted: list[tuple[int, str]] = field(default_factory=list)  # (user_id, display_name)
    declined: list[tuple[int, str]] = field(default_factory=list)
    closed: bool = False


def _mention(user_id: int, name: str, username: str | None) -> str:
    if username:
        return f"@{username}"
    return f'<a href="tg://user?id={user_id}">{name}</a>'


def _build_status_text(session: GatherSession) -> str:
    count = len(session.accepted)
    if session.closed and count >= PARTY_MAX:
        players = ", ".join(name for _, name in session.accepted)
        return (
            f"🎮 Party is FULL ({PARTY_MAX}/{PARTY_MAX}) — GL HF! 🎉\n\n"
            f"Players: {players}"
        )

    accepted_str = ", ".join(name for _, name in session.accepted) or "—"
    declined_str = ", ".join(name for _, name in session.declined) or "—"
    lines = [
        f"🎮 Party status ({count}/{PARTY_MAX})",
        "/accept to join · /decline to pass",
        "",
        f"Accepted ({count}): {accepted_str}",
        f"Declined ({len(session.declined)}): {declined_str}",
    ]
    if session.closed:
        lines.append("\n⏰ Gather closed.")
    return "\n".join(lines)


async def _edit_status(bot, session: GatherSession) -> None:
    try:
        await bot.edit_message_text(
            chat_id=session.chat_id,
            message_id=session.status_message_id,
            text=_build_status_text(session),
            parse_mode="HTML",
        )
    except Exception:
        pass  # message may have been deleted


async def track_member(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    user = update.effective_user
    if not user or user.is_bot:
        return
    store = context.bot_data["store"]
    store.record_member(update.effective_chat.id, user.id, user.username, user.first_name)



async def on_chat_member_update(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    chat_member = update.chat_member
    if not chat_member:
        return
    user = chat_member.new_chat_member.user
    if user.is_bot:
        return
    chat_id = chat_member.chat.id
    store = context.bot_data["store"]
    status = chat_member.new_chat_member.status
    if status in ("member", "administrator", "creator"):
        store.record_member(chat_id, user.id, user.username, user.first_name)
    elif status in ("left", "kicked", "restricted"):
        store.remove_member(chat_id, user.id)


async def gather(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    chat = update.effective_chat
    user = update.effective_user
    if chat.type == ChatType.PRIVATE:
        await update.message.reply_text("Use /party in a group chat.")
        return

    store = context.bot_data["store"]
    store.record_member(chat.id, user.id, user.username, user.first_name)

    sessions: dict[int, GatherSession] = context.bot_data["gather_sessions"]
    if chat.id in sessions and not sessions[chat.id].closed:
        await update.message.reply_text(
            "A party is already active! Use /accept or /decline to join."
        )
        return

    time_minutes: int | None = None
    if context.args:
        try:
            time_minutes = int(context.args[0])
            if not (1 <= time_minutes <= 480):
                raise ValueError
        except ValueError:
            await update.message.reply_text("Usage: /party [minutes] (1–480)")
            return

    members = [m for m in store.list_members(chat.id) if m.user_id != user.id]
    if not members:
        await update.message.reply_text(
            "No group members recorded yet — have everyone send a message first."
        )
        return

    inviter_mention = _mention(user.id, user.first_name, user.username)
    time_str = f" ⏰ Starting in {time_minutes} min" if time_minutes else ""
    member_mentions = " ".join(_mention(m.user_id, m.first_name, m.username) for m in members)

    await update.message.reply_text(
        f"🚨 {inviter_mention} is gathering a Dota 2 party!{time_str}\n\n"
        f"👥 Players pool:\n{member_mentions}",
        parse_mode="HTML",
    )

    status_msg = await update.message.reply_text(
        f"🎮 Party status (1/{PARTY_MAX})\n"
        "/accept to join · /decline to pass\n\n"
        f"Accepted (1): {user.first_name}\n"
        "Declined (0): —",
    )
    try:
        await status_msg.pin(disable_notification=True)
    except Exception:
        pass

    session = GatherSession(
        inviter_name=user.first_name,
        inviter_id=user.id,
        chat_id=chat.id,
        status_message_id=status_msg.message_id,
        time_minutes=time_minutes,
        accepted=[(user.id, user.first_name)],
    )
    sessions[chat.id] = session

    expiry = (time_minutes * 60) if time_minutes else GATHER_EXPIRY_SECONDS
    context.job_queue.run_once(
        expire_gather,
        when=expiry,
        data={"chat_id": chat.id},
        name=f"gather_{chat.id}",
    )


async def accept(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    chat = update.effective_chat
    user = update.effective_user
    if not user or user.is_bot:
        return
    if chat.type == ChatType.PRIVATE:
        await update.message.reply_text("Use /accept in a group chat.")
        return

    store = context.bot_data["store"]
    store.record_member(chat.id, user.id, user.username, user.first_name)

    sessions: dict[int, GatherSession] = context.bot_data["gather_sessions"]
    session = sessions.get(chat.id)
    if not session or session.closed:
        await update.message.reply_text("No active party. Use /party to start one.")
        return

    display = user.first_name
    if any(uid == user.id for uid, _ in session.accepted):
        await update.message.reply_text("You're already in! 👍")
        return

    session.declined = [(uid, name) for uid, name in session.declined if uid != user.id]
    session.accepted.append((user.id, display))

    if len(session.accepted) >= PARTY_MAX:
        session.closed = True
        for job in context.job_queue.get_jobs_by_name(f"gather_{chat.id}"):
            job.schedule_removal()

    await _edit_status(context.bot, session)


async def decline(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    chat = update.effective_chat
    user = update.effective_user
    if not user or user.is_bot:
        return
    if chat.type == ChatType.PRIVATE:
        await update.message.reply_text("Use /decline in a group chat.")
        return

    store = context.bot_data["store"]
    store.record_member(chat.id, user.id, user.username, user.first_name)

    sessions: dict[int, GatherSession] = context.bot_data["gather_sessions"]
    session = sessions.get(chat.id)
    if not session or session.closed:
        await update.message.reply_text("No active gather.")
        return

    display = user.first_name
    if any(uid == user.id for uid, _ in session.declined):
        await update.message.reply_text("You already declined.")
        return

    session.accepted = [(uid, name) for uid, name in session.accepted if uid != user.id]
    session.declined.append((user.id, display))

    await _edit_status(context.bot, session)


async def abandon(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    chat = update.effective_chat
    user = update.effective_user
    if not user or user.is_bot:
        return
    if chat.type == ChatType.PRIVATE:
        await update.message.reply_text("Use /abandon in a group chat.")
        return

    sessions: dict[int, GatherSession] = context.bot_data["gather_sessions"]
    session = sessions.get(chat.id)

    if not session or session.closed:
        await update.message.reply_text("No active gather to abandon.")
        return

    if user.id != session.inviter_id:
        await update.message.reply_text("Only the gather host can abandon it.")
        return

    session.closed = True
    for job in context.job_queue.get_jobs_by_name(f"gather_{chat.id}"):
        job.schedule_removal()

    try:
        count = len(session.accepted)
        accepted_str = ", ".join(name for _, name in session.accepted) or "—"
        declined_str = ", ".join(name for _, name in session.declined) or "—"
        text = (
            f"🎮 Party status ({count}/{PARTY_MAX})\n\n"
            f"Accepted ({count}): {accepted_str}\n"
            f"Declined ({len(session.declined)}): {declined_str}\n\n"
            f"❌ Gather was abandoned by the host ({session.inviter_name})."
        )
        await context.bot.edit_message_text(
            chat_id=chat.id,
            message_id=session.status_message_id,
            text=text,
        )
    except Exception:
        pass
    try:
        await context.bot.unpin_chat_message(chat_id=chat.id, message_id=session.status_message_id)
    except Exception:
        pass


async def expire_gather(context: ContextTypes.DEFAULT_TYPE) -> None:
    chat_id = context.job.data["chat_id"]
    sessions: dict[int, GatherSession] = context.bot_data["gather_sessions"]
    session = sessions.get(chat_id)
    if not session or session.closed:
        return
    session.closed = True
    try:
        count = len(session.accepted)
        accepted_str = ", ".join(name for _, name in session.accepted) or "—"
        declined_str = ", ".join(name for _, name in session.declined) or "—"
        text = (
            f"🎮 Party status ({count}/{PARTY_MAX})\n\n"
            f"Accepted ({count}): {accepted_str}\n"
            f"Declined ({len(session.declined)}): {declined_str}\n\n"
            "⏰ Time's up — gather closed."
        )
        await context.bot.edit_message_text(
            chat_id=chat_id,
            message_id=session.status_message_id,
            text=text,
        )
    except Exception:
        pass
    try:
        await context.bot.unpin_chat_message(chat_id=chat_id, message_id=session.status_message_id)
    except Exception:
        pass
