"""Tests for the @tool decorator."""

from typing import Literal

from pydantic import BaseModel, Field

from koan.tools.decorator import tool
from koan.tools.types import ToolDefinition  # noqa: TCH001  # used at runtime (no __future__ annotations)


class TestToolDecorator:
    def test_sync_function(self) -> None:
        @tool
        def greet(name: str) -> str:
            """Say hello."""
            return f"Hello {name}"

        defn: ToolDefinition = greet.__tool_definition__
        assert defn.name == "greet"
        assert defn.description == "Say hello."
        assert defn.is_async is False
        assert "name" in defn.parameters["properties"]
        assert defn.parameters["properties"]["name"]["type"] == "string"

    def test_async_function(self) -> None:
        @tool
        async def fetch(url: str) -> str:
            """Fetch a URL."""
            return ""

        defn: ToolDefinition = fetch.__tool_definition__
        assert defn.is_async is True
        assert defn.name == "fetch"

    def test_custom_name_and_description(self) -> None:
        @tool(name="search", description="Search the web")
        def web_search(query: str) -> list:
            return []

        defn: ToolDefinition = web_search.__tool_definition__
        assert defn.name == "search"
        assert defn.description == "Search the web"

    def test_default_values(self) -> None:
        @tool
        def query(text: str, limit: int = 10) -> list:
            """Run a query."""
            return []

        defn: ToolDefinition = query.__tool_definition__
        props = defn.parameters["properties"]
        assert "text" in props
        assert "limit" in props
        # limit has a default, so it shouldn't be in required
        assert "limit" not in defn.parameters.get("required", [])
        assert "text" in defn.parameters["required"]

    def test_field_descriptions(self) -> None:
        @tool
        def analyze(
            text: str = Field(description="The text to analyze"),
            depth: int = Field(default=1, description="Analysis depth"),
        ) -> str:
            """Analyze text."""
            return ""

        defn: ToolDefinition = analyze.__tool_definition__
        props = defn.parameters["properties"]
        assert props["text"]["description"] == "The text to analyze"
        assert props["depth"]["description"] == "Analysis depth"

    def test_literal_type(self) -> None:
        @tool
        def classify(sentiment: Literal["positive", "negative", "neutral"]) -> str:
            """Classify sentiment."""
            return sentiment

        defn: ToolDefinition = classify.__tool_definition__
        props = defn.parameters["properties"]
        assert "enum" in props["sentiment"]
        assert set(props["sentiment"]["enum"]) == {"positive", "negative", "neutral"}

    def test_pydantic_model_param(self) -> None:
        class SearchParams(BaseModel):
            query: str
            max_results: int = 10

        @tool
        def search(params: SearchParams) -> list:
            """Search with structured params."""
            return []

        defn: ToolDefinition = search.__tool_definition__
        props = defn.parameters["properties"]
        assert "params" in props
        # Pydantic uses $ref for nested models, with $defs at the top level
        assert "$ref" in props["params"]
        assert "$defs" in defn.parameters

    def test_no_docstring_uses_fallback(self) -> None:
        @tool
        def mystery(x: int) -> int:
            return x

        defn: ToolDefinition = mystery.__tool_definition__
        assert defn.description == "Tool: mystery"

    def test_context_param_excluded(self) -> None:
        """RunContext parameters should not appear in the schema."""

        class RunContext:
            pass

        @tool
        def do_thing(query: str, ctx: RunContext) -> str:
            """Do a thing."""
            return query

        defn: ToolDefinition = do_thing.__tool_definition__
        props = defn.parameters["properties"]
        assert "query" in props
        assert "ctx" not in props

    def test_multiple_params(self) -> None:
        @tool
        def multi(a: str, b: int, c: float = 0.5, d: bool = True) -> str:
            """Multi-param tool."""
            return ""

        defn: ToolDefinition = multi.__tool_definition__
        props = defn.parameters["properties"]
        assert len(props) == 4
        required = defn.parameters["required"]
        assert "a" in required
        assert "b" in required
        assert "c" not in required
        assert "d" not in required
