"""Update handlers — route incoming updates to user-defined callbacks."""

from __future__ import annotations

import re
from typing import Any, Awaitable, Callable, Optional, Protocol, Union

from .filters import BaseFilter
from .types import Update


class Handler(Protocol):
    """Protocol for all update handlers.

    The ``bot`` parameter in ``handle()`` is typed as ``Any`` to avoid a
    circular import with :class:`~dexfather.bot.DexFatherBot`.  At runtime
    it will always be a ``DexFatherBot`` instance.
    """

    def check(self, update: Update) -> bool:
        """Return True if this handler should process the update."""
        ...

    async def handle(self, update: Update, bot: Any) -> Any:
        """Process the update.

        Args:
            update: The incoming update to process.
            bot: The ``DexFatherBot`` instance dispatching this update.
                 Typed as ``Any`` to avoid circular imports; always a
                 :class:`~dexfather.bot.DexFatherBot` at runtime.
        """
        ...


# Type alias for handler callbacks.
# ``bot`` is typed as ``Any`` to avoid circular imports with DexFatherBot.
# At runtime the second argument is always a DexFatherBot instance.
HandlerCallback = Callable[["Update", Any], Awaitable[Any]]


class CommandHandler:
    """Handles /command messages.

    Usage:
        CommandHandler("start", on_start)
        CommandHandler(["help", "h"], on_help)
    """

    def __init__(
        self,
        command: Union[str, list[str]],
        callback: HandlerCallback,
    ) -> None:
        """Create a command handler.

        Args:
            command: Command name or list of names (without leading ``/``).
            callback: Async function called with ``(update, bot)``.
        """
        self.commands = [command] if isinstance(command, str) else command
        self.callback = callback

    def check(self, update: Update) -> bool:
        if not update.message or not update.message.text:
            return False
        text = update.message.text.strip()
        for cmd in self.commands:
            pattern = f"/{cmd}"
            if text == pattern or text.startswith(f"{pattern} ") or text.startswith(f"{pattern}@"):
                return True
        return False

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)


class MessageHandler:
    """Handles text messages matching an optional regex or composable filter.

    Usage:
        MessageHandler(on_message)                             # all non-command text
        MessageHandler(on_price, pattern=r"^price")            # regex match
        MessageHandler(on_text, filters=TEXT & ~COMMAND)        # composable filter
    """

    def __init__(
        self,
        callback: HandlerCallback,
        pattern: Optional[str] = None,
        *,
        filters: Optional[BaseFilter] = None,
    ) -> None:
        """Create a message handler.

        Args:
            callback: Async function called with ``(update, bot)``.
            pattern: Optional regex pattern to filter messages.
            filters: Optional composable filter (from ``dexfather.filters``).
                Cannot be used together with *pattern*.

        Raises:
            ValueError: If both *pattern* and *filters* are provided.
        """
        if pattern is not None and filters is not None:
            raise ValueError("Cannot use both 'pattern' and 'filters' — choose one")
        self.callback = callback
        self.pattern = re.compile(pattern) if pattern else None
        self.filters = filters

    def check(self, update: Update) -> bool:
        # Filter mode — delegate entirely to the composable filter
        if self.filters is not None:
            return self.filters.check(update)
        # Legacy mode — text messages only, skip commands, optional regex
        if not update.message or not update.message.text:
            return False
        # Skip commands — those are for CommandHandler
        if update.message.text.startswith("/"):
            return False
        if self.pattern:
            return bool(self.pattern.search(update.message.text))
        return True

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)


class CallbackQueryHandler:
    """Handles inline keyboard callback queries.

    Usage:
        CallbackQueryHandler(on_callback)                     # all callbacks
        CallbackQueryHandler(on_confirm, pattern=r"^confirm:")  # filtered
    """

    def __init__(
        self,
        callback: HandlerCallback,
        pattern: Optional[str] = None,
    ) -> None:
        """Create a callback query handler.

        Args:
            callback: Async function called with ``(update, bot)``.
            pattern: Optional regex to filter by ``callback_data``.
        """
        self.callback = callback
        self.pattern = re.compile(pattern) if pattern else None

    def check(self, update: Update) -> bool:
        if not update.callback_query:
            return False
        if self.pattern and update.callback_query.data:
            return bool(self.pattern.search(update.callback_query.data))
        return self.pattern is None

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)


class InlineQueryHandler:
    """Handles inline query updates (@botusername queries).

    Usage:
        InlineQueryHandler(on_inline)                          # all inline queries
        InlineQueryHandler(on_price, pattern=r"^price")       # filtered by query text
    """

    def __init__(
        self,
        callback: HandlerCallback,
        pattern: Optional[str] = None,
    ) -> None:
        """Create an inline query handler.

        Args:
            callback: Async function called with ``(update, bot)``.
            pattern: Optional regex to filter by query text.
        """
        self.callback = callback
        self.pattern = re.compile(pattern) if pattern else None

    def check(self, update: Update) -> bool:
        if not update.inline_query:
            return False
        if self.pattern and update.inline_query.query:
            return bool(self.pattern.search(update.inline_query.query))
        return self.pattern is None

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)


class TypeHandler:
    """Generic handler matching on update type field name.

    Fires for any update that has a non-None value for the given field.

    Usage:
        TypeHandler("message_reaction", on_reaction)
        TypeHandler("poll", on_poll_update)
    """

    def __init__(self, update_type: str, callback: HandlerCallback) -> None:
        self.update_type = update_type
        self.callback = callback

    def check(self, update: Update) -> bool:
        return getattr(update, self.update_type, None) is not None

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)


class PollAnswerHandler:
    """Handles poll_answer updates (user voted in a poll).

    Usage:
        PollAnswerHandler(on_vote)
    """

    def __init__(self, callback: HandlerCallback) -> None:
        self.callback = callback

    def check(self, update: Update) -> bool:
        return update.poll_answer is not None

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)


class ChatMemberHandler:
    """Handles chat_member updates (member status changed).

    Usage:
        ChatMemberHandler(on_member_update)
    """

    def __init__(self, callback: HandlerCallback) -> None:
        self.callback = callback

    def check(self, update: Update) -> bool:
        return getattr(update, "chat_member", None) is not None

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)


class ChatJoinRequestHandler:
    """Handles chat_join_request updates (user requested to join a chat).

    Usage:
        ChatJoinRequestHandler(on_join_request)
    """

    def __init__(self, callback: HandlerCallback) -> None:
        self.callback = callback

    def check(self, update: Update) -> bool:
        return getattr(update, "chat_join_request", None) is not None

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)


class ChosenInlineResultHandler:
    """Handles chosen_inline_result updates (user selected an inline result).

    Usage:
        ChosenInlineResultHandler(on_chosen_result)
    """

    def __init__(self, callback: HandlerCallback) -> None:
        self.callback = callback

    def check(self, update: Update) -> bool:
        return getattr(update, "chosen_inline_result", None) is not None

    async def handle(self, update: Update, bot: Any) -> Any:
        return await self.callback(update, bot)
