from __future__ import annotations

import os
import uuid

import pytest
import pytest_asyncio

from a2a.client import ClientFactory
from a2a.types import AgentCard, Message, Part, Role, TaskState, TextPart

from crewai.a2a.updates.polling.handler import PollingHandler
from crewai.a2a.updates.streaming.handler import StreamingHandler


A2A_TEST_ENDPOINT = os.getenv("A2A_TEST_ENDPOINT", "http://localhost:9999")


@pytest_asyncio.fixture
async def a2a_client():
    """Create A2A client for test server."""
    client = await ClientFactory.connect(A2A_TEST_ENDPOINT)
    yield client
    await client.close()


@pytest.fixture
def test_message() -> Message:
    """Create a simple test message."""
    return Message(
        role=Role.user,
        parts=[Part(root=TextPart(text="What is 2 + 2?"))],
        message_id=str(uuid.uuid4()),
    )


@pytest_asyncio.fixture
async def agent_card(a2a_client) -> AgentCard:
    """Fetch the real agent card from the server."""
    return await a2a_client.get_card()


class TestA2AAgentCardFetching:
    """Integration tests for agent card fetching."""

    @pytest.mark.vcr()
    @pytest.mark.asyncio
    async def test_fetch_agent_card(self, a2a_client) -> None:
        """Test fetching an agent card from the server."""
        card = await a2a_client.get_card()

        assert card is not None
        assert card.name == "GPT Assistant"
        assert card.url is not None
        assert card.capabilities is not None
        assert card.capabilities.streaming is True


class TestA2APollingIntegration:
    """Integration tests for A2A polling handler."""

    @pytest.mark.vcr()
    @pytest.mark.asyncio
    async def test_polling_completes_task(
        self,
        a2a_client,
        test_message: Message,
        agent_card: AgentCard,
    ) -> None:
        """Test that polling handler completes a task successfully."""
        new_messages: list[Message] = []

        result = await PollingHandler.execute(
            client=a2a_client,
            message=test_message,
            new_messages=new_messages,
            agent_card=agent_card,
            polling_interval=0.5,
            polling_timeout=30.0,
        )

        assert isinstance(result, dict)
        assert result["status"] == TaskState.completed
        assert result.get("result") is not None
        assert "4" in result["result"]


class TestA2AStreamingIntegration:
    """Integration tests for A2A streaming handler."""

    @pytest.mark.vcr()
    @pytest.mark.asyncio
    async def test_streaming_completes_task(
        self,
        a2a_client,
        test_message: Message,
        agent_card: AgentCard,
    ) -> None:
        """Test that streaming handler completes a task successfully."""
        new_messages: list[Message] = []

        result = await StreamingHandler.execute(
            client=a2a_client,
            message=test_message,
            new_messages=new_messages,
            agent_card=agent_card,
        )

        assert isinstance(result, dict)
        assert result["status"] == TaskState.completed
        assert result.get("result") is not None


class TestA2ATaskOperations:
    """Integration tests for task operations."""

    @pytest.mark.vcr()
    @pytest.mark.asyncio
    async def test_send_message_and_get_response(
        self,
        a2a_client,
        test_message: Message,
    ) -> None:
        """Test sending a message and getting a response."""
        from a2a.types import Task

        final_task: Task | None = None
        async for event in a2a_client.send_message(test_message):
            if isinstance(event, tuple) and len(event) >= 1:
                task, _ = event
                if isinstance(task, Task):
                    final_task = task

        assert final_task is not None
        assert final_task.id is not None
        assert final_task.status is not None
        assert final_task.status.state == TaskState.completed


