"""Tests for ToolParameterSimilarityMetric."""

from __future__ import annotations

import asyncio

import pytest

from evalkit.metrics.tool_parameter_similarity import ToolParameterSimilarityMetric
from evalkit.models import MetricScore, ToolCall
from evalkit.models import TestCase as EvalTestCase


def _make_agent_tc(expected: list[ToolCall], actual: list[ToolCall]) -> EvalTestCase:
    return EvalTestCase(
        question="Which tools were called?",
        expected_tool_calls=expected,
        actual_tool_calls=actual,
    )


class TestToolParameterSimilarityMetricName:
    def test_name(self) -> None:
        m = ToolParameterSimilarityMetric()
        assert m.name == "tool_parameter_similarity"


class TestToolParameterSimilaritySkip:
    def test_skip_when_expected_is_none(self, mock_judge) -> None:
        tc = EvalTestCase(
            question="Which tools were called?",
            actual_tool_calls=[ToolCall(name="search", parameters={"q": "hello"})],
        )
        m = ToolParameterSimilarityMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.0)
        assert mock_judge.call_count == 0

    def test_skip_when_actual_is_none(self, mock_judge) -> None:
        tc = EvalTestCase(
            question="Which tools were called?",
            expected_tool_calls=[ToolCall(name="search", parameters={"q": "hello"})],
        )
        m = ToolParameterSimilarityMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.0)
        assert mock_judge.call_count == 0

    def test_skip_when_no_matching_tools(self, mock_judge) -> None:
        tc = _make_agent_tc(
            expected=[ToolCall(name="search", parameters={"q": "hello"})],
            actual=[ToolCall(name="book", parameters={"flight": "NYC"})],
        )
        m = ToolParameterSimilarityMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.0)
        assert mock_judge.call_count == 0


class TestToolParameterSimilarityJudge:
    def test_calls_judge_when_tools_match(self, mock_judge) -> None:
        tc = _make_agent_tc(
            expected=[ToolCall(name="search", parameters={"q": "hello"})],
            actual=[ToolCall(name="search", parameters={"q": "world"})],
        )
        m = ToolParameterSimilarityMetric()
        result = m.score(tc, mock_judge)
        assert mock_judge.call_count == 1
        assert isinstance(result, MetricScore)

    def test_prompt_contains_tool_calls(self, mock_judge) -> None:
        tc = _make_agent_tc(
            expected=[ToolCall(name="get_weather", parameters={"city": "Paris"})],
            actual=[ToolCall(name="get_weather", parameters={"city": "London"})],
        )
        m = ToolParameterSimilarityMetric()
        m.score(tc, mock_judge)
        assert mock_judge.last_prompt is not None
        assert "get_weather" in mock_judge.last_prompt
        assert "Paris" in mock_judge.last_prompt
        assert "London" in mock_judge.last_prompt


class TestToolParameterSimilarityAsync:
    def test_async_skip(self, mock_judge) -> None:
        tc = EvalTestCase(
            question="Which tools were called?",
            actual_tool_calls=[ToolCall(name="search", parameters={"q": "hello"})],
        )
        m = ToolParameterSimilarityMetric()
        result = asyncio.run(m.score_async(tc, mock_judge))
        assert result.score == pytest.approx(0.0)
        assert mock_judge.call_count == 0

    def test_async_calls_judge(self, mock_judge) -> None:
        tc = _make_agent_tc(
            expected=[ToolCall(name="search", parameters={"q": "hello"})],
            actual=[ToolCall(name="search", parameters={"q": "world"})],
        )
        m = ToolParameterSimilarityMetric()
        result = asyncio.run(m.score_async(tc, mock_judge))
        assert mock_judge.call_count == 1
        assert isinstance(result, MetricScore)
