"""Server Routes 单元测试 - 使用 FastAPI TestClient"""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from fastapi.testclient import TestClient

from agentkit.core.agent_pool import AgentPool
from agentkit.core.config_driven import AgentConfig
from agentkit.core.protocol import AgentStatus
from agentkit.llm.gateway import LLMGateway
from agentkit.llm.protocol import LLMResponse, TokenUsage
from agentkit.skills.base import Skill, SkillConfig
from agentkit.skills.registry import SkillRegistry
from agentkit.tools.registry import ToolRegistry
from agentkit.server.app import create_app


@pytest.fixture
def mock_llm_gateway():
    gateway = LLMGateway()
    # Register a mock provider so gateway.chat() works
    mock_provider = AsyncMock()
    mock_provider.chat.return_value = LLMResponse(
        content='{"result": "mocked output"}',
        model="test-model",
        usage=TokenUsage(prompt_tokens=10, completion_tokens=20),
    )
    gateway.register_provider("test", mock_provider)
    return gateway


@pytest.fixture
def skill_registry():
    return SkillRegistry()


@pytest.fixture
def tool_registry():
    return ToolRegistry()


@pytest.fixture
def app(mock_llm_gateway, skill_registry, tool_registry):
    return create_app(
        llm_gateway=mock_llm_gateway,
        skill_registry=skill_registry,
        tool_registry=tool_registry,
    )


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


class TestHealthRoute:
    """GET /api/v1/health"""

    def test_health_returns_ok(self, client):
        response = client.get("/api/v1/health")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] in ("ok", "healthy", "degraded")
        assert data["version"] == "2.0.0"
        assert "checks" in data


