"""Tests for asyncio/FastAPI compatibility.

AC Round 6 requirement: Verify SDK works correctly in async contexts
(FastAPI, async web frameworks) without deadlocks or context loss.
"""

import asyncio
import pytest
from mca_sdk import MCAClient


@pytest.mark.enable_socket
class TestAsyncioCompatibility:
    """Test SDK functions correctly inside async event loops."""

    @pytest.mark.asyncio
    async def test_sdk_initialization_in_async_context(self):
        """Test SDK can be initialized inside async function.

        Verifies no blocking I/O during initialization that would deadlock event loop.
        """
        client = MCAClient(
            service_name="async-service",
            model_id="mdl-async-001",
            team_name="test-team",
        )

        # Should initialize without blocking the event loop
        assert client is not None
        assert client.service_name == "async-service"

        client.shutdown()

    @pytest.mark.asyncio
    async def test_record_prediction_in_async_context(self):
        """Test record_prediction() works inside async function.

        Verifies telemetry recording doesn't block event loop or lose context.
        """
        client = MCAClient(
            service_name="async-telemetry",
            model_id="mdl-async-002",
            team_name="test-team",
        )

        # Record telemetry inside async context
        client.record_prediction(latency=0.15, prediction_id="async-pred-1")

        # Should not block
        await asyncio.sleep(0.01)

        # Record more telemetry
        client.record_prediction(latency=0.20, prediction_id="async-pred-2")

        client.shutdown()

    @pytest.mark.asyncio
    async def test_concurrent_async_tasks_with_sdk(self):
        """Test SDK handles concurrent async tasks correctly.

        Verifies contextvars propagation across await boundaries.
        """
        client = MCAClient(
            service_name="concurrent-async",
            model_id="mdl-async-003",
            team_name="test-team",
        )

        async def async_prediction_task(task_id: int):
            """Simulate async prediction workflow."""
            # Simulate async I/O
            await asyncio.sleep(0.01)

            # Record prediction
            client.record_prediction(latency=0.1 + task_id * 0.01, prediction_id=f"task-{task_id}")

            await asyncio.sleep(0.01)
            return task_id

        # Run multiple concurrent async tasks
        results = await asyncio.gather(
            async_prediction_task(1),
            async_prediction_task(2),
            async_prediction_task(3),
            async_prediction_task(4),
            async_prediction_task(5),
        )

        assert len(results) == 5
        assert results == [1, 2, 3, 4, 5]

        client.shutdown()

    @pytest.mark.asyncio
    async def test_trace_context_manager_in_async_function(self):
        """Test trace() context manager preserves context across await.

        AC Round 6: Verifies contextvars don't leak when await yields control.
        """
        client = MCAClient(
            service_name="async-trace", model_id="mdl-async-004", team_name="test-team"
        )

        async def traced_async_operation():
            """Async operation with tracing."""
            with client.trace("async_operation") as span:
                # Simulate async work
                await asyncio.sleep(0.01)

                # Record metric inside traced context
                client.record_prediction(latency=0.1)

                # More async work
                await asyncio.sleep(0.01)

                return "success"

        result = await traced_async_operation()
        assert result == "success"

        client.shutdown()

    @pytest.mark.asyncio
    async def test_shutdown_from_async_context(self):
        """Test shutdown() can be called from async function.

        Verifies cleanup doesn't deadlock when called from event loop.
        """
        client = MCAClient(
            service_name="async-shutdown",
            model_id="mdl-async-005",
            team_name="test-team",
        )

        client.record_prediction(latency=0.15)

        # Shutdown from async context should not block
        client.shutdown(timeout_millis=5000)

        # Should complete without deadlock
        await asyncio.sleep(0.01)

    @pytest.mark.asyncio
    async def test_multiple_sdk_instances_in_async_context(self):
        """Test multiple SDK instances can coexist in async context.

        Verifies contextvars isolation between different SDK instances.
        """
        client1 = MCAClient(
            service_name="async-client-1", model_id="mdl-async-101", team_name="team-1"
        )

        client2 = MCAClient(
            service_name="async-client-2", model_id="mdl-async-102", team_name="team-2"
        )

        async def task_with_client1():
            await asyncio.sleep(0.01)
            client1.record_prediction(latency=0.1, prediction_id="client1-pred")

        async def task_with_client2():
            await asyncio.sleep(0.01)
            client2.record_prediction(latency=0.2, prediction_id="client2-pred")

        # Run both concurrently
        await asyncio.gather(task_with_client1(), task_with_client2())

        client1.shutdown()
        client2.shutdown()

    @pytest.mark.asyncio
    async def test_exception_propagation_in_async_trace(self):
        """Test exceptions propagate correctly through trace context manager in async.

        AC Round 6: Verifies span.end() called even when async function raises.
        """
        client = MCAClient(
            service_name="async-exception",
            model_id="mdl-async-006",
            team_name="test-team",
        )

        async def failing_async_operation():
            """Async operation that raises exception."""
            with client.trace("failing_operation"):
                await asyncio.sleep(0.01)
                raise ValueError("Async operation failed")

        with pytest.raises(ValueError, match="Async operation failed"):
            await failing_async_operation()

        # Span should be closed despite exception
        client.shutdown()


@pytest.mark.enable_socket
class TestFastAPISimulation:
    """Simulate FastAPI request handling patterns."""

    @pytest.mark.asyncio
    async def test_fastapi_request_handler_pattern(self):
        """Simulate typical FastAPI endpoint with SDK instrumentation.

        Pattern: Initialize SDK once, use across multiple async requests.
        """
        # SDK initialized at app startup (module level in real app)
        client = MCAClient(
            service_name="fastapi-app", model_id="mdl-fastapi-001", team_name="api-team"
        )

        async def predict_endpoint(input_data: dict):
            """Simulated FastAPI endpoint."""
            with client.trace("predict_request"):
                # Simulate async model inference
                await asyncio.sleep(0.02)

                prediction = {"result": input_data.get("value", 0) * 2}

                client.record_prediction(latency=0.02, prediction_id=f"req-{input_data['id']}")

                return prediction

        # Simulate concurrent requests
        results = await asyncio.gather(
            predict_endpoint({"id": 1, "value": 10}),
            predict_endpoint({"id": 2, "value": 20}),
            predict_endpoint({"id": 3, "value": 30}),
        )

        assert len(results) == 3
        assert results[0]["result"] == 20
        assert results[1]["result"] == 40
        assert results[2]["result"] == 60

        client.shutdown()

    @pytest.mark.asyncio
    async def test_fastapi_background_task_pattern(self):
        """Test SDK with FastAPI background tasks (fire-and-forget).

        Verifies telemetry recording in background tasks doesn't lose context.
        """
        client = MCAClient(
            service_name="fastapi-bg", model_id="mdl-fastapi-bg", team_name="api-team"
        )

        background_task_executed = asyncio.Event()

        async def background_telemetry_task(request_id: str):
            """Simulated FastAPI background task."""
            await asyncio.sleep(0.05)

            client.record_prediction(latency=0.05, prediction_id=f"bg-{request_id}")

            background_task_executed.set()

        async def endpoint_with_background_task():
            """Endpoint that spawns background task."""
            # Spawn background task (don't await)
            asyncio.create_task(background_telemetry_task("req-123"))

            return {"status": "accepted"}

        response = await endpoint_with_background_task()
        assert response["status"] == "accepted"

        # Wait for background task to complete
        await asyncio.wait_for(background_task_executed.wait(), timeout=1.0)

        client.shutdown()
