"""Tests for the composable filter system."""

import pytest

from dexfather.filters import (
    ALL,
    ANIMATION,
    AUDIO,
    BOT,
    CAPTION,
    COMMAND,
    CONTACT,
    DICE,
    DOCUMENT,
    FORWARDED,
    LOCATION,
    MEDIA_GROUP,
    PHOTO,
    POLL,
    REPLY,
    STICKER,
    TEXT,
    VIDEO,
    VIDEO_NOTE,
    VENUE,
    VOICE,
    Chat,
    ChatType,
    Regex,
    User,
)
from dexfather.types import (
    Chat as ChatModel,
    Contact as ContactModel,
    Dice as DiceModel,
    Location as LocationModel,
    Message,
    Poll,
    PollOption,
    Update,
    User as UserModel,
    Venue as VenueModel,
)


def test_text_filter(text_update):
    """TEXT filter matches message with non-empty text."""
    assert TEXT.check(text_update) is True


def test_text_filter_no_message():
    """TEXT filter rejects update with no message."""
    update = Update(update_id=10)
    assert TEXT.check(update) is False


def test_text_filter_empty_text():
    """TEXT filter rejects message with empty text."""
    update = Update(
        update_id=10,
        message=Message(
            message_id=1,
            chat=ChatModel(id=1, type="private"),
            text="",
            date=0,
        ),
    )
    assert TEXT.check(update) is False


def test_command_filter(command_update, text_update):
    """COMMAND filter matches /command messages, rejects plain text."""
    assert COMMAND.check(command_update) is True
    assert COMMAND.check(text_update) is False


def test_and_composition(text_update, command_update):
    """AND composition matches when both filters pass."""
    f = TEXT & ~COMMAND
    assert f.check(text_update) is True
    assert f.check(command_update) is False


def test_or_composition(text_update, command_update):
    """OR composition matches when either filter passes."""
    f = TEXT | COMMAND
    assert f.check(text_update) is True
    assert f.check(command_update) is True


def test_not_composition(text_update, command_update):
    """NOT composition inverts filter logic."""
    f = ~COMMAND
    assert f.check(text_update) is True
    assert f.check(command_update) is False


def test_complex_composition(text_update):
    """Complex filter composition (TEXT & ~COMMAND & Regex)."""
    f = TEXT & ~COMMAND & Regex(r"hello")
    assert f.check(text_update) is True


def test_complex_composition_no_match():
    """Complex filter does not match when regex fails."""
    update = Update(
        update_id=10,
        message=Message(
            message_id=1,
            chat=ChatModel(id=1, type="private"),
            from_user=UserModel(id=1),
            text="goodbye world",
            date=0,
        ),
    )
    f = TEXT & ~COMMAND & Regex(r"hello")
    assert f.check(update) is False


def test_chat_type_filter(text_update):
    """ChatType.GROUP matches group chats, PRIVATE does not."""
    assert ChatType.GROUP.check(text_update) is True
    assert ChatType.PRIVATE.check(text_update) is False


def test_user_filter(text_update):
    """User filter matches specific user ID."""
    assert User(42).check(text_update) is True
    assert User(999).check(text_update) is False


def test_chat_filter(text_update):
    """Chat filter matches specific chat ID."""
    assert Chat(100).check(text_update) is True
    assert Chat(999).check(text_update) is False


def test_regex_filter(text_update):
    """Regex filter matches message text pattern."""
    assert Regex(r"hello").check(text_update) is True
    assert Regex(r"goodbye").check(text_update) is False


def test_bot_filter():
    """BOT filter matches updates from bot users."""
    bot_update = Update(
        update_id=10,
        message=Message(
            message_id=1,
            chat=ChatModel(id=1, type="private"),
            from_user=UserModel(id=1, is_bot=True),
            text="hello",
            date=0,
        ),
    )
    human_update = Update(
        update_id=11,
        message=Message(
            message_id=2,
            chat=ChatModel(id=1, type="private"),
            from_user=UserModel(id=2, is_bot=False),
            text="hello",
            date=0,
        ),
    )
    assert BOT.check(bot_update) is True
    assert BOT.check(human_update) is False


def test_reply_filter():
    """REPLY filter matches messages that are replies."""
    reply_update = Update(
        update_id=10,
        message=Message(
            message_id=2,
            chat=ChatModel(id=1, type="private"),
            text="replying",
            date=0,
            reply_to_message=Message(
                message_id=1,
                chat=ChatModel(id=1, type="private"),
                text="original",
                date=0,
            ),
        ),
    )
    non_reply_update = Update(
        update_id=11,
        message=Message(
            message_id=3,
            chat=ChatModel(id=1, type="private"),
            text="not a reply",
            date=0,
        ),
    )
    assert REPLY.check(reply_update) is True
    assert REPLY.check(non_reply_update) is False


def test_all_filter(text_update):
    """ALL filter matches any update with a message."""
    assert ALL.check(text_update) is True
    assert ALL.check(Update(update_id=99)) is False


def test_poll_filter():
    """POLL filter matches messages containing a poll."""
    poll_update = Update(
        update_id=10,
        message=Message(
            message_id=1,
            chat=ChatModel(id=1, type="group"),
            date=0,
            poll=Poll(
                id="poll1",
                question="Best color?",
                options=[PollOption(text="Red"), PollOption(text="Blue")],
            ),
        ),
    )
    assert POLL.check(poll_update) is True
    assert POLL.check(Update(update_id=99)) is False


