"""
Tests for laneswap FastAPI integration.

This module tests the FastAPI framework integration with real FastAPI instances.
"""

import json
from typing import Dict

import pytest

fastapi = pytest.importorskip("fastapi")
from fastapi import FastAPI
from fastapi.testclient import TestClient

from laneswap import LaneSwap
from laneswap.integrations.fastapi import setup_fastapi_integration


class TestFastAPIIntegration:
    """Test FastAPI integration functionality."""

    def test_setup_fastapi_integration_registers_health_endpoint(
        self, clean_env: None
    ) -> None:
        """Test that setup_fastapi_integration registers /health endpoint."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        assert response.status_code == 200

    def test_setup_fastapi_integration_raises_on_non_fastapi_app(
        self, clean_env: None
    ) -> None:
        """Test that TypeError is raised for non-FastAPI apps."""
        not_fastapi = object()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        with pytest.raises(TypeError) as exc_info:
            setup_fastapi_integration(not_fastapi, ls)  # type: ignore

        assert "Expected FastAPI app" in str(exc_info.value)

    def test_health_endpoint_returns_json(self, clean_env: None) -> None:
        """Test that /health endpoint returns JSON response."""
        app = FastAPI()
        ls = LaneSwap(
            service_name="fastapi-test",
            setup_signals=False,
            setup_logs=False,
        )

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        assert response.headers["content-type"] == "application/json"

    def test_health_endpoint_returns_correct_structure(
        self, clean_env: None, mock_time: pytest.MonkeyPatch
    ) -> None:
        """Test that /health endpoint returns correctly structured response."""
        app = FastAPI()
        ls = LaneSwap(
            service_name="fastapi-test",
            setup_signals=False,
            setup_logs=False,
        )

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert data["status"] == "healthy"
        assert data["service"] == "fastapi-test"
        assert "timestamp" in data

    def test_health_endpoint_includes_sync_checks(self, clean_env: None) -> None:
        """Test that /health endpoint includes synchronous health check results."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        def check_database() -> Dict[str, str]:
            return {"database": "connected"}

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert data["database"] == "connected"

    @pytest.mark.asyncio
    async def test_health_endpoint_includes_async_checks(
        self, clean_env: None
    ) -> None:
        """Test that /health endpoint includes async health check results."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        async def check_redis() -> Dict[str, str]:
            return {"redis": "connected"}

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert data["redis"] == "connected"

    @pytest.mark.asyncio
    async def test_health_endpoint_includes_mixed_checks(
        self, clean_env: None
    ) -> None:
        """Test /health endpoint with both sync and async health checks."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        def sync_check() -> Dict[str, str]:
            return {"database": "connected"}

        @ls.health_check
        async def async_check() -> Dict[str, str]:
            return {"redis": "connected"}

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert data["database"] == "connected"
        assert data["redis"] == "connected"

    def test_health_endpoint_handles_failing_sync_checks(
        self, clean_env: None
    ) -> None:
        """Test that /health endpoint returns 503 when sync health checks fail."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        def failing_check() -> Dict[str, str]:
            raise RuntimeError("Database connection failed")

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert response.status_code == 503
        assert data["status"] == "unhealthy"
        assert "error" in data

    @pytest.mark.asyncio
    async def test_health_endpoint_handles_failing_async_checks(
        self, clean_env: None
    ) -> None:
        """Test that /health endpoint returns 503 when async health checks fail."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        async def failing_async_check() -> Dict[str, str]:
            raise RuntimeError("Redis connection failed")

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert response.status_code == 503
        assert data["status"] == "unhealthy"
        assert "error" in data

    def test_health_endpoint_handles_empty_checks(self, clean_env: None) -> None:
        """Test /health endpoint works with no custom health checks."""
        app = FastAPI()
        ls = LaneSwap(
            service_name="simple-service",
            setup_signals=False,
            setup_logs=False,
        )

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert response.status_code == 200
        assert data["status"] == "healthy"
        assert data["service"] == "simple-service"

    def test_health_endpoint_only_accepts_get(self, clean_env: None) -> None:
        """Test that /health endpoint only accepts GET requests."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        setup_fastapi_integration(app, ls)

        client = TestClient(app)

        # GET should work
        response = client.get("/health")
        assert response.status_code == 200

        # POST should fail
        response = client.post("/health")
        assert response.status_code == 405

        # PUT should fail
        response = client.put("/health")
        assert response.status_code == 405

        # DELETE should fail
        response = client.delete("/health")
        assert response.status_code == 405

    def test_laneswap_auto_integration_with_fastapi(
        self, clean_env: None
    ) -> None:
        """Test that LaneSwap automatically integrates with FastAPI app."""
        app = FastAPI()
        ls = LaneSwap(app=app, setup_signals=False, setup_logs=False)

        # /health endpoint should be automatically registered
        client = TestClient(app)
        response = client.get("/health")
        assert response.status_code == 200

    @pytest.mark.asyncio
    async def test_multiple_async_checks_run_concurrently(
        self, clean_env: None
    ) -> None:
        """Test that multiple async health checks run concurrently."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        call_order = []

        @ls.health_check
        async def check1() -> Dict[str, str]:
            call_order.append(1)
            return {"check1": "ok"}

        @ls.health_check
        async def check2() -> Dict[str, str]:
            call_order.append(2)
            return {"check2": "ok"}

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert data["check1"] == "ok"
        assert data["check2"] == "ok"
        # Both checks should have been called
        assert len(call_order) == 2

    def test_health_check_with_none_return(self, clean_env: None) -> None:
        """Test health check that returns None doesn't break endpoint."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        def check_returns_none() -> None:
            return None

        @ls.health_check
        async def async_check_returns_data() -> Dict[str, str]:
            return {"data": "present"}

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert response.status_code == 200
        assert data["data"] == "present"

    def test_fastapi_integration_with_custom_service_name(
        self, clean_env: None
    ) -> None:
        """Test FastAPI integration with custom service name."""
        app = FastAPI()
        ls = LaneSwap(
            service_name="my-custom-service",
            setup_signals=False,
            setup_logs=False,
        )

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert data["service"] == "my-custom-service"

    def test_fastapi_integration_preserves_existing_routes(
        self, clean_env: None
    ) -> None:
        """Test that FastAPI integration doesn't interfere with existing routes."""
        app = FastAPI()

        @app.get("/")
        def index() -> Dict[str, str]:
            return {"message": "Hello World"}

        @app.get("/api/data")
        def api_data() -> Dict[str, str]:
            return {"data": "API Data"}

        ls = LaneSwap(setup_signals=False, setup_logs=False)
        setup_fastapi_integration(app, ls)

        client = TestClient(app)

        # Existing routes should still work
        response = client.get("/")
        assert response.status_code == 200
        assert response.json()["message"] == "Hello World"

        response = client.get("/api/data")
        assert response.status_code == 200
        assert response.json()["data"] == "API Data"

        # Health endpoint should also work
        response = client.get("/health")
        assert response.status_code == 200


