"""
Session CRUD + Prompt API 测试。

使用 respx Mock httpx 请求，不依赖真实 Gateway。
"""

from __future__ import annotations

import json

import httpx
import pytest
import respx

from cmdc import CMDC, SessionNotFoundError, AuthError, RateLimitError
from cmdc.types import SessionOptions


BASE_URL = "http://localhost:4000"
API_KEY = "sk-test-key"


def make_client() -> CMDC:
    return CMDC(base_url=BASE_URL, api_key=API_KEY)


# ---------------------------------------------------------------------------
# 健康检查
# ---------------------------------------------------------------------------


class TestHealth:
    @respx.mock
    def test_health_ok(self) -> None:
        respx.get(f"{BASE_URL}/healthz").mock(
            return_value=httpx.Response(
                200,
                json={
                    "status": "ok",
                    "version": "0.1.0",
                    "sessions": {"active": 3},
                    "meter": {"tracked_keys": 2},
                    "timestamp": "2026-04-08T12:00:00Z",
                },
            )
        )

        client = make_client()
        info = client.health()

        assert info.status == "ok"
        assert info.version == "0.1.0"
        assert info.active_sessions == 3
        assert info.tracked_keys == 2


# ---------------------------------------------------------------------------
# 创建 Session
# ---------------------------------------------------------------------------


class TestCreateSession:
    @respx.mock
    def test_create_session_with_model(self) -> None:
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201,
                json={"sessionId": "sess-abc", "status": "created"},
            )
        )

        client = make_client()
        session = client.create_session(model="deepseek:deepseek-chat")

        assert session.session_id == "sess-abc"

    @respx.mock
    def test_create_session_with_options(self) -> None:
        route = respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201,
                json={"sessionId": "sess-xyz", "status": "created"},
            )
        )

        opts = SessionOptions(
            model="anthropic:claude-sonnet-4",
            system_prompt="你是助手",
            tools=["CMDC.Tool.Shell"],
            max_turns=50,
        )
        client = make_client()
        session = client.create_session(options=opts)

        assert session.session_id == "sess-xyz"
        body = json.loads(route.calls[0].request.content)
        assert body["model"] == "anthropic:claude-sonnet-4"
        assert body["systemPrompt"] == "你是助手"
        assert "CMDC.Tool.Shell" in body["tools"]
        assert body["maxTurns"] == 50

    @respx.mock
    def test_create_session_auth_error(self) -> None:
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                401,
                json={"error": "unauthorized", "message": "Missing or invalid API key"},
            )
        )

        client = make_client()
        with pytest.raises(AuthError):
            client.create_session(model="deepseek:deepseek-chat")

    @respx.mock
    def test_create_session_rate_limited(self) -> None:
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                429,
                headers={"Retry-After": "15"},
                json={
                    "error": "rate_limited",
                    "message": "Rate limit exceeded",
                    "retryAfter": 15,
                },
            )
        )

        client = make_client()
        with pytest.raises(RateLimitError) as exc_info:
            client.create_session(model="deepseek:deepseek-chat")
        assert exc_info.value.retry_after == 15

    def test_create_session_no_model_raises(self) -> None:
        client = make_client()
        with pytest.raises(ValueError, match="model"):
            client.create_session()


# ---------------------------------------------------------------------------
# 查询 / 删除 Session
# ---------------------------------------------------------------------------


class TestSessionInfo:
    @respx.mock
    def test_get_info(self) -> None:
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201, json={"sessionId": "sess-1", "status": "created"}
            )
        )
        respx.get(f"{BASE_URL}/v1/sessions/sess-1").mock(
            return_value=httpx.Response(
                200,
                json={
                    "sessionId": "sess-1",
                    "state": "idle",
                    "turns": 2,
                    "toolCalls": 5,
                    "totalTokens": 1500,
                    "uptimeMs": 12000,
                },
            )
        )

        client = make_client()
        session = client.create_session(model="deepseek:deepseek-chat")
        info = session.get_info()

        assert info.session_id == "sess-1"
        assert info.state == "idle"
        assert info.turns == 2
        assert info.total_tokens == 1500

    @respx.mock
    def test_get_info_not_found(self) -> None:
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201, json={"sessionId": "sess-dead", "status": "created"}
            )
        )
        respx.get(f"{BASE_URL}/v1/sessions/sess-dead").mock(
            return_value=httpx.Response(
                404,
                json={"error": "not_found", "message": "Session sess-dead not found"},
            )
        )

        client = make_client()
        session = client.create_session(model="deepseek:deepseek-chat")
        with pytest.raises(SessionNotFoundError):
            session.get_info()

    @respx.mock
    def test_stop_session(self) -> None:
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201, json={"sessionId": "sess-stop", "status": "created"}
            )
        )
        route = respx.delete(f"{BASE_URL}/v1/sessions/sess-stop").mock(
            return_value=httpx.Response(
                200, json={"sessionId": "sess-stop", "status": "deleted"}
            )
        )

        client = make_client()
        session = client.create_session(model="deepseek:deepseek-chat")
        session.stop()

        assert route.called


# ---------------------------------------------------------------------------
# Context Manager
# ---------------------------------------------------------------------------


class TestContextManager:
    @respx.mock
    def test_client_context_manager(self) -> None:
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201, json={"sessionId": "sess-cm", "status": "created"}
            )
        )
        respx.delete(f"{BASE_URL}/v1/sessions/sess-cm").mock(
            return_value=httpx.Response(
                200, json={"sessionId": "sess-cm", "status": "deleted"}
            )
        )

        with CMDC(base_url=BASE_URL, api_key=API_KEY) as client:
            with client.session(model="deepseek:deepseek-chat") as session:
                assert session.session_id == "sess-cm"

    @respx.mock
    def test_session_context_manager_stops_on_exit(self) -> None:
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201, json={"sessionId": "sess-auto-stop", "status": "created"}
            )
        )
        delete_route = respx.delete(f"{BASE_URL}/v1/sessions/sess-auto-stop").mock(
            return_value=httpx.Response(
                200,
                json={"sessionId": "sess-auto-stop", "status": "deleted"},
            )
        )

        client = make_client()
        with client.session(model="deepseek:deepseek-chat"):
            pass

        assert delete_route.called
