from __future__ import annotations

import asyncio

from pygitweb.change_queue import ChangeQueue


class TestChangeQueue:
    def test_notify_wakes_waiter_with_project_name(self) -> None:
        async def scenario() -> tuple[int, str | None, int]:
            queue: ChangeQueue = ChangeQueue()
            waiter: asyncio.Task[str | None] = asyncio.create_task(queue.wait_for_changes(["alpha"], timeout=2.0))
            await asyncio.sleep(0)
            woken: int = await queue.notify("alpha")
            result: str | None = await waiter
            leftover: int = await queue.waiter_count()
            return woken, result, leftover

        woken, result, leftover = asyncio.run(scenario())
        assert woken == 1
        assert result == "alpha"
        assert leftover == 0

    def test_wait_returns_none_on_timeout(self) -> None:
        async def scenario() -> tuple[str | None, int]:
            queue: ChangeQueue = ChangeQueue()
            outcome: str | None = await queue.wait_for_changes(["alpha"], timeout=0.05)
            leftover: int = await queue.waiter_count("alpha")
            return outcome, leftover

        outcome, leftover = asyncio.run(scenario())
        assert outcome is None
        assert leftover == 0

    def test_notify_only_wakes_subscribed_waiters(self) -> None:
        async def scenario() -> tuple[int, str | None]:
            queue: ChangeQueue = ChangeQueue()
            waiter: asyncio.Task[str | None] = asyncio.create_task(queue.wait_for_changes(["alpha"], timeout=0.2))
            await asyncio.sleep(0)
            woken: int = await queue.notify("beta")
            outcome: str | None = await waiter
            return woken, outcome

        woken, outcome = asyncio.run(scenario())
        assert woken == 0
        assert outcome is None

    def test_wait_for_multiple_projects_wakes_on_any(self) -> None:
        async def scenario() -> tuple[str | None, int]:
            queue: ChangeQueue = ChangeQueue()
            waiter: asyncio.Task[str | None] = asyncio.create_task(
                queue.wait_for_changes(["alpha", "group/beta"], timeout=2.0)
            )
            await asyncio.sleep(0)
            await queue.notify("group/beta")
            outcome: str | None = await waiter
            leftover: int = await queue.waiter_count()
            return outcome, leftover

        outcome, leftover = asyncio.run(scenario())
        assert outcome == "group/beta"
        assert leftover == 0

    def test_notify_returns_zero_when_no_waiters(self) -> None:
        async def scenario() -> int:
            queue: ChangeQueue = ChangeQueue()
            return await queue.notify("alpha")

        assert asyncio.run(scenario()) == 0

    def test_wait_returns_none_when_shutdown_event_set(self) -> None:
        async def scenario() -> str | None:
            queue: ChangeQueue = ChangeQueue()
            sd: asyncio.Event = asyncio.Event()
            sd.set()
            return await queue.wait_for_changes(["alpha"], timeout=2.0, shutdown_event=sd)

        assert asyncio.run(scenario()) is None

    def test_wait_returns_none_when_shutdown_beats_notify(self) -> None:
        async def scenario() -> str | None:
            queue: ChangeQueue = ChangeQueue()
            sd: asyncio.Event = asyncio.Event()
            waiter: asyncio.Task[str | None] = asyncio.create_task(
                queue.wait_for_changes(["alpha"], timeout=2.0, shutdown_event=sd)
            )
            await asyncio.sleep(0)
            sd.set()
            return await waiter

        assert asyncio.run(scenario()) is None

    def test_notify_all_wakes_every_waiter(self) -> None:
        async def scenario() -> tuple[int, str | None, str | None, int]:
            queue: ChangeQueue = ChangeQueue()
            w1: asyncio.Task[str | None] = asyncio.create_task(queue.wait_for_changes(["alpha"], timeout=2.0))
            w2: asyncio.Task[str | None] = asyncio.create_task(queue.wait_for_changes(["beta"], timeout=2.0))
            await asyncio.sleep(0)
            woken: int = await queue.notify_all()
            r1: str | None = await w1
            r2: str | None = await w2
            leftover: int = await queue.waiter_count()
            return woken, r1, r2, leftover

        woken, r1, r2, leftover = asyncio.run(scenario())
        assert woken == 2
        assert r1 == "alpha"
        assert r2 == "beta"
        assert leftover == 0

    def test_notify_all_dedupes_multi_project_waiter(self) -> None:
        async def scenario() -> tuple[int, str | None, int]:
            queue: ChangeQueue = ChangeQueue()
            waiter: asyncio.Task[str | None] = asyncio.create_task(
                queue.wait_for_changes(["alpha", "beta"], timeout=2.0)
            )
            await asyncio.sleep(0)
            woken: int = await queue.notify_all()
            result: str | None = await waiter
            leftover: int = await queue.waiter_count()
            return woken, result, leftover

        woken, result, leftover = asyncio.run(scenario())
        assert woken == 1
        assert result in {"alpha", "beta"}
        assert leftover == 0