"""
Tests for Pydantic AI integration.

Covers:
    - run_sync creates agent span
    - Disabled tracer passthrough
    - Error recording
    - Model name capture
    - Result type capture
    - Tool names capture
    - Async run creates span
"""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import pytest


class TestPydanticAIIntegration:
    """Tests for Pydantic AI Agent.run/run_sync/run_stream patches."""

    @pytest.fixture
    def mock_tracer(self):
        tracer = MagicMock()
        tracer.is_enabled = True
        mock_span = MagicMock()
        mock_span.span_id = "abcdef0123456789"
        mock_span.trace_id = "a" * 32
        tracer.start_span.return_value.__enter__ = MagicMock(return_value=mock_span)
        tracer.start_span.return_value.__exit__ = MagicMock(return_value=False)
        return tracer

    @pytest.fixture
    def mock_agent(self):
        """Create a mock Pydantic AI Agent instance."""
        agent = MagicMock()
        agent.model = "openai:gpt-4o"
        agent.name = "TestAgent"
        agent.result_type = type("UserModel", (), {})
        agent.system_prompt = "You are a helpful assistant."
        agent._tools = {"search": MagicMock(), "calculator": MagicMock()}
        return agent

    @pytest.fixture
    def mock_result(self):
        """Create a mock RunResult."""
        result = MagicMock()
        result.output = "Hello, world!"
        usage = MagicMock()
        usage.input_tokens = 100
        usage.output_tokens = 50
        usage.requests = 1
        result.usage = MagicMock(return_value=usage)
        return result

    def test_run_sync_creates_span(self, mock_tracer, mock_agent, mock_result):
        """run_sync should create an agent span."""
        from risicare.integrations.pydantic_ai._patches import _wrap_run_sync

        mock_wrapped = MagicMock(return_value=mock_result)

        with patch(
            "risicare.integrations.pydantic_ai._patches.get_tracer",
            return_value=mock_tracer,
        ), patch(
            "risicare.integrations.pydantic_ai._patches.is_tracing_enabled",
            return_value=True,
        ):
            result = _wrap_run_sync(mock_wrapped, mock_agent, ("Hello",), {})

        assert result is mock_result
        mock_tracer.start_span.assert_called_once()
        call_kwargs = mock_tracer.start_span.call_args
        assert "pydantic_ai.agent.run/TestAgent" in str(call_kwargs)

    def test_disabled_tracer_passthrough(self, mock_agent, mock_result):
        """When tracing is disabled, run_sync should pass through."""
        from risicare.integrations.pydantic_ai._patches import _wrap_run_sync

        mock_wrapped = MagicMock(return_value=mock_result)

        with patch(
            "risicare.integrations.pydantic_ai._patches.is_tracing_enabled",
            return_value=False,
        ):
            result = _wrap_run_sync(mock_wrapped, mock_agent, ("Hello",), {})

        assert result is mock_result
        mock_wrapped.assert_called_once_with("Hello")

    def test_records_error_on_exception(self, mock_tracer, mock_agent):
        """Exceptions should be recorded on the span."""
        from risicare.integrations.pydantic_ai._patches import _wrap_run_sync

        error = RuntimeError("Agent failed")
        mock_wrapped = MagicMock(side_effect=error)

        mock_span = MagicMock()
        mock_tracer.start_span.return_value.__enter__ = MagicMock(return_value=mock_span)

        with patch(
            "risicare.integrations.pydantic_ai._patches.get_tracer",
            return_value=mock_tracer,
        ), patch(
            "risicare.integrations.pydantic_ai._patches.is_tracing_enabled",
            return_value=True,
        ):
            with pytest.raises(RuntimeError, match="Agent failed"):
                _wrap_run_sync(mock_wrapped, mock_agent, ("Hello",), {})

        mock_span.record_exception.assert_called_once_with(error)
        mock_span.set_attribute.assert_any_call("error", True)

    def test_captures_model_name(self, mock_tracer, mock_agent, mock_result):
        """Model string should be captured in attributes."""
        from risicare.integrations.pydantic_ai._patches import _wrap_run_sync

        mock_wrapped = MagicMock(return_value=mock_result)

        with patch(
            "risicare.integrations.pydantic_ai._patches.get_tracer",
            return_value=mock_tracer,
        ), patch(
            "risicare.integrations.pydantic_ai._patches.is_tracing_enabled",
            return_value=True,
        ):
            _wrap_run_sync(mock_wrapped, mock_agent, ("Hello",), {})

        call_kwargs = mock_tracer.start_span.call_args
        attrs = call_kwargs.kwargs.get("attributes", call_kwargs[1].get("attributes", {}))
        assert attrs.get("framework.pydantic_ai.model") == "openai:gpt-4o"

    def test_captures_result_type(self, mock_tracer, mock_agent, mock_result):
        """Result type class name should be captured."""
        from risicare.integrations.pydantic_ai._patches import _wrap_run_sync

        mock_wrapped = MagicMock(return_value=mock_result)

        with patch(
            "risicare.integrations.pydantic_ai._patches.get_tracer",
            return_value=mock_tracer,
        ), patch(
            "risicare.integrations.pydantic_ai._patches.is_tracing_enabled",
            return_value=True,
        ):
            _wrap_run_sync(mock_wrapped, mock_agent, ("Hello",), {})

        call_kwargs = mock_tracer.start_span.call_args
        attrs = call_kwargs.kwargs.get("attributes", call_kwargs[1].get("attributes", {}))
        assert attrs.get("framework.pydantic_ai.result_type") == "UserModel"

    def test_captures_tool_names(self, mock_tracer, mock_agent, mock_result):
        """Tool names should be captured from the agent."""
        from risicare.integrations.pydantic_ai._patches import _wrap_run_sync

        mock_wrapped = MagicMock(return_value=mock_result)

        with patch(
            "risicare.integrations.pydantic_ai._patches.get_tracer",
            return_value=mock_tracer,
        ), patch(
            "risicare.integrations.pydantic_ai._patches.is_tracing_enabled",
            return_value=True,
        ):
            _wrap_run_sync(mock_wrapped, mock_agent, ("Hello",), {})

        call_kwargs = mock_tracer.start_span.call_args
        attrs = call_kwargs.kwargs.get("attributes", call_kwargs[1].get("attributes", {}))
        tools = attrs.get("framework.pydantic_ai.tools", [])
        assert "search" in tools
        assert "calculator" in tools

    @pytest.mark.asyncio
    async def test_run_async_creates_span(self, mock_tracer, mock_agent, mock_result):
        """Async run() should create an agent span."""
        from risicare.integrations.pydantic_ai._patches import _wrap_run

        mock_wrapped = AsyncMock(return_value=mock_result)

        with patch(
            "risicare.integrations.pydantic_ai._patches.get_tracer",
            return_value=mock_tracer,
        ), patch(
            "risicare.integrations.pydantic_ai._patches.is_tracing_enabled",
            return_value=True,
        ):
            result = await _wrap_run(mock_wrapped, mock_agent, ("Hello",), {})

        assert result is mock_result
        mock_tracer.start_span.assert_called_once()
        call_kwargs = mock_tracer.start_span.call_args
        assert "pydantic_ai.agent.run/TestAgent" in str(call_kwargs)
