"""Tests for the assertions module."""

from __future__ import annotations

import pytest

from mcp_forge._types import ContentBlock, ToolInfo, ToolResult
from mcp_forge.assertions import (
    MCPAssertionError,
    assert_content_type,
    assert_is_error,
    assert_no_error,
    assert_text_contains,
    assert_text_equals,
    assert_text_matches,
    assert_text_not_contains,
    assert_tool_count,
    assert_tool_exists,
    assert_tool_schema,
)


def _make_result(text: str = "hello", is_error: bool = False) -> ToolResult:
    return ToolResult(
        content=[ContentBlock(type="text", text=text)],
        is_error=is_error,
    )


def _make_tools() -> list[ToolInfo]:
    return [
        ToolInfo(
            name="search",
            description="Search files",
            input_schema={
                "type": "object",
                "required": ["query"],
                "properties": {
                    "query": {"type": "string"},
                    "limit": {"type": "integer"},
                },
            },
        ),
        ToolInfo(name="list", description="List items", input_schema={"type": "object"}),
    ]


class TestAssertToolExists:
    def test_existing_tool(self):
        tools = _make_tools()
        tool = assert_tool_exists(tools, "search")
        assert tool.name == "search"

    def test_missing_tool(self):
        tools = _make_tools()
        with pytest.raises(MCPAssertionError, match="not found"):
            assert_tool_exists(tools, "nonexistent")


class TestAssertToolCount:
    def test_correct_count(self):
        tools = _make_tools()
        assert_tool_count(tools, 2)

    def test_wrong_count(self):
        tools = _make_tools()
        with pytest.raises(MCPAssertionError, match="Expected 5"):
            assert_tool_count(tools, 5)


class TestAssertToolSchema:
    def test_matching_schema(self):
        tool = _make_tools()[0]
        assert_tool_schema(
            tool,
            {
                "type": "object",
                "required": ["query"],
                "properties": {
                    "query": {"type": "string"},
                },
            },
        )

    def test_missing_required_field(self):
        tool = _make_tools()[0]
        with pytest.raises(MCPAssertionError, match="missing required"):
            assert_tool_schema(
                tool,
                {
                    "type": "object",
                    "required": ["query", "nonexistent"],
                    "properties": {"query": {"type": "string"}},
                },
            )

    def test_missing_property(self):
        tool = _make_tools()[0]
        with pytest.raises(MCPAssertionError, match="missing property"):
            assert_tool_schema(
                tool,
                {
                    "type": "object",
                    "properties": {"nonexistent": {"type": "string"}},
                },
            )

    def test_wrong_type(self):
        tool = _make_tools()[0]
        with pytest.raises(MCPAssertionError, match="expected type"):
            assert_tool_schema(
                tool,
                {
                    "type": "object",
                    "properties": {"query": {"type": "integer"}},
                },
            )


class TestAssertNoError:
    def test_success(self):
        assert_no_error(_make_result("ok", is_error=False))

    def test_error(self):
        with pytest.raises(MCPAssertionError, match="returned an error"):
            assert_no_error(_make_result("bad", is_error=True))


class TestAssertIsError:
    def test_error(self):
        assert_is_error(_make_result("bad", is_error=True))

    def test_success(self):
        with pytest.raises(MCPAssertionError, match="Expected.*error"):
            assert_is_error(_make_result("ok", is_error=False))


class TestAssertTextContains:
    def test_contains(self):
        assert_text_contains(_make_result("hello world"), "world")

    def test_not_contains(self):
        with pytest.raises(MCPAssertionError):
            assert_text_contains(_make_result("hello"), "missing")


class TestAssertTextNotContains:
    def test_not_contains(self):
        assert_text_not_contains(_make_result("hello"), "missing")

    def test_contains(self):
        with pytest.raises(MCPAssertionError):
            assert_text_not_contains(_make_result("hello world"), "world")


class TestAssertTextMatches:
    def test_matches(self):
        assert_text_matches(_make_result("abc123"), r"\d+")

    def test_no_match(self):
        with pytest.raises(MCPAssertionError):
            assert_text_matches(_make_result("abc"), r"\d+")


class TestAssertTextEquals:
    def test_equal(self):
        assert_text_equals(_make_result("exact"), "exact")

    def test_not_equal(self):
        with pytest.raises(MCPAssertionError, match="Text mismatch"):
            assert_text_equals(_make_result("actual"), "expected")


class TestAssertContentType:
    def test_correct_type(self):
        assert_content_type(_make_result("hello"), "text", index=0)

    def test_wrong_type(self):
        with pytest.raises(MCPAssertionError, match="expected type"):
            assert_content_type(_make_result("hello"), "image", index=0)

    def test_out_of_range(self):
        with pytest.raises(MCPAssertionError, match="out of range"):
            assert_content_type(_make_result("hello"), "text", index=5)