class TestA2APushNotificationHandler:
    """Tests for push notification handler.

    These tests use mocks for the result store since webhook callbacks
    are incoming requests that can't be recorded with VCR.
    """

    @pytest.fixture
    def mock_agent_card(self) -> AgentCard:
        """Create a minimal valid agent card for testing."""
        from a2a.types import AgentCapabilities

        return AgentCard(
            name="Test Agent",
            description="Test agent for push notification tests",
            url="http://localhost:9999",
            version="1.0.0",
            capabilities=AgentCapabilities(streaming=True, push_notifications=True),
            default_input_modes=["text"],
            default_output_modes=["text"],
            skills=[],
        )

    @pytest.fixture
    def mock_task(self) -> "Task":
        """Create a minimal valid task for testing."""
        from a2a.types import Task, TaskStatus

        return Task(
            id="task-123",
            context_id="ctx-123",
            status=TaskStatus(state=TaskState.working),
        )

    @pytest.mark.asyncio
    async def test_push_handler_waits_for_result(
        self,
        mock_agent_card: AgentCard,
        mock_task,
    ) -> None:
        """Test that push handler waits for result from store."""
        from unittest.mock import AsyncMock, MagicMock

        from a2a.types import Task, TaskStatus
        from pydantic import AnyHttpUrl

        from crewai.a2a.updates.push_notifications.config import PushNotificationConfig
        from crewai.a2a.updates.push_notifications.handler import PushNotificationHandler

        completed_task = Task(
            id="task-123",
            context_id="ctx-123",
            status=TaskStatus(state=TaskState.completed),
            history=[],
        )

        mock_store = MagicMock()
        mock_store.wait_for_result = AsyncMock(return_value=completed_task)

        async def mock_send_message(*args, **kwargs):
            yield (mock_task, None)

        mock_client = MagicMock()
        mock_client.send_message = mock_send_message

        config = PushNotificationConfig(
            url=AnyHttpUrl("http://localhost:8080/a2a/callback"),
            token="secret-token",
            result_store=mock_store,
        )

        test_msg = Message(
            role=Role.user,
            parts=[Part(root=TextPart(text="What is 2+2?"))],
            message_id="msg-001",
        )

        new_messages: list[Message] = []

        result = await PushNotificationHandler.execute(
            client=mock_client,
            message=test_msg,
            new_messages=new_messages,
            agent_card=mock_agent_card,
            config=config,
            result_store=mock_store,
            polling_timeout=30.0,
            polling_interval=1.0,
        )

        mock_store.wait_for_result.assert_called_once_with(
            task_id="task-123",
            timeout=30.0,
            poll_interval=1.0,
        )

        assert result["status"] == TaskState.completed

    @pytest.mark.asyncio
    async def test_push_handler_returns_failure_on_timeout(
        self,
        mock_agent_card: AgentCard,
    ) -> None:
        """Test that push handler returns failure when result store times out."""
        from unittest.mock import AsyncMock, MagicMock

        from a2a.types import Task, TaskStatus
        from pydantic import AnyHttpUrl

        from crewai.a2a.updates.push_notifications.config import PushNotificationConfig
        from crewai.a2a.updates.push_notifications.handler import PushNotificationHandler

        mock_store = MagicMock()
        mock_store.wait_for_result = AsyncMock(return_value=None)

        working_task = Task(
            id="task-456",
            context_id="ctx-456",
            status=TaskStatus(state=TaskState.working),
        )

        async def mock_send_message(*args, **kwargs):
            yield (working_task, None)

        mock_client = MagicMock()
        mock_client.send_message = mock_send_message

        config = PushNotificationConfig(
            url=AnyHttpUrl("http://localhost:8080/a2a/callback"),
            token="token",
            result_store=mock_store,
        )

        test_msg = Message(
            role=Role.user,
            parts=[Part(root=TextPart(text="test"))],
            message_id="msg-002",
        )

        new_messages: list[Message] = []

        result = await PushNotificationHandler.execute(
            client=mock_client,
            message=test_msg,
            new_messages=new_messages,
            agent_card=mock_agent_card,
            config=config,
            result_store=mock_store,
            polling_timeout=5.0,
            polling_interval=0.5,
        )

        assert result["status"] == TaskState.failed
        assert "timeout" in result.get("error", "").lower()

    @pytest.mark.asyncio
    async def test_push_handler_requires_config(
        self,
        mock_agent_card: AgentCard,
    ) -> None:
        """Test that push handler fails gracefully without config."""
        from unittest.mock import MagicMock

        from crewai.a2a.updates.push_notifications.handler import PushNotificationHandler

        mock_client = MagicMock()

        test_msg = Message(
            role=Role.user,
            parts=[Part(root=TextPart(text="test"))],
            message_id="msg-003",
        )

        new_messages: list[Message] = []

        result = await PushNotificationHandler.execute(
            client=mock_client,
            message=test_msg,
            new_messages=new_messages,
            agent_card=mock_agent_card,
        )

        assert result["status"] == TaskState.failed
        assert "config" in result.get("error", "").lower()
