"""Tests for tracing decorators."""

from unittest.mock import MagicMock, patch

import pytest
from opentelemetry.trace import SpanKind

from src.tracing_decorators import trace_agent_operation, trace_tool_execution


class TestTraceAgentOperation:
    """Test suite for @trace_agent_operation decorator."""

    @pytest.mark.asyncio
    async def test_trace_agent_operation_success(self) -> None:
        """Test tracing successful agent operation."""
        with patch("src.tracing_decorators.get_tracer") as mock_get_tracer:
            mock_tracer = MagicMock()
            mock_span = MagicMock()
            mock_tracer.start_as_current_span.return_value.__enter__ = MagicMock(
                return_value=mock_span
            )
            mock_tracer.start_as_current_span.return_value.__exit__ = MagicMock(
                return_value=None
            )
            mock_get_tracer.return_value = mock_tracer

            @trace_agent_operation(operation_name="test_operation")
            async def test_func(issue_id: int) -> str:
                return f"processed-{issue_id}"

            result = await test_func(issue_id=42)

            assert result == "processed-42"
            mock_tracer.start_as_current_span.assert_called_once_with(
                "agent.test_operation", kind=SpanKind.INTERNAL
            )
            mock_span.set_attribute.assert_any_call("agent.issue_id", 42)

    @pytest.mark.asyncio
    async def test_trace_agent_operation_with_attributes(self) -> None:
        """Test tracing with custom attributes."""
        with patch("src.tracing_decorators.get_tracer") as mock_get_tracer:
            mock_tracer = MagicMock()
            mock_span = MagicMock()
            mock_tracer.start_as_current_span.return_value.__enter__ = MagicMock(
                return_value=mock_span
            )
            mock_tracer.start_as_current_span.return_value.__exit__ = MagicMock(
                return_value=None
            )
            mock_get_tracer.return_value = mock_tracer

            @trace_agent_operation(operation_name="test_op")
            async def test_func(issue_id: int, agent_type: str) -> str:
                return "done"

            await test_func(issue_id=42, agent_type="maintainer")

            mock_span.set_attribute.assert_any_call("agent.issue_id", 42)
            mock_span.set_attribute.assert_any_call("agent.agent_type", "maintainer")

    @pytest.mark.asyncio
    async def test_trace_agent_operation_error(self) -> None:
        """Test tracing when operation raises exception."""
        with patch("src.tracing_decorators.get_tracer") as mock_get_tracer:
            mock_tracer = MagicMock()
            mock_span = MagicMock()
            mock_tracer.start_as_current_span.return_value.__enter__ = MagicMock(
                return_value=mock_span
            )
            mock_tracer.start_as_current_span.return_value.__exit__ = MagicMock(
                return_value=None
            )
            mock_get_tracer.return_value = mock_tracer

            @trace_agent_operation(operation_name="failing_op")
            async def test_func() -> None:
                raise ValueError("Test error")

            with pytest.raises(ValueError, match="Test error"):
                await test_func()

            mock_span.record_exception.assert_called_once()
            mock_span.set_status.assert_called_once()

    @pytest.mark.asyncio
    async def test_trace_agent_operation_sync_function(self) -> None:
        """Test decorator works with sync functions."""
        with patch("src.tracing_decorators.get_tracer") as mock_get_tracer:
            mock_tracer = MagicMock()
            mock_span = MagicMock()
            mock_tracer.start_as_current_span.return_value.__enter__ = MagicMock(
                return_value=mock_span
            )
            mock_tracer.start_as_current_span.return_value.__exit__ = MagicMock(
                return_value=None
            )
            mock_get_tracer.return_value = mock_tracer

            @trace_agent_operation(operation_name="sync_op")
            def test_func() -> str:
                return "sync_result"

            result = test_func()

            assert result == "sync_result"
            mock_tracer.start_as_current_span.assert_called_once()


class TestTraceToolExecution:
    """Test suite for @trace_tool_execution decorator."""

    @pytest.mark.asyncio
    async def test_trace_tool_execution_success(self) -> None:
        """Test tracing successful tool execution."""
        with patch("src.tracing_decorators.get_tracer") as mock_get_tracer:
            mock_tracer = MagicMock()
            mock_span = MagicMock()
            mock_tracer.start_as_current_span.return_value.__enter__ = MagicMock(
                return_value=mock_span
            )
            mock_tracer.start_as_current_span.return_value.__exit__ = MagicMock(
                return_value=None
            )
            mock_get_tracer.return_value = mock_tracer

            @trace_tool_execution(tool_name="test_tool")
            async def test_func(param: str) -> str:
                return f"result-{param}"

            result = await test_func(param="value")

            assert result == "result-value"
            mock_tracer.start_as_current_span.assert_called_once_with(
                "tool.test_tool", kind=SpanKind.CLIENT
            )
            mock_span.set_attribute.assert_any_call("tool.name", "test_tool")

    @pytest.mark.asyncio
    async def test_trace_tool_execution_with_params(self) -> None:
        """Test tracing tool with parameter attributes."""
        with patch("src.tracing_decorators.get_tracer") as mock_get_tracer:
            mock_tracer = MagicMock()
            mock_span = MagicMock()
            mock_tracer.start_as_current_span.return_value.__enter__ = MagicMock(
                return_value=mock_span
            )
            mock_tracer.start_as_current_span.return_value.__exit__ = MagicMock(
                return_value=None
            )
            mock_get_tracer.return_value = mock_tracer

            @trace_tool_execution(tool_name="parser")
            async def test_func(issue_number: int, content: str) -> str:
                return "parsed"

            await test_func(issue_number=123, content="test content")

            mock_span.set_attribute.assert_any_call("tool.name", "parser")
            mock_span.set_attribute.assert_any_call("tool.issue_number", 123)

    @pytest.mark.asyncio
    async def test_trace_tool_execution_error(self) -> None:
        """Test tracing when tool execution fails."""
        with patch("src.tracing_decorators.get_tracer") as mock_get_tracer:
            mock_tracer = MagicMock()
            mock_span = MagicMock()
            mock_tracer.start_as_current_span.return_value.__enter__ = MagicMock(
                return_value=mock_span
            )
            mock_tracer.start_as_current_span.return_value.__exit__ = MagicMock(
                return_value=None
            )
            mock_get_tracer.return_value = mock_tracer

            @trace_tool_execution(tool_name="failing_tool")
            async def test_func() -> None:
                raise RuntimeError("Tool failed")

            with pytest.raises(RuntimeError, match="Tool failed"):
                await test_func()

            mock_span.record_exception.assert_called_once()
            mock_span.set_status.assert_called_once()

    def test_trace_tool_execution_sync_function(self) -> None:
        """Test decorator works with sync functions."""
        with patch("src.tracing_decorators.get_tracer") as mock_get_tracer:
            mock_tracer = MagicMock()
            mock_span = MagicMock()
            mock_tracer.start_as_current_span.return_value.__enter__ = MagicMock(
                return_value=mock_span
            )
            mock_tracer.start_as_current_span.return_value.__exit__ = MagicMock(
                return_value=None
            )
            mock_get_tracer.return_value = mock_tracer

            @trace_tool_execution(tool_name="sync_tool")
            def test_func(value: int) -> int:
                return value * 2

            result = test_func(value=5)

            assert result == 10
            mock_tracer.start_as_current_span.assert_called_once()
