# SPDX-FileCopyrightText: © 2026 scy
#
# SPDX-License-Identifier: MIT

import asyncio

import pytest

from evel.driver.async_queue import AsyncQueueBus


def test_two_topics() -> None:
    """Example usage subscribing & publishing to two topics."""

    async def main() -> None:
        # Example messages to send. Each has a topic and a message.
        send = {("foo", "abc"), ("bar", "def")}

        # Create simple event bus using asyncio queues.
        bus = AsyncQueueBus[str](queue_size=10)

        # Subscribe to the "foo" and "bar" topics. We need to subscribe before
        # sending events, else they'll be dropped since there's no subscriber.
        with bus.subscribe("foo", "bar") as events:
            # This set will hold the received messages.
            received = set()

            # Send the messages.
            for topic, msg in send:
                await bus.publish(topic, msg)

            # Listen for messages until we have the same number we've sent.
            async for topic, msg in events:
                received.add((topic, msg))
                if len(received) == len(send):
                    break

        # Make sure we received everything correctly.
        assert received == send

    # Run the async function.
    asyncio.run(main())


def test_raw_queues() -> None:
    """Test the interface for working with raw queues."""

    async def main() -> None:
        # Example messages to send. Each has a topic and a message.
        send = {("foo", "abc"), ("bar", "def")}

        # Create simple event bus using asyncio queues.
        bus = AsyncQueueBus[str](queue_size=10)

        # Subscribe to the "foo" and "bar" topics. We need to subscribe before
        # sending events, else they'll be dropped since there's no subscriber.
        with bus.subscribe_queues("foo", "bar") as queues:
            # Send the messages.
            for topic, msg in send:
                await bus.publish(topic, msg)

            # Retrieve an item from each queue.
            received = {
                (topic, (await queue.get())) for topic, queue in queues.items()
            }

        # Make sure we received everything correctly.
        assert received == send

    # Run the async function.
    asyncio.run(main())


def test_raw_queue() -> None:
    """Test the interface for working with a single raw queue."""

    async def main() -> None:
        # Example message to send.
        send = "abc"

        # Create simple event bus using asyncio queues.
        bus = AsyncQueueBus[str](queue_size=10)

        # Subscribe to the "foo" topic. We need to subscribe before sending
        # events, else they'll be dropped since there's no subscriber.
        with bus.subscribe_queue("foo") as queue:
            # Send the message.
            await bus.publish("foo", send)

            # Retrieve an item from the queue.
            received = await queue.get()

        # Make sure we received everything correctly.
        assert received == send

    # Run the async function.
    asyncio.run(main())


def test_wait_on_publish() -> None:
    """Verify sender waits on full queue."""

    async def sender(bus: AsyncQueueBus) -> None:
        await bus.publish("foo", "abc")
        await bus.publish("foo", "def")

    async def main() -> None:
        # Limit the queues per subscriber to just one.
        bus = AsyncQueueBus[str](queue_size=1)

        with bus.subscribe("foo") as events:
            # Create a sender that will work in the background.
            sender_task = asyncio.create_task(sender(bus))

            # Listen for messages until we have two, which is larger than the
            # queue size we defined.
            msgs = []
            async for _, msg in events:
                msgs.append(msg)
                if len(msgs) == 2:  # noqa: PLR2004
                    break

            assert msgs == ["abc", "def"]

            # Clean up the sender.
            await sender_task

    asyncio.run(main())


def test_timeout() -> None:
    """Verify timeout behavior."""

    async def main() -> None:
        # Create a simple bus.
        bus = AsyncQueueBus[str](queue_size=10)

        with bus.subscribe("foo", timeout=0.05) as events:
            # Listen for messages until we have a timeout.
            async for topic, msg in events:
                assert topic is msg is None
                break

    asyncio.run(main())


def test_topic_type() -> None:
    """Ensure topics have to be strings."""
    bus = AsyncQueueBus[str](queue_size=10)
    with pytest.raises(TypeError):
        bus.sync_publish(123, "abc")  # type: ignore[arg-type]
    with pytest.raises(TypeError):  # noqa: SIM117
        with bus.subscribe(123):  # type: ignore[call-overload]
            pass
