"""Unit tests for coalex.ext.tool."""

from __future__ import annotations

import json

import pytest
from coalex.ext.tool import tool_span


def test_tool_span_sets_kind(memory_exporter):
    @tool_span()
    def search(query: str) -> str:
        return f"results for {query}"

    search(query="test")
    spans = memory_exporter.get_finished_spans()
    assert len(spans) == 1
    assert spans[0].attributes["openinference.span.kind"] == "TOOL"


def test_tool_span_sets_tool_name_from_function(memory_exporter):
    @tool_span()
    def my_tool() -> str:
        return "ok"

    my_tool()
    spans = memory_exporter.get_finished_spans()
    assert spans[0].attributes["tool.name"] == "my_tool"


def test_tool_span_sets_custom_name(memory_exporter):
    @tool_span(name="web_search")
    def search() -> str:
        return "ok"

    search()
    spans = memory_exporter.get_finished_spans()
    assert spans[0].name == "web_search"
    assert spans[0].attributes["tool.name"] == "web_search"


def test_tool_span_sets_description(memory_exporter):
    @tool_span(description="Searches the web for a query")
    def search(query: str) -> str:
        return "ok"

    search(query="q")
    spans = memory_exporter.get_finished_spans()
    assert spans[0].attributes["tool.description"] == "Searches the web for a query"


def test_tool_span_omits_description_when_not_provided(memory_exporter):
    @tool_span()
    def search(query: str) -> str:
        return "ok"

    search(query="q")
    spans = memory_exporter.get_finished_spans()
    assert "tool.description" not in spans[0].attributes


def test_tool_span_encodes_kwargs_as_parameters(memory_exporter):
    @tool_span()
    def search(query: str, limit: int = 5) -> str:
        return "ok"

    search(query="diabetes", limit=10)
    spans = memory_exporter.get_finished_spans()
    params = json.loads(spans[0].attributes["tool.parameters"])
    assert params == {"query": "diabetes", "limit": 10}


def test_tool_span_omits_parameters_when_no_kwargs(memory_exporter):
    @tool_span()
    def noop(x: int) -> int:
        return x

    noop(42)
    spans = memory_exporter.get_finished_spans()
    assert "tool.parameters" not in spans[0].attributes


def test_tool_span_returns_function_result(memory_exporter):
    @tool_span()
    def add(a: int, b: int) -> int:
        return a + b

    result = add(2, 3)
    assert result == 5


def test_tool_span_propagates_exception(memory_exporter):
    @tool_span()
    def boom() -> None:
        raise ValueError("tool failed")

    with pytest.raises(ValueError, match="tool failed"):
        boom()


@pytest.mark.asyncio
async def test_tool_span_async_sets_kind(memory_exporter):
    @tool_span(name="async_search")
    async def search(query: str) -> str:
        return f"results for {query}"

    await search(query="async test")
    spans = memory_exporter.get_finished_spans()
    assert len(spans) == 1
    assert spans[0].attributes["openinference.span.kind"] == "TOOL"
    assert spans[0].attributes["tool.name"] == "async_search"


@pytest.mark.asyncio
async def test_tool_span_async_encodes_kwargs(memory_exporter):
    @tool_span()
    async def fetch(url: str, timeout: int = 30) -> str:
        return "data"

    await fetch(url="http://example.com", timeout=10)
    spans = memory_exporter.get_finished_spans()
    params = json.loads(spans[0].attributes["tool.parameters"])
    assert params["url"] == "http://example.com"
    assert params["timeout"] == 10


@pytest.mark.asyncio
async def test_tool_span_async_propagates_exception(memory_exporter):
    @tool_span()
    async def fail() -> None:
        raise RuntimeError("async tool failed")

    with pytest.raises(RuntimeError, match="async tool failed"):
        await fail()