def test_filter_repr(text_update):
    """Filter repr includes component filter names."""
    f = TEXT & ~COMMAND
    r = repr(f)
    assert "TEXT" in r
    assert "COMMAND" in r


def test_no_message_returns_false():
    """TEXT filter returns False for update with no message."""
    update = Update(update_id=99)
    assert TEXT.check(update) is False


# ── Helper ──────────────────────────────────────────────────────────


def _make_update(**msg_fields) -> Update:
    """Create an Update with a Message containing the given fields."""
    return Update(
        update_id=1,
        message=Message(
            message_id=1,
            date=0,
            chat=ChatModel(id=1, type="private"),
            **msg_fields,
        ),
    )


# ── Media filter tests ──────────────────────────────────────────────


def test_photo_filter():
    """PHOTO filter matches messages with photo field set."""
    update = _make_update(photo=[{"file_id": "abc", "width": 100, "height": 100}])
    assert PHOTO.check(update) is True

    update_no = _make_update(text="hello")
    assert PHOTO.check(update_no) is False
    assert PHOTO.check(Update(update_id=99)) is False


def test_video_filter():
    """VIDEO filter matches messages with video field set."""
    update = _make_update(video={"file_id": "vid1", "width": 1920, "height": 1080})
    assert VIDEO.check(update) is True

    update_no = _make_update(text="hello")
    assert VIDEO.check(update_no) is False


def test_audio_filter():
    """AUDIO filter matches messages with audio field set."""
    update = _make_update(audio={"file_id": "aud1", "duration": 180})
    assert AUDIO.check(update) is True

    update_no = _make_update(text="hello")
    assert AUDIO.check(update_no) is False


def test_document_filter():
    """DOCUMENT filter matches messages with document field set."""
    update = _make_update(document={"file_id": "doc1", "file_name": "report.pdf"})
    assert DOCUMENT.check(update) is True

    update_no = _make_update(text="hello")
    assert DOCUMENT.check(update_no) is False


def test_animation_filter():
    """ANIMATION filter matches messages with animation (GIF) field set."""
    update = _make_update(animation={"file_id": "gif1", "width": 320, "height": 240})
    assert ANIMATION.check(update) is True

    update_no = _make_update(text="hello")
    assert ANIMATION.check(update_no) is False


def test_voice_filter():
    """VOICE filter matches messages with voice field set."""
    update = _make_update(voice={"file_id": "voice1", "duration": 5})
    assert VOICE.check(update) is True

    update_no = _make_update(text="hello")
    assert VOICE.check(update_no) is False


def test_sticker_filter():
    """STICKER filter matches messages with sticker field set."""
    update = _make_update(sticker={"file_id": "stk1", "type": "regular"})
    assert STICKER.check(update) is True

    update_no = _make_update(text="hello")
    assert STICKER.check(update_no) is False


def test_video_note_filter():
    """VIDEO_NOTE filter matches messages with video_note field set."""
    update = _make_update(video_note={"file_id": "vn1", "length": 240, "duration": 10})
    assert VIDEO_NOTE.check(update) is True

    update_no = _make_update(text="hello")
    assert VIDEO_NOTE.check(update_no) is False


def test_contact_filter():
    """CONTACT filter matches messages with contact field set."""
    update = _make_update(
        contact=ContactModel(phone_number="+1234567890", first_name="John"),
    )
    assert CONTACT.check(update) is True

    update_no = _make_update(text="hello")
    assert CONTACT.check(update_no) is False


def test_location_filter():
    """LOCATION filter matches messages with location field set."""
    update = _make_update(
        location=LocationModel(latitude=59.3293, longitude=18.0686),
    )
    assert LOCATION.check(update) is True

    update_no = _make_update(text="hello")
    assert LOCATION.check(update_no) is False


def test_venue_filter():
    """VENUE filter matches messages with venue field set."""
    update = _make_update(
        venue=VenueModel(
            location=LocationModel(latitude=59.3293, longitude=18.0686),
            title="Gamla Stan",
            address="Stockholm, Sweden",
        ),
    )
    assert VENUE.check(update) is True

    update_no = _make_update(text="hello")
    assert VENUE.check(update_no) is False


def test_dice_filter():
    """DICE filter matches messages with dice field set."""
    update = _make_update(dice=DiceModel(value=4))
    assert DICE.check(update) is True

    update_no = _make_update(text="hello")
    assert DICE.check(update_no) is False


def test_forwarded_filter():
    """FORWARDED filter matches messages with forward_from or forward_date."""
    update_from = _make_update(
        text="forwarded msg",
        forward_from=UserModel(id=99, first_name="Alice"),
    )
    assert FORWARDED.check(update_from) is True

    update_date = _make_update(text="forwarded msg", forward_date=1700000000)
    assert FORWARDED.check(update_date) is True

    update_no = _make_update(text="normal msg")
    assert FORWARDED.check(update_no) is False
    assert FORWARDED.check(Update(update_id=99)) is False


def test_media_group_filter():
    """MEDIA_GROUP filter matches messages with media_group_id set."""
    update = _make_update(text="album item", media_group_id="mg_123")
    assert MEDIA_GROUP.check(update) is True

    update_no = _make_update(text="single msg")
    assert MEDIA_GROUP.check(update_no) is False


def test_caption_filter():
    """CAPTION filter matches messages with caption field set."""
    update = _make_update(caption="A nice photo")
    assert CAPTION.check(update) is True

    update_no = _make_update(text="hello")
    assert CAPTION.check(update_no) is False
    assert CAPTION.check(Update(update_id=99)) is False
