#!/usr/bin/env python3
"""services/openai_compat_server.py 테스트 스위트

FastAPI TestClient를 사용한 엔드포인트 통합 테스트 +
Pydantic 모델 단위 테스트.
"""

import json
import sys
import time
from pathlib import Path
from typing import Any, Dict

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent.parent))

# fastapi/httpx availability guard
try:
    from fastapi.testclient import TestClient
    from services.openai_compat_server import (
        app,
        ChatCompletionRequest,
        ChatCompletionResponse,
    )
    FASTAPI_AVAILABLE = True
except ImportError:
    FASTAPI_AVAILABLE = False


pytestmark = pytest.mark.skipif(
    not FASTAPI_AVAILABLE,
    reason="fastapi not installed",
)


# ---------------------------------------------------------------------------
# Pydantic 모델 단위 테스트
# ---------------------------------------------------------------------------


class TestChatCompletionRequest:
    """ChatCompletionRequest Pydantic 모델 테스트"""

    def test_basic_creation(self) -> None:
        """기본 필드로 생성"""
        req = ChatCompletionRequest(
            model="gpt-4o",
            messages=[{"role": "user", "content": "Hello"}],
        )
        assert req.model == "gpt-4o"
        assert len(req.messages) == 1

    def test_defaults(self) -> None:
        """선택 필드 기본값 확인"""
        req = ChatCompletionRequest(
            model="claude-sonnet-4-6",
            messages=[{"role": "user", "content": "hi"}],
        )
        assert req.temperature is None or isinstance(req.temperature, float)
        assert req.max_tokens is None or isinstance(req.max_tokens, int)
        assert req.stream is False or req.stream is None or isinstance(req.stream, bool)

    def test_with_all_fields(self) -> None:
        """모든 필드 설정"""
        req = ChatCompletionRequest(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": "You are helpful."},
                {"role": "user", "content": "Summarize this."},
            ],
            temperature=0.7,
            max_tokens=512,
            stream=False,
        )
        assert req.temperature == 0.7
        assert req.max_tokens == 512
        assert req.stream is False

    def test_stream_true(self) -> None:
        """stream=True 설정"""
        req = ChatCompletionRequest(
            model="gpt-4o",
            messages=[{"role": "user", "content": "hi"}],
            stream=True,
        )
        assert req.stream is True


class TestChatCompletionResponse:
    """ChatCompletionResponse Pydantic 모델 테스트"""

    def test_openai_format_fields(self) -> None:
        """OpenAI 형식 필드 구조 확인"""
        resp = ChatCompletionResponse(
            id="chatcmpl-test-123",
            object="chat.completion",
            created=int(time.time()),
            model="gpt-4o",
            choices=[
                {
                    "index": 0,
                    "message": {"role": "assistant", "content": "Hello!"},
                    "finish_reason": "stop",
                }
            ],
        )
        assert resp.id.startswith("chatcmpl-")
        assert resp.object == "chat.completion"
        assert len(resp.choices) == 1

    def test_usage_field_optional(self) -> None:
        """usage 필드는 선택적"""
        resp = ChatCompletionResponse(
            id="chatcmpl-abc",
            object="chat.completion",
            created=1234567890,
            model="claude-sonnet-4-6",
            choices=[],
        )
        # usage가 없어도 생성 가능
        assert resp.id == "chatcmpl-abc"


# ---------------------------------------------------------------------------
# FastAPI 엔드포인트 통합 테스트
# ---------------------------------------------------------------------------


@pytest.fixture
def client():
    """FastAPI TestClient fixture"""
    return TestClient(app)


class TestHealthEndpoint:
    """GET /health 테스트"""

    def test_health_returns_200(self, client) -> None:
        """상태 확인 - 200 OK"""
        response = client.get("/health")
        assert response.status_code == 200

    def test_health_response_body(self, client) -> None:
        """health 응답 본문에 status 필드 포함"""
        response = client.get("/health")
        body = response.json()
        assert "status" in body
        assert body["status"] == "ok"


class TestModelsEndpoint:
    """GET /v1/models 테스트"""

    def test_models_returns_200(self, client) -> None:
        """모델 목록 - 200 OK"""
        response = client.get("/v1/models")
        assert response.status_code == 200

    def test_models_response_format(self, client) -> None:
        """OpenAI 형식 응답 구조 확인"""
        response = client.get("/v1/models")
        body = response.json()
        assert "object" in body
        assert body["object"] == "list"
        assert "data" in body
        assert isinstance(body["data"], list)

    def test_models_list_not_empty(self, client) -> None:
        """모델 목록이 비어있지 않음"""
        response = client.get("/v1/models")
        body = response.json()
        assert len(body["data"]) > 0

    def test_model_entry_has_id(self, client) -> None:
        """각 모델 항목에 id 필드 존재"""
        response = client.get("/v1/models")
        body = response.json()
        for model_entry in body["data"]:
            assert "id" in model_entry