class TestFastAPIIntegrationEdgeCases:
    """Test edge cases in FastAPI integration."""

    def test_health_check_exception_contains_error_message(
        self, clean_env: None
    ) -> None:
        """Test that exception message is included in error response."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        def failing_check() -> Dict[str, str]:
            raise ValueError("Specific error message")

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert "Specific error message" in data["error"]

    def test_concurrent_health_check_requests(self, clean_env: None) -> None:
        """Test that health endpoint can handle concurrent requests."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        call_count = []

        @ls.health_check
        def check() -> Dict[str, str]:
            call_count.append(1)
            return {"count": len(call_count)}

        setup_fastapi_integration(app, ls)

        client = TestClient(app)

        # Make multiple requests
        responses = [client.get("/health") for _ in range(3)]

        # All should succeed
        for response in responses:
            assert response.status_code == 200

    @pytest.mark.asyncio
    async def test_async_health_check_with_actual_async_operation(
        self, clean_env: None
    ) -> None:
        """Test async health check with actual async operation."""
        import asyncio

        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        async def async_check() -> Dict[str, str]:
            await asyncio.sleep(0.01)  # Simulate async operation
            return {"async_op": "completed"}

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        assert data["async_op"] == "completed"

    def test_health_endpoint_response_headers(self, clean_env: None) -> None:
        """Test that health endpoint has correct response headers."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")

        assert "content-type" in response.headers
        assert "application/json" in response.headers["content-type"]

    def test_overlapping_check_keys_merge_correctly(
        self, clean_env: None
    ) -> None:
        """Test that overlapping keys from different checks merge correctly."""
        app = FastAPI()
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        def sync_check() -> Dict[str, str]:
            return {"component": "sync_value", "sync_only": "present"}

        @ls.health_check
        async def async_check() -> Dict[str, str]:
            return {"component": "async_value", "async_only": "present"}

        setup_fastapi_integration(app, ls)

        client = TestClient(app)
        response = client.get("/health")
        data = response.json()

        # Both unique keys should be present
        assert "sync_only" in data
        assert "async_only" in data
        # Overlapping key should be present (async wins due to dict merge)
        assert "component" in data
