"""Tests for POST /v1/chat/completions endpoint.

This is the critical path — every request goes through this endpoint.
"""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from fastapi import status


class TestChatCompletionsHappyPath:
    """Happy path tests for chat completions."""

    def test_simple_request_returns_200(self, authed_client):
        """Minimal valid request returns 200."""
        with patch("proxym.api.completions.completion") as mock_completion:
            mock_completion.return_value = MagicMock(
                choices=[MagicMock(message=MagicMock(content="Hello!"))],
                usage=MagicMock(prompt_tokens=10, completion_tokens=5),
                model="claude-3-haiku-20240307",
            )

            response = authed_client.post(
                "/v1/chat/completions",
                json={
                    "model": "claude-3-haiku-20240307",
                    "messages": [{"role": "user", "content": "Hi"}],
                },
            )

        assert response.status_code == 200
        data = response.json()
        assert data["choices"][0]["message"]["content"] == "Hello!"

    def test_streaming_request_returns_stream(self, authed_client):
        """stream=True returns streaming response with SSE format."""
        async def mock_stream():
            chunks = [
                {"choices": [{"delta": {"content": "Hello"}}]},
                {"choices": [{"delta": {"content": " world"}}]},
                {"choices": [{"delta": {}, "finish_reason": "stop"}]},
            ]
            for chunk in chunks:
                yield chunk

        with patch("proxym.api.completions.completion") as mock_completion:
            mock_completion.return_value = mock_stream()

            response = authed_client.post(
                "/v1/chat/completions",
                json={
                    "model": "claude-3-haiku-20240307",
                    "messages": [{"role": "user", "content": "Hi"}],
                    "stream": True,
                },
            )

        assert response.status_code == 200
        # Check for SSE format
        content = response.content.decode()
        assert "data:" in content

    def test_model_alias_resolved(self, authed_client):
        """Model 'fast' resolves to actual model."""
        with patch("proxym.api.completions._resolve_model") as mock_resolve:
            mock_resolve.return_value = "claude-3-haiku-20240307"
            with patch("proxym.api.completions.completion") as mock_completion:
                mock_completion.return_value = MagicMock(
                    choices=[MagicMock(message=MagicMock(content="Fast response"))],
                    usage=MagicMock(prompt_tokens=5, completion_tokens=3),
                    model="claude-3-haiku-20240307",
                )

                response = authed_client.post(
                    "/v1/chat/completions",
                    json={
                        "model": "fast",
                        "messages": [{"role": "user", "content": "Hi"}],
                    },
                )

        assert response.status_code == 200

    def test_x_task_tier_header_overrides_routing(self, authed_client):
        """Header X-Task-Tier: complex forces tier."""
        with patch("proxym.api.completions._analyze_request") as mock_analyze:
            mock_analyze.return_value = "complex"
            with patch("proxym.api.completions.completion") as mock_completion:
                mock_completion.return_value = MagicMock(
                    choices=[MagicMock(message=MagicMock(content="Complex response"))],
                    usage=MagicMock(prompt_tokens=100, completion_tokens=50),
                    model="claude-3-opus-20240229",
                )

                response = authed_client.post(
                    "/v1/chat/completions",
                    headers={"X-Task-Tier": "complex"},
                    json={
                        "model": "smart",
                        "messages": [{"role": "user", "content": "Complex task"}],
                    },
                )

        assert response.status_code == 200


