"""Composable filter system for DexFather bot update routing.

Filters are composable objects that decide whether an update should be
handled by a particular handler. They support Python's bitwise operators
for natural composition:

    from dexfather.filters import TEXT, PHOTO, VIDEO, COMMAND, Regex, ChatType, User

    # Match non-command text messages
    handler = MessageHandler(callback, filters=TEXT & ~COMMAND)

    # Match messages with photo or video
    handler = MessageHandler(callback, filters=PHOTO | VIDEO)

    # Match user 123 in group chats
    handler = MessageHandler(callback, filters=ChatType.GROUP & User(123))

    # Match messages containing "price" that aren't commands
    handler = MessageHandler(callback, filters=Regex("price") & ~COMMAND)

Note on naming: ``User`` and ``Chat`` in this module are *filter factories*,
not to be confused with the Pydantic models in ``dexfather.types``. Import
from the appropriate module::

    from dexfather.types import User as UserModel   # Pydantic model
    from dexfather.filters import User               # Filter factory
"""

from __future__ import annotations

import re as _re
from typing import Optional

from .types import Update


# -- Base class ----------------------------------------------------------------


class BaseFilter:
    """Abstract base for all filters. Supports ``&``, ``|``, ``~`` composition."""

    name: str = "BaseFilter"

    def check(self, update: Update) -> bool:
        """Return True if *update* passes this filter.

        Subclasses MUST override this method.
        """
        raise NotImplementedError

    def __and__(self, other: BaseFilter) -> BaseFilter:
        return _AndFilter(self, other)

    def __or__(self, other: BaseFilter) -> BaseFilter:
        return _OrFilter(self, other)

    def __invert__(self) -> BaseFilter:
        return _InvertFilter(self)

    def __repr__(self) -> str:
        return f"<filter {self.name}>"


# -- Composition internals -----------------------------------------------------


class _AndFilter(BaseFilter):
    """Short-circuit AND of two filters."""

    __slots__ = ("a", "b", "name")

    def __init__(self, a: BaseFilter, b: BaseFilter) -> None:
        self.a = a
        self.b = b
        self.name = f"({a.name} & {b.name})"

    def check(self, update: Update) -> bool:
        return self.a.check(update) and self.b.check(update)


class _OrFilter(BaseFilter):
    """Short-circuit OR of two filters."""

    __slots__ = ("a", "b", "name")

    def __init__(self, a: BaseFilter, b: BaseFilter) -> None:
        self.a = a
        self.b = b
        self.name = f"({a.name} | {b.name})"

    def check(self, update: Update) -> bool:
        return self.a.check(update) or self.b.check(update)


class _InvertFilter(BaseFilter):
    """Logical NOT of a filter."""

    __slots__ = ("f", "name")

    def __init__(self, f: BaseFilter) -> None:
        self.f = f
        self.name = f"(~{f.name})"

    def check(self, update: Update) -> bool:
        return not self.f.check(update)


# -- Content type singleton filters --------------------------------------------


class _TextFilter(BaseFilter):
    name = "TEXT"

    def check(self, update: Update) -> bool:
        return (
            update.message is not None
            and update.message.text is not None
            and len(update.message.text) > 0
        )


class _CommandFilter(BaseFilter):
    name = "COMMAND"

    def check(self, update: Update) -> bool:
        return (
            update.message is not None
            and update.message.text is not None
            and update.message.text.startswith("/")
        )


class _CaptionFilter(BaseFilter):
    name = "CAPTION"

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


class _PollFilter(BaseFilter):
    name = "POLL"

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


class _DiceFilter(BaseFilter):
    name = "DICE"

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


class _ContactFilter(BaseFilter):
    name = "CONTACT"

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


class _LocationFilter(BaseFilter):
    name = "LOCATION"

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


class _VenueFilter(BaseFilter):
    name = "VENUE"

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


class _ReplyFilter(BaseFilter):
    name = "REPLY"

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


class _AllFilter(BaseFilter):
    name = "ALL"

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


class _ForwardedFilter(BaseFilter):
    name = "FORWARDED"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return (
            getattr(update.message, "forward_from", None) is not None
            or getattr(update.message, "forward_date", None) is not None
        )


class _MediaGroupFilter(BaseFilter):
    name = "MEDIA_GROUP"

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


# -- Media type filters (forward-compatible via getattr) -----------------------
# These filters use getattr() to safely check for media fields that may
# not yet exist on the Message model. They will activate automatically
# when the Message model gains the corresponding media type fields
# (e.g., photo, video, audio, document, etc.).


class _PhotoFilter(BaseFilter):
    name = "PHOTO"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return getattr(update.message, "photo", None) is not None


class _VideoFilter(BaseFilter):
    name = "VIDEO"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return getattr(update.message, "video", None) is not None


class _AudioFilter(BaseFilter):
    name = "AUDIO"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return getattr(update.message, "audio", None) is not None


class _DocumentFilter(BaseFilter):
    name = "DOCUMENT"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return getattr(update.message, "document", None) is not None


class _AnimationFilter(BaseFilter):
    name = "ANIMATION"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return getattr(update.message, "animation", None) is not None


