"""ToolParameterSimilarityMetric — LLM-judged semantic similarity of tool parameters."""

from __future__ import annotations

import json

from evalkit.judges.base import BaseJudge
from evalkit.metrics.base import BaseMetric
from evalkit.models import MetricScore, TestCase, ToolCall
from evalkit.prompts.tool_parameter_similarity import TEMPLATE


class ToolParameterSimilarityMetric(BaseMetric):
    """Measures semantic similarity of parameters for matched tool calls.

    Compares parameters only for tool calls that share the same name at the
    same position in the sequence.  If no position has a matching tool name,
    the metric returns 0.0 without calling the judge.

    A score of 1.0 means the actual parameters are semantically equivalent
    to the expected parameters.  A score of 0.0 means they are unrelated.
    """

    name: str = "tool_parameter_similarity"

    def __init__(self, threshold: float = 0.5) -> None:
        self.threshold = threshold

    def _matched_pairs(self, test_case: TestCase) -> list[tuple[ToolCall, ToolCall]]:
        """Return (expected, actual) pairs where both have the same tool name at the same position."""
        expected = test_case.expected_tool_calls or []
        actual = test_case.actual_tool_calls or []
        return [
            (expected[i], actual[i])
            for i in range(min(len(expected), len(actual)))
            if expected[i].name == actual[i].name
        ]

    def _has_matched_tools(self, test_case: TestCase) -> bool:
        if test_case.expected_tool_calls is None or test_case.actual_tool_calls is None:
            return False
        return len(self._matched_pairs(test_case)) > 0

    def _skip_result(self) -> MetricScore:
        return MetricScore(
            metric_name=self.name,
            score=0.0,
            reason="No matching tool names found; parameter similarity cannot be assessed",
            passed=False,
        )

    def _build_prompt(self, test_case: TestCase) -> str:
        pairs = self._matched_pairs(test_case)
        tool_lines: list[str] = []
        for idx, (exp, act) in enumerate(pairs, start=1):
            tool_lines.append(
                f'<tool name="{exp.name}">\n'
                f"  <expected_parameters>{json.dumps(exp.parameters)}</expected_parameters>\n"
                f"  <actual_parameters>{json.dumps(act.parameters)}</actual_parameters>\n"
                f"</tool>"
            )
        return TEMPLATE.format(tool_comparisons="\n".join(tool_lines))

    def score(self, test_case: TestCase, judge: BaseJudge | None) -> MetricScore:
        if not self._has_matched_tools(test_case):
            return self._skip_result()
        return super().score(test_case, judge)

    async def score_async(self, test_case: TestCase, judge: BaseJudge | None) -> MetricScore:
        if not self._has_matched_tools(test_case):
            return self._skip_result()
        return await super().score_async(test_case, judge)
