"""Tests for the MCP server."""

from __future__ import annotations

import pytest

from openjarvis.mcp.protocol import (
    INVALID_PARAMS,
    METHOD_NOT_FOUND,
    MCPRequest,
)
from openjarvis.mcp.server import MCPServer
from openjarvis.tools.calculator import CalculatorTool
from openjarvis.tools.think import ThinkTool


@pytest.fixture
def server():
    """Create an MCP server with calculator and think tools."""
    return MCPServer([CalculatorTool(), ThinkTool()])


class TestMCPServer:
    def test_initialize(self, server):
        req = MCPRequest(method="initialize", id=1)
        resp = server.handle(req)
        assert resp.error is None
        assert resp.id == 1
        result = resp.result
        assert "protocolVersion" in result
        assert "capabilities" in result
        assert "serverInfo" in result
        assert result["serverInfo"]["name"] == "openjarvis"

    def test_initialize_capabilities(self, server):
        req = MCPRequest(method="initialize", id=1)
        resp = server.handle(req)
        caps = resp.result["capabilities"]
        assert "tools" in caps

    def test_tools_list_all(self, server):
        req = MCPRequest(method="tools/list", id=2)
        resp = server.handle(req)
        assert resp.error is None
        tools = resp.result["tools"]
        assert len(tools) == 2
        names = {t["name"] for t in tools}
        assert "calculator" in names
        assert "think" in names

    def test_tools_list_includes_spec(self, server):
        req = MCPRequest(method="tools/list", id=2)
        resp = server.handle(req)
        tools = resp.result["tools"]
        for tool in tools:
            assert "name" in tool
            assert "description" in tool
            assert "inputSchema" in tool

    def test_tools_list_input_schema_has_properties(self, server):
        req = MCPRequest(method="tools/list", id=2)
        resp = server.handle(req)
        tools = resp.result["tools"]
        for tool in tools:
            schema = tool["inputSchema"]
            assert "properties" in schema

    def test_tools_call_calculator(self, server):
        req = MCPRequest(
            method="tools/call",
            params={"name": "calculator", "arguments": {"expression": "2+2"}},
            id=3,
        )
        resp = server.handle(req)
        assert resp.error is None
        assert resp.result["isError"] is False
        content = resp.result["content"]
        assert len(content) == 1
        assert content[0]["type"] == "text"
        assert "4" in content[0]["text"]

    def test_tools_call_calculator_complex(self, server):
        req = MCPRequest(
            method="tools/call",
            params={"name": "calculator", "arguments": {"expression": "3 * (4 + 5)"}},
            id=4,
        )
        resp = server.handle(req)
        assert resp.error is None
        assert "27" in resp.result["content"][0]["text"]

    def test_tools_call_think(self, server):
        req = MCPRequest(
            method="tools/call",
            params={
                "name": "think",
                "arguments": {"thought": "Step 1: analyze the problem."},
            },
            id=5,
        )
        resp = server.handle(req)
        assert resp.error is None
        assert resp.result["isError"] is False
        assert "Step 1" in resp.result["content"][0]["text"]

    def test_tools_call_unknown_tool(self, server):
        req = MCPRequest(
            method="tools/call",
            params={"name": "nonexistent", "arguments": {}},
            id=6,
        )
        resp = server.handle(req)
        assert resp.error is not None
        assert resp.error["code"] == INVALID_PARAMS
        assert "Unknown tool" in resp.error["message"]

    def test_tools_call_missing_name(self, server):
        req = MCPRequest(
            method="tools/call",
            params={"arguments": {}},
            id=7,
        )
        resp = server.handle(req)
        assert resp.error is not None
        assert resp.error["code"] == INVALID_PARAMS
        assert "name" in resp.error["message"]

    def test_tools_call_invalid_arguments(self, server):
        req = MCPRequest(
            method="tools/call",
            params={"name": "calculator", "arguments": {}},
            id=8,
        )
        resp = server.handle(req)
        # Calculator returns failure but not a protocol error
        assert resp.error is None
        assert resp.result["isError"] is True

    def test_result_format_mcp_compliant(self, server):
        req = MCPRequest(
            method="tools/call",
            params={"name": "think", "arguments": {"thought": "hello"}},
            id=9,
        )
        resp = server.handle(req)
        result = resp.result
        assert "content" in result
        assert "isError" in result
        assert isinstance(result["content"], list)
        assert result["content"][0]["type"] == "text"

    def test_unknown_method(self, server):
        req = MCPRequest(method="resources/list", id=10)
        resp = server.handle(req)
        assert resp.error is not None
        assert resp.error["code"] == METHOD_NOT_FOUND
        assert "Unknown method" in resp.error["message"]

    def test_response_preserves_request_id(self, server):
        req = MCPRequest(method="tools/list", id=42)
        resp = server.handle(req)
        assert resp.id == 42

    def test_response_preserves_string_id(self, server):
        req = MCPRequest(method="tools/list", id="req-abc")
        resp = server.handle(req)
        assert resp.id == "req-abc"

    def test_empty_tools_server(self):
        empty_server = MCPServer([])
        req = MCPRequest(method="tools/list", id=1)
        resp = empty_server.handle(req)
        assert resp.result["tools"] == []

    def test_tools_call_with_no_arguments_key(self, server):
        req = MCPRequest(
            method="tools/call",
            params={"name": "calculator"},
            id=11,
        )
        resp = server.handle(req)
        # Should still execute (with empty arguments)
        assert resp.error is None

    def test_calculator_division_by_zero_returns_inf(self, server):
        # Rust calculator (meval) returns inf for 1/0 rather than an error
        req = MCPRequest(
            method="tools/call",
            params={"name": "calculator", "arguments": {"expression": "1/0"}},
            id=12,
        )
        resp = server.handle(req)
        assert resp.error is None
        assert resp.result["isError"] is False
        assert "inf" in resp.result["content"][0]["text"]