class TestChatCompletionsEndpoint:
    """POST /v1/chat/completions 테스트"""

    def _basic_payload(self) -> Dict[str, Any]:
        return {
            "model": "gpt-4o",
            "messages": [{"role": "user", "content": "Hello"}],
        }

    def test_completions_returns_200(self, client) -> None:
        """기본 요청 - 200 OK"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        assert response.status_code == 200

    def test_completions_response_has_id(self, client) -> None:
        """응답에 id 필드 포함"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        body = response.json()
        assert "id" in body
        assert body["id"].startswith("chatcmpl-")

    def test_completions_response_object_type(self, client) -> None:
        """응답 object 필드가 chat.completion"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        body = response.json()
        assert body.get("object") == "chat.completion"

    def test_completions_response_has_choices(self, client) -> None:
        """응답에 choices 배열 포함"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        body = response.json()
        assert "choices" in body
        assert isinstance(body["choices"], list)
        assert len(body["choices"]) > 0

    def test_completions_choice_has_message(self, client) -> None:
        """choices[0]에 message 필드 포함"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        body = response.json()
        choice = body["choices"][0]
        assert "message" in choice
        assert "role" in choice["message"]
        assert choice["message"]["role"] == "assistant"

    def test_completions_response_has_model(self, client) -> None:
        """응답에 model 필드 포함"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        body = response.json()
        assert "model" in body

    def test_completions_response_has_created(self, client) -> None:
        """응답에 created 타임스탬프 포함"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        body = response.json()
        assert "created" in body
        assert isinstance(body["created"], int)

    def test_completions_invalid_request_missing_messages(self, client) -> None:
        """messages 없는 요청 - 422 Unprocessable Entity"""
        response = client.post(
            "/v1/chat/completions", json={"model": "gpt-4o"}
        )
        assert response.status_code == 422

    def test_completions_invalid_request_missing_model(self, client) -> None:
        """model 없는 요청 - 422"""
        response = client.post(
            "/v1/chat/completions",
            json={"messages": [{"role": "user", "content": "hi"}]},
        )
        assert response.status_code == 422

    def test_completions_with_system_message(self, client) -> None:
        """system + user 메시지 처리"""
        payload = {
            "model": "gpt-4o",
            "messages": [
                {"role": "system", "content": "You are a helpful assistant."},
                {"role": "user", "content": "What is 2+2?"},
            ],
        }
        response = client.post("/v1/chat/completions", json=payload)
        assert response.status_code == 200

    def test_completions_with_temperature(self, client) -> None:
        """temperature 파라미터 처리"""
        payload = {**self._basic_payload(), "temperature": 0.5}
        response = client.post("/v1/chat/completions", json=payload)
        assert response.status_code == 200

    def test_completions_with_max_tokens(self, client) -> None:
        """max_tokens 파라미터 처리"""
        payload = {**self._basic_payload(), "max_tokens": 100}
        response = client.post("/v1/chat/completions", json=payload)
        assert response.status_code == 200

    def test_completions_finish_reason_present(self, client) -> None:
        """choices[0].finish_reason 존재"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        body = response.json()
        choice = body["choices"][0]
        assert "finish_reason" in choice

    def test_completions_choice_index(self, client) -> None:
        """choices[0].index == 0"""
        response = client.post("/v1/chat/completions", json=self._basic_payload())
        body = response.json()
        assert body["choices"][0]["index"] == 0


class TestStreamEndpoint:
    """POST /v1/chat/completions stream=True SSE 테스트"""

    def test_stream_returns_200(self, client) -> None:
        """stream=True 요청 - 200 OK"""
        payload = {
            "model": "gpt-4o",
            "messages": [{"role": "user", "content": "hi"}],
            "stream": True,
        }
        with client.stream("POST", "/v1/chat/completions", json=payload) as response:
            assert response.status_code == 200

    def test_stream_content_type_event_stream(self, client) -> None:
        """stream=True 응답 Content-Type이 text/event-stream"""
        payload = {
            "model": "gpt-4o",
            "messages": [{"role": "user", "content": "hi"}],
            "stream": True,
        }
        with client.stream("POST", "/v1/chat/completions", json=payload) as response:
            content_type = response.headers.get("content-type", "")
            assert "text/event-stream" in content_type

    def test_stream_contains_data_lines(self, client) -> None:
        """SSE 스트림에 data: 라인 포함"""
        payload = {
            "model": "gpt-4o",
            "messages": [{"role": "user", "content": "hi"}],
            "stream": True,
        }
        with client.stream("POST", "/v1/chat/completions", json=payload) as response:
            chunks = list(response.iter_text())
            text = "".join(chunks)
            assert "data:" in text

    def test_stream_ends_with_done(self, client) -> None:
        """SSE 스트림이 [DONE] 으로 종료"""
        payload = {
            "model": "gpt-4o",
            "messages": [{"role": "user", "content": "hi"}],
            "stream": True,
        }
        with client.stream("POST", "/v1/chat/completions", json=payload) as response:
            chunks = list(response.iter_text())
            text = "".join(chunks)
            assert "[DONE]" in text
