"""Tests for ToolSelectionMetric."""

from __future__ import annotations

import asyncio

import pytest

from evalkit.metrics.tool_selection import ToolSelectionMetric
from evalkit.models import MetricScore, ToolCall
from evalkit.models import TestCase as EvalTestCase


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


class TestToolSelectionMetricName:
    def test_name(self) -> None:
        m = ToolSelectionMetric()
        assert m.name == "tool_selection"


class TestToolSelectionMetricScore:
    def test_perfect_match(self, mock_judge) -> None:
        tc = _make_agent_tc(["search", "book"], ["search", "book"])
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(1.0)
        assert mock_judge.call_count == 0

    def test_partial_match(self, mock_judge) -> None:
        tc = _make_agent_tc(["search", "book"], ["search", "cancel"])
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.5)

    def test_order_matters(self, mock_judge) -> None:
        tc = _make_agent_tc(["search", "book"], ["book", "search"])
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.0)

    def test_actual_shorter(self, mock_judge) -> None:
        tc = _make_agent_tc(["search", "book"], ["search"])
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.5)

    def test_actual_longer(self, mock_judge) -> None:
        tc = _make_agent_tc(["search"], ["search", "book"])
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.5)

    def test_no_match(self, mock_judge) -> None:
        tc = _make_agent_tc(["search"], ["cancel"])
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.0)

    def test_both_empty_lists(self, mock_judge) -> None:
        tc = _make_agent_tc([], [])
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(1.0)

    def test_single_tool_match(self, mock_judge) -> None:
        tc = _make_agent_tc(["search"], ["search"])
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(1.0)

    def test_skip_when_expected_is_none(self, mock_judge) -> None:
        tc = EvalTestCase(
            question="Which tools were called?",
            actual_tool_calls=[ToolCall(name="search")],
        )
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.0)
        assert result.passed is False

    def test_skip_when_actual_is_none(self, mock_judge) -> None:
        tc = EvalTestCase(
            question="Which tools were called?",
            expected_tool_calls=[ToolCall(name="search")],
        )
        m = ToolSelectionMetric()
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.0)
        assert result.passed is False

    def test_custom_threshold(self, mock_judge) -> None:
        tc = _make_agent_tc(["search", "book"], ["search", "cancel"])
        m = ToolSelectionMetric(threshold=1.0)
        result = m.score(tc, mock_judge)
        assert result.score == pytest.approx(0.5)
        assert result.passed is False

    def test_async_score(self, mock_judge) -> None:
        tc = _make_agent_tc(["search", "book"], ["search", "book"])
        m = ToolSelectionMetric()
        result = asyncio.run(m.score_async(tc, mock_judge))
        assert isinstance(result, MetricScore)
        assert result.score == pytest.approx(1.0)
        assert mock_judge.call_count == 0
