"""Outer middleware enforcing mandatory channel subscription for non-admin users."""
from __future__ import annotations

from typing import Any, Awaitable, Callable

from aiogram import BaseMiddleware
from aiogram.types import CallbackQuery, Message, TelegramObject, Update

from app import runtime
from app.common import is_admin
from app.keyboards import _chat_url, sub_gate_kb

# Statuses that mean the user is NOT subscribed.
_NOT_MEMBER = {"left", "kicked"}

_GATE_TEXT = (
    "🔒 <b>Subscription required</b>\n\n"
    "Please join our channel to use the bot, then tap "
    "“✓ I've subscribed”."
)

# Cache of resolved join links, keyed by the configured channel value (rarely changes).
_join_cache: dict[str, str] = {}


def _chat_target(channel: str):
    """Numeric ids as int, everything else (@name) as-is — what the Bot API expects."""
    channel = (channel or "").strip()
    if channel.lstrip("-").isdigit():
        return int(channel)
    return channel


async def is_subscribed(bot, channel: str, user_id: int) -> bool:
    """True if the user is a member. Fails open (True) on any API error so a
    misconfigured channel never hard-locks users out of the bot."""
    try:
        member = await bot.get_chat_member(_chat_target(channel), user_id)
    except Exception:
        return True
    return member.status not in _NOT_MEMBER


async def resolve_join_url(bot, channel: str) -> str:
    """A clickable join link for the channel — even a private one.

    Public @name → t.me link. Otherwise ask the API for the chat's username / primary
    invite link (bot must be an admin there); create a primary invite link if needed.
    Returns "" if nothing can be resolved.
    """
    if not channel:
        return ""
    url = _chat_url(channel)
    if url:
        return url
    if channel in _join_cache:
        return _join_cache[channel]
    target = _chat_target(channel)
    link = ""
    try:
        chat = await bot.get_chat(target)
        if getattr(chat, "username", None):
            link = f"https://t.me/{chat.username}"
        elif getattr(chat, "invite_link", None):
            link = chat.invite_link
        else:
            created = await bot.create_chat_invite_link(target)
            link = created.invite_link
    except Exception:
        link = ""
    if link:
        _join_cache[channel] = link
    return link


async def gate_markup(bot, channel: str):
    """Build the subscription-gate keyboard with a working Join button."""
    return sub_gate_kb(await resolve_join_url(bot, channel))


def clear_join_cache() -> None:
    _join_cache.clear()


class SubscriptionMiddleware(BaseMiddleware):
    async def __call__(
        self,
        handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
        event: Update,
        data: dict[str, Any],
    ) -> Any:
        message = event.message
        callback = event.callback_query
        user = None
        if message is not None:
            user = message.from_user
        elif callback is not None:
            user = callback.from_user
        else:
            return await handler(event, data)

        channel = runtime.get(runtime.REQUIRED_CHANNEL)
        # Let the re-check button through so its handler can verify and greet.
        is_recheck = callback is not None and callback.data == "subcheck"
        if not channel or user is None or is_admin(user.id) or is_recheck:
            return await handler(event, data)

        bot = data.get("bot") or event.bot
        if await is_subscribed(bot, channel, user.id):
            return await handler(event, data)

        # Not subscribed — show the gate and stop the update.
        kb = await gate_markup(bot, channel)
        if callback is not None:
            await callback.answer("Please subscribe first", show_alert=True)
            try:
                await callback.message.answer(_GATE_TEXT, reply_markup=kb)
            except Exception:
                pass
        elif message is not None:
            await message.answer(_GATE_TEXT, reply_markup=kb)
        return None