class TestAgentRoutes:
    """Agent CRUD 路由测试"""

    def test_create_agent_201(self, client):
        response = client.post(
            "/api/v1/agents",
            json={
                "config": {
                    "name": "test_agent",
                    "agent_type": "test_type",
                    "task_mode": "llm_generate",
                    "prompt": {"identity": "Test", "instructions": "Do test"},
                }
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert data["name"] == "test_agent"
        assert data["agent_type"] == "test_type"

    def test_create_agent_from_skill_201(self, client, skill_registry):
        skill_config = SkillConfig(
            name="my_skill",
            agent_type="skill_type",
            task_mode="llm_generate",
            prompt={"identity": "Skill Agent"},
            intent={"keywords": ["skill"], "description": "A skill"},
        )
        skill = Skill(config=skill_config)
        skill_registry.register(skill)

        response = client.post(
            "/api/v1/agents",
            json={"skill_name": "my_skill"},
        )
        assert response.status_code == 201
        data = response.json()
        assert data["name"] == "my_skill"

    def test_list_agents_empty(self, client):
        response = client.get("/api/v1/agents")
        assert response.status_code == 200
        assert response.json() == []

    def test_list_agents_after_create(self, client):
        client.post(
            "/api/v1/agents",
            json={
                "config": {
                    "name": "agent1",
                    "agent_type": "type1",
                    "task_mode": "llm_generate",
                    "prompt": {"identity": "Agent 1"},
                }
            },
        )
        response = client.get("/api/v1/agents")
        assert response.status_code == 200
        data = response.json()
        assert len(data) == 1
        assert data[0]["name"] == "agent1"

    def test_get_agent_detail(self, client):
        client.post(
            "/api/v1/agents",
            json={
                "config": {
                    "name": "detail_agent",
                    "agent_type": "detail_type",
                    "task_mode": "llm_generate",
                    "prompt": {"identity": "Detail Agent"},
                }
            },
        )
        response = client.get("/api/v1/agents/detail_agent")
        assert response.status_code == 200
        data = response.json()
        assert data["name"] == "detail_agent"
        assert data["agent_type"] == "detail_type"

    def test_get_agent_not_found_404(self, client):
        response = client.get("/api/v1/agents/nonexistent")
        assert response.status_code == 404

    def test_delete_agent_204(self, client):
        client.post(
            "/api/v1/agents",
            json={
                "config": {
                    "name": "to_delete",
                    "agent_type": "del_type",
                    "task_mode": "llm_generate",
                    "prompt": {"identity": "Delete me"},
                }
            },
        )
        response = client.delete("/api/v1/agents/to_delete")
        assert response.status_code == 204

        # Verify agent is gone
        response = client.get("/api/v1/agents/to_delete")
        assert response.status_code == 404


class TestTaskRoutes:
    """Task 提交路由测试"""

    def test_submit_task_with_skill_name(self, client, skill_registry):
        # Register a skill first
        skill_config = SkillConfig(
            name="task_skill",
            agent_type="task_type",
            task_mode="llm_generate",
            prompt={"identity": "Task Skill", "instructions": "Handle tasks"},
            intent={"keywords": ["task"], "description": "Task skill"},
        )
        skill = Skill(config=skill_config)
        skill_registry.register(skill)

        response = client.post(
            "/api/v1/tasks",
            json={
                "input_data": {"query": "test query"},
                "skill_name": "task_skill",
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert "skill_name" in data or "data" in data or "output" in data

    def test_submit_task_with_agent_name(self, client):
        # Create an agent first
        client.post(
            "/api/v1/agents",
            json={
                "config": {
                    "name": "task_agent",
                    "agent_type": "task_type",
                    "task_mode": "llm_generate",
                    "prompt": {"identity": "Task Agent"},
                }
            },
        )
        response = client.post(
            "/api/v1/tasks",
            json={
                "input_data": {"query": "test query"},
                "agent_name": "task_agent",
            },
        )
        assert response.status_code == 200

    def test_submit_task_no_skill_no_agent_error(self, client):
        response = client.post(
            "/api/v1/tasks",
            json={
                "input_data": {"query": "test query"},
            },
        )
        # Should return 400 or 422 since no skill or agent specified and no skills registered
        assert response.status_code in (400, 422)

    def test_get_task_status_placeholder(self, client):
        response = client.get("/api/v1/tasks/some-task-id")
        # Placeholder implementation
        assert response.status_code in (200, 404)


class TestSkillRoutes:
    """Skill 注册路由测试"""

    def test_register_skill_201(self, client):
        response = client.post(
            "/api/v1/skills",
            json={
                "config": {
                    "name": "new_skill",
                    "agent_type": "skill_type",
                    "task_mode": "llm_generate",
                    "prompt": {"identity": "New Skill"},
                    "intent": {"keywords": ["new"], "description": "A new skill"},
                }
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert data["name"] == "new_skill"

    def test_list_skills_empty(self, client):
        response = client.get("/api/v1/skills")
        assert response.status_code == 200
        assert response.json() == []

    def test_list_skills_after_register(self, client):
        client.post(
            "/api/v1/skills",
            json={
                "config": {
                    "name": "listed_skill",
                    "agent_type": "skill_type",
                    "task_mode": "llm_generate",
                    "prompt": {"identity": "Listed Skill"},
                    "intent": {"keywords": ["listed"], "description": "A listed skill"},
                }
            },
        )
        response = client.get("/api/v1/skills")
        assert response.status_code == 200
        data = response.json()
        assert len(data) >= 1
        names = [s["name"] for s in data]
        assert "listed_skill" in names


class TestLLMRoute:
    """LLM Usage 路由测试"""

    def test_get_usage(self, client):
        response = client.get("/api/v1/llm/usage")
        assert response.status_code == 200
        data = response.json()
        assert "total_tokens" in data or "total_cost" in data

    def test_get_usage_with_agent_name(self, client):
        response = client.get("/api/v1/llm/usage?agent_name=test_agent")
        assert response.status_code == 200


class TestSSEStreamUsesAgentConfig:
    """U8: SSE stream uses agent's configuration (max_steps, model, tools, system_prompt)"""

    def test_stream_uses_agent_model(self, client, skill_registry):
        """Stream endpoint should use the agent's configured model, not hardcoded default"""
        skill_config = SkillConfig(
            name="stream_skill",
            agent_type="stream_type",
            task_mode="llm_generate",
            prompt={"identity": "Stream Agent", "instructions": "Handle streams"},
            intent={"keywords": ["stream"], "description": "Stream skill"},
            llm={"model": "gpt-4-turbo"},
        )
        skill = Skill(config=skill_config)
        skill_registry.register(skill)

        # Create agent so it's in the pool
        client.post("/api/v1/agents", json={"skill_name": "stream_skill"})

        # Verify the agent's get_model() returns the configured model
        pool = client.app.state.agent_pool
        agent = pool.get_agent("stream_skill")
        assert agent is not None
        assert agent.get_model() == "gpt-4-turbo"

    def test_stream_uses_agent_max_steps(self, client, skill_registry):
        """Stream endpoint should use agent's max_steps, not default 10"""
        skill_config = SkillConfig(
            name="maxsteps_skill",
            agent_type="maxsteps_type",
            task_mode="llm_generate",
            prompt={"identity": "MaxSteps Agent"},
            intent={"keywords": ["maxsteps"], "description": "MaxSteps skill"},
            max_steps=3,
        )
        skill = Skill(config=skill_config)
        skill_registry.register(skill)

        client.post("/api/v1/agents", json={"skill_name": "maxsteps_skill"})

        pool = client.app.state.agent_pool
        agent = pool.get_agent("maxsteps_skill")
        assert agent is not None
        react_config = agent.get_react_config()
        assert react_config["max_steps"] == 3

    def test_stream_uses_agent_tools(self, client, skill_registry):
        """Stream endpoint should use agent.get_tools(), not private _tool_registry"""
        skill_config = SkillConfig(
            name="tools_skill",
            agent_type="tools_type",
            task_mode="llm_generate",
            prompt={"identity": "Tools Agent"},
            intent={"keywords": ["tools"], "description": "Tools skill"},
        )
        skill = Skill(config=skill_config)
        skill_registry.register(skill)

        client.post("/api/v1/agents", json={"skill_name": "tools_skill"})

        pool = client.app.state.agent_pool
        agent = pool.get_agent("tools_skill")
        assert agent is not None
        # get_tools() should return a list (may be empty)
        tools = agent.get_tools()
        assert isinstance(tools, list)

    def test_stream_uses_agent_system_prompt(self, client, skill_registry):
        """Stream endpoint should use agent.get_system_prompt(), not private _system_prompt"""
        skill_config = SkillConfig(
            name="prompt_skill",
            agent_type="prompt_type",
            task_mode="llm_generate",
            prompt={"identity": "Prompt Agent", "instructions": "Do stuff"},
            intent={"keywords": ["prompt"], "description": "Prompt skill"},
        )
        skill = Skill(config=skill_config)
        skill_registry.register(skill)

        client.post("/api/v1/agents", json={"skill_name": "prompt_skill"})

        pool = client.app.state.agent_pool
        agent = pool.get_agent("prompt_skill")
        assert agent is not None
        prompt = agent.get_system_prompt()
        assert prompt is not None
        assert "Prompt Agent" in prompt


class TestSSEStreamFallback:
    """U8: SSE stream fallback when provider fails during streaming"""

    def test_stream_fallback_no_chunks_sent(self, client, skill_registry, mock_llm_gateway):
        """When provider fails before any chunks, fallback model is attempted"""
        from agentkit.core.exceptions import LLMProviderError

        skill_config = SkillConfig(
            name="fallback_skill",
            agent_type="fallback_type",
            task_mode="llm_generate",
            prompt={"identity": "Fallback Agent"},
            intent={"keywords": ["fallback"], "description": "Fallback skill"},
        )
        skill = Skill(config=skill_config)
        skill_registry.register(skill)

        client.post("/api/v1/agents", json={"skill_name": "fallback_skill"})

        pool = client.app.state.agent_pool
        agent = pool.get_agent("fallback_skill")
        assert agent is not None

        # Verify the gateway has _get_fallback_model method
        assert hasattr(mock_llm_gateway, "_get_fallback_model")

    def test_stream_error_event_on_mid_stream_failure(self, client, skill_registry):
        """When provider fails mid-stream, an error event is yielded"""
        skill_config = SkillConfig(
            name="midskill",
            agent_type="mid_type",
            task_mode="llm_generate",
            prompt={"identity": "Mid Agent"},
            intent={"keywords": ["mid"], "description": "Mid skill"},
        )
        skill = Skill(config=skill_config)
        skill_registry.register(skill)

        client.post("/api/v1/agents", json={"skill_name": "midskill"})

        pool = client.app.state.agent_pool
        agent = pool.get_agent("midskill")
        assert agent is not None