class TestChatCompletionsRouting:
    """Routing and fallback tests."""

    def test_routes_to_cheapest_model_for_simple_task(self, authed_client):
        """Simple task routes to cheapest model."""
        with patch("proxym.api.completions._analyze_request") as mock_analyze:
            mock_analyze.return_value = "simple"
            with patch("proxym.api.completions._route_to_model") as mock_route:
                mock_route.return_value = ("claude-3-haiku-20240307", "anthropic")
                with patch("proxym.api.completions.completion") as mock_completion:
                    mock_completion.return_value = MagicMock(
                        choices=[MagicMock(message=MagicMock(content="Simple"))],
                        usage=MagicMock(prompt_tokens=5, completion_tokens=3),
                        model="claude-3-haiku-20240307",
                    )

                    response = authed_client.post(
                        "/v1/chat/completions",
                        json={
                            "model": "fast",
                            "messages": [{"role": "user", "content": "Hi"}],
                        },
                    )

        assert response.status_code == 200
        mock_route.assert_called_once()

    def test_routes_to_premium_model_for_complex_task(self, authed_client):
        """Complex task routes to premium model."""
        with patch("proxym.api.completions._analyze_request") as mock_analyze:
            mock_analyze.return_value = "complex"
            with patch("proxym.api.completions._route_to_model") as mock_route:
                mock_route.return_value = ("claude-3-opus-20240229", "anthropic")
                with patch("proxym.api.completions.completion") as mock_completion:
                    mock_completion.return_value = MagicMock(
                        choices=[MagicMock(message=MagicMock(content="Complex"))],
                        usage=MagicMock(prompt_tokens=500, completion_tokens=200),
                        model="claude-3-opus-20240229",
                    )

                    response = authed_client.post(
                        "/v1/chat/completions",
                        json={
                            "model": "best",
                            "messages": [{"role": "user", "content": "Complex task"}],
                        },
                    )

        assert response.status_code == 200

    def test_fallback_to_next_provider_on_rate_limit(self, authed_client, mock_429_response):
        """429 error triggers fallback to next provider."""
        with patch("proxym.api.completions.completion") as mock_completion:
            # First call raises 429, second succeeds
            mock_completion.side_effect = [
                mock_429_response,
                MagicMock(
                    choices=[MagicMock(message=MagicMock(content="Fallback response"))],
                    usage=MagicMock(prompt_tokens=10, completion_tokens=5),
                    model="gpt-3.5-turbo",
                ),
            ]
            with patch("proxym.api.completions._route_to_model") as mock_route:
                mock_route.side_effect = [
                    ("claude-3-haiku-20240307", "anthropic"),
                    ("gpt-3.5-turbo", "openai"),
                ]

                response = authed_client.post(
                    "/v1/chat/completions",
                    json={
                        "model": "fast",
                        "messages": [{"role": "user", "content": "Hi"}],
                    },
                )

        assert response.status_code == 200

    def test_fallback_exhausted_returns_503(self, authed_client):
        """All providers failing returns 503."""
        with patch("proxym.api.completions.completion") as mock_completion:
            mock_completion.side_effect = Exception("All providers failed")
            with patch("proxym.api.completions._route_to_model") as mock_route:
                mock_route.return_value = ("claude-3-haiku-20240307", "anthropic")

                response = authed_client.post(
                    "/v1/chat/completions",
                    json={
                        "model": "fast",
                        "messages": [{"role": "user", "content": "Hi"}],
                    },
                )

        assert response.status_code == 503


class TestChatCompletionsCostTracking:
    """Cost tracking tests."""

    def test_cost_header_in_response(self, authed_client, assert_cost_header):
        """X-Proxym-Cost header is set after each request."""
        with patch("proxym.api.completions.completion") as mock_completion:
            mock_completion.return_value = MagicMock(
                choices=[MagicMock(message=MagicMock(content="Hi"))],
                usage=MagicMock(prompt_tokens=10, completion_tokens=5),
                model="claude-3-haiku-20240307",
            )

            response = authed_client.post(
                "/v1/chat/completions",
                json={
                    "model": "claude-3-haiku-20240307",
                    "messages": [{"role": "user", "content": "Hi"}],
                },
            )

        assert response.status_code == 200
        assert_cost_header(response)

    def test_cost_recorded_in_ledger(self, authed_client):
        """After request, CostLedger.record() is called."""
        with patch("proxym.api.completions.ledger") as mock_ledger:
            mock_ledger.record = MagicMock()
            with patch("proxym.api.completions.completion") as mock_completion:
                mock_completion.return_value = MagicMock(
                    choices=[MagicMock(message=MagicMock(content="Hi"))],
                    usage=MagicMock(prompt_tokens=10, completion_tokens=5),
                    model="claude-3-haiku-20240307",
                )

                response = authed_client.post(
                    "/v1/chat/completions",
                    json={
                        "model": "claude-3-haiku-20240307",
                        "messages": [{"role": "user", "content": "Hi"}],
                    },
                )

        assert response.status_code == 200
        mock_ledger.record.assert_called_once()


