import asyncio
from typing import AsyncGenerator, List, Sequence

import pytest
import pytest_asyncio
from autogen_agentchat.agents import BaseChatAgent
from autogen_agentchat.base import Response
from autogen_agentchat.messages import ChatMessage, TextMessage
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_core import AgentRuntime, CancellationToken, SingleThreadedAgentRuntime


class TestAgent(BaseChatAgent):
    """A test agent that does nothing."""

    def __init__(self, name: str, description: str) -> None:
        super().__init__(name=name, description=description)
        self._is_paused = False
        self._tasks: List[asyncio.Task[None]] = []
        self.counter = 0

    @property
    def produced_message_types(self) -> Sequence[type[ChatMessage]]:
        return [TextMessage]

    async def on_messages(self, messages: Sequence[ChatMessage], cancellation_token: CancellationToken) -> Response:
        assert not self._is_paused, "Agent is paused"

        async def _process() -> None:
            # Simulate a repetitive task that runs forever.
            while True:
                if self._is_paused:
                    await asyncio.sleep(0.1)
                    continue
                else:
                    # Simulate a I/O operation that takes time, e.g., a browser operation.
                    await asyncio.sleep(0.1)
                    self.counter += 1

        curr_task = asyncio.create_task(_process())
        self._tasks.append(curr_task)

        try:
            # This will never return until the task is cancelled, at which point it will
            # raise an exception.
            await curr_task
        except asyncio.CancelledError:
            # The task was cancelled, so we can safely ignore this.
            pass

        return Response(
            chat_message=TextMessage(
                source=self.name,
                content="",
            ),
        )

    async def on_reset(self, cancellation_token: CancellationToken) -> None:
        self.counter = 0

    async def on_pause(self, cancellation_token: CancellationToken) -> None:
        self._is_paused = True

    async def on_resume(self, cancellation_token: CancellationToken) -> None:
        self._is_paused = False

    async def close(self) -> None:
        # Cancel all tasks and wait for them to finish.
        while self._tasks:
            task = self._tasks.pop()
            task.cancel()
            try:
                await task
            except asyncio.CancelledError:
                pass


@pytest_asyncio.fixture(params=["single_threaded", "embedded"])  # type: ignore
async def runtime(request: pytest.FixtureRequest) -> AsyncGenerator[AgentRuntime | None, None]:
    if request.param == "single_threaded":
        runtime = SingleThreadedAgentRuntime()
        runtime.start()
        yield runtime
        await runtime.stop()
    elif request.param == "embedded":
        yield None


@pytest.mark.asyncio
async def test_group_chat_pause_resume(runtime: AgentRuntime | None) -> None:
    agent = TestAgent(name="test_agent", description="test agent")

    team = RoundRobinGroupChat([agent], runtime=runtime, max_turns=1)

    # Run the team in a separate task.
    team_task = asyncio.create_task(team.run())

    # Get the current counter.
    curr_counter = agent.counter

    # Let the agent process the counter for a while.
    await asyncio.sleep(1)

    # Check that the agent's counter has increased.
    assert curr_counter < agent.counter
    curr_counter = agent.counter

    # Pause the team.
    await team.pause()

    # Wait for a while for the agent to process the pause.
    await asyncio.sleep(1)

    # Get the current counter value.
    curr_counter = agent.counter

    # Wait for a while.
    await asyncio.sleep(1)

    # Check that the agent's counter has not increased.
    assert curr_counter == agent.counter

    # Resume the agent.
    await team.resume()

    # Wait for a while for the agent to process the resume.
    await asyncio.sleep(1)

    # Get the current counter value.
    curr_counter = agent.counter

    # Wait for a while.
    await asyncio.sleep(1)

    # Check that the agent's counter has increased.
    assert curr_counter < agent.counter

    # Clean up -- force the agent to respond and terminate the team.
    await agent.close()

    # Wait for the team to terminate.
    await team_task