class _VoiceFilter(BaseFilter):
    name = "VOICE"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return getattr(update.message, "voice", None) is not None


class _StickerFilter(BaseFilter):
    name = "STICKER"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return getattr(update.message, "sticker", None) is not None


class _VideoNoteFilter(BaseFilter):
    name = "VIDEO_NOTE"

    def check(self, update: Update) -> bool:
        if update.message is None:
            return False
        return getattr(update.message, "video_note", None) is not None


# Module-level singleton instances
TEXT = _TextFilter()
COMMAND = _CommandFilter()
CAPTION = _CaptionFilter()
POLL = _PollFilter()
DICE = _DiceFilter()
CONTACT = _ContactFilter()
LOCATION = _LocationFilter()
VENUE = _VenueFilter()
REPLY = _ReplyFilter()
ALL = _AllFilter()
FORWARDED = _ForwardedFilter()
MEDIA_GROUP = _MediaGroupFilter()
PHOTO = _PhotoFilter()
VIDEO = _VideoFilter()
AUDIO = _AudioFilter()
DOCUMENT = _DocumentFilter()
ANIMATION = _AnimationFilter()
VOICE = _VoiceFilter()
STICKER = _StickerFilter()
VIDEO_NOTE = _VideoNoteFilter()


# -- ChatType namespace --------------------------------------------------------


class _ChatTypeFilter(BaseFilter):
    """Matches updates where the effective message's chat has a specific type."""

    __slots__ = ("_chat_type", "name")

    def __init__(self, chat_type: str) -> None:
        self._chat_type = chat_type
        self.name = f"ChatType.{chat_type.upper()}"

    def check(self, update: Update) -> bool:
        msg = update.effective_message
        return msg is not None and msg.chat is not None and msg.chat.type == self._chat_type


class ChatType:
    """Namespace for chat-type filters.

    Usage::

        from dexfather.filters import ChatType

        ChatType.PRIVATE   # matches private/DM chats
        ChatType.GROUP     # matches group chats
        ChatType.CHANNEL   # matches channel posts
    """

    PRIVATE = _ChatTypeFilter("private")
    GROUP = _ChatTypeFilter("group")
    CHANNEL = _ChatTypeFilter("channel")


# -- Parameterized filters -----------------------------------------------------


class User(BaseFilter):
    """Filter that matches a specific user by ID.

    Usage::

        from dexfather.filters import User
        handler = MessageHandler(callback, filters=User(123))
    """

    __slots__ = ("_user_id", "name")

    def __init__(self, user_id: int) -> None:
        self._user_id = user_id
        self.name = f"User({user_id})"

    def check(self, update: Update) -> bool:
        user = update.effective_user
        return user is not None and user.id == self._user_id


class Chat(BaseFilter):
    """Filter that matches a specific chat by ID.

    Usage::

        from dexfather.filters import Chat
        handler = MessageHandler(callback, filters=Chat(456))
    """

    __slots__ = ("_chat_id", "name")

    def __init__(self, chat_id: int) -> None:
        self._chat_id = chat_id
        self.name = f"Chat({chat_id})"

    def check(self, update: Update) -> bool:
        chat = update.effective_chat
        return chat is not None and chat.id == self._chat_id


class Regex(BaseFilter):
    """Filter that matches message text against a regex pattern.

    Usage::

        from dexfather.filters import Regex
        handler = MessageHandler(callback, filters=Regex(r"price\\\\s+\\\\d+"))
    """

    __slots__ = ("_pattern", "name")

    def __init__(self, pattern: str) -> None:
        self._pattern = _re.compile(pattern)
        self.name = f"Regex({pattern!r})"

    def check(self, update: Update) -> bool:
        msg = update.effective_message
        return (
            msg is not None
            and msg.text is not None
            and bool(self._pattern.search(msg.text))
        )


class _BotFilter(BaseFilter):
    """Matches updates from bot users."""

    name = "BOT"

    def check(self, update: Update) -> bool:
        user = update.effective_user
        return user is not None and user.is_bot


BOT = _BotFilter()

# NOTE: An ADMIN filter is intentionally deferred. Determining admin status
# requires a database lookup for each incoming update, which would make the
# filter non-pure and add latency. Admin checks should be performed inside
# the handler callback instead.


# -- Exports -------------------------------------------------------------------

__all__ = [
    # Base
    "BaseFilter",
    # Content type singletons
    "TEXT",
    "COMMAND",
    "CAPTION",
    "POLL",
    "DICE",
    "CONTACT",
    "LOCATION",
    "VENUE",
    "REPLY",
    "ALL",
    "FORWARDED",
    "MEDIA_GROUP",
    # Media type singletons
    "PHOTO",
    "VIDEO",
    "AUDIO",
    "DOCUMENT",
    "ANIMATION",
    "VOICE",
    "STICKER",
    "VIDEO_NOTE",
    # Chat type namespace
    "ChatType",
    # Parameterized filters
    "User",
    "Chat",
    "Regex",
    # Special singletons
    "BOT",
]