class TestChatCompletionsErrorHandling:
    """Error handling tests."""

    def test_empty_messages_returns_422(self, authed_client):
        """Empty messages array returns 422."""
        response = authed_client.post(
            "/v1/chat/completions",
            json={
                "model": "claude-3-haiku-20240307",
                "messages": [],
            },
        )

        assert response.status_code == 422

    def test_invalid_json_returns_400(self, authed_client):
        """Invalid JSON returns 400."""
        response = authed_client.post(
            "/v1/chat/completions",
            data="not valid json",
            headers={"Content-Type": "application/json"},
        )

        assert response.status_code == 400

    def test_missing_api_key_returns_401(self, client):
        """Missing API key returns 401."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "model": "claude-3-haiku-20240307",
                "messages": [{"role": "user", "content": "Hi"}],
            },
        )

        assert response.status_code == 401

    def test_provider_500_triggers_fallback(self, authed_client, mock_500_response):
        """Provider 500 triggers fallback."""
        with patch("proxym.api.completions.completion") as mock_completion:
            mock_completion.side_effect = [
                mock_500_response,
                MagicMock(
                    choices=[MagicMock(message=MagicMock(content="Fallback"))],
                    usage=MagicMock(prompt_tokens=10, completion_tokens=5),
                    model="gpt-3.5-turbo",
                ),
            ]
            with patch("proxym.api.completions._route_to_model") as mock_route:
                mock_route.side_effect = [
                    ("claude-3-haiku-20240307", "anthropic"),
                    ("gpt-3.5-turbo", "openai"),
                ]

                response = authed_client.post(
                    "/v1/chat/completions",
                    json={
                        "model": "fast",
                        "messages": [{"role": "user", "content": "Hi"}],
                    },
                )

        assert response.status_code == 200


class TestChatCompletionsStreaming:
    """Streaming-specific tests."""

    def test_stream_chunks_are_valid_sse(self, authed_client):
        """Each chunk is 'data: {...}\\n\\n'."""
        async def mock_stream():
            chunks = [
                {"choices": [{"delta": {"content": "Hello"}}]},
                {"choices": [{"delta": {"content": "!"}}]},
                {"choices": [{"delta": {}, "finish_reason": "stop"}]},
            ]
            for chunk in chunks:
                yield chunk

        with patch("proxym.api.completions.completion") as mock_completion:
            mock_completion.return_value = mock_stream()

            response = authed_client.post(
                "/v1/chat/completions",
                json={
                    "model": "claude-3-haiku-20240307",
                    "messages": [{"role": "user", "content": "Hi"}],
                    "stream": True,
                },
            )

        content = response.content.decode()
        lines = content.strip().split("\n")
        for line in lines:
            if line.startswith("data:"):
                assert line.startswith("data: ")

    def test_stream_final_chunk_has_done_marker(self, authed_client):
        """Final chunk: 'data: [DONE]'."""
        async def mock_stream():
            chunks = [
                {"choices": [{"delta": {"content": "Hello"}}]},
                {"choices": [{"delta": {}, "finish_reason": "stop"}]},
            ]
            for chunk in chunks:
                yield chunk

        with patch("proxym.api.completions.completion") as mock_completion:
            mock_completion.return_value = mock_stream()

            response = authed_client.post(
                "/v1/chat/completions",
                json={
                    "model": "claude-3-haiku-20240307",
                    "messages": [{"role": "user", "content": "Hi"}],
                    "stream": True,
                },
            )

        content = response.content.decode()
        assert "[DONE]" in content
