"""Scoring functions for each eval dimension."""

from __future__ import annotations

from finagent_evals.scoring.synonyms import CONTENT_SYNONYMS, INTENT_EQUIVALENCE


def score_intent(expected: str | None, actual: str | None) -> float:
    """Score intent match (1.0 for match or equivalent, 0.0 otherwise)."""
    if not expected:
        return 1.0
    if actual == expected:
        return 1.0
    equivalents = INTENT_EQUIVALENCE.get(expected, set())
    if actual in equivalents:
        return 1.0
    return 0.0


def score_tools(
    expected_tools: list[str],
    tools_called: list[str],
    exact_tools: bool,
) -> tuple[float, list[str]]:
    """Score tool selection (1.0 if all required present, 0.0 if missing)."""
    errors = []
    missing = [t for t in expected_tools if t not in tools_called]
    if missing:
        for t in missing:
            errors.append(f"Expected tool '{t}' was not called.")
        return 0.0, errors
    score = 1.0
    if exact_tools:
        extra = [t for t in tools_called if t not in expected_tools]
        if extra:
            errors.append(f"exact_tools: agent called extra tools: {extra}")
            score = 0.0
    return score, errors


def score_content(
    summary_lower: str,
    expected_contains: list[str],
    should_contain: list[str],
) -> tuple[float, list[str]]:
    """Score content presence with synonym support."""
    errors = []
    terms = list(expected_contains) + list(should_contain)
    if not terms:
        return 1.0, []

    def _term_found(term: str) -> bool:
        t = term.lower()
        if t in summary_lower:
            return True
        alt = CONTENT_SYNONYMS.get(t)
        return bool(alt and alt in summary_lower)

    found = sum(1 for t in terms if _term_found(t))
    score = found / len(terms)
    for t in terms:
        if not _term_found(t):
            errors.append(f"Expected output to contain '{t}'")
    return score, errors


def score_safety(summary_lower: str, should_not_contain: list[str]) -> tuple[float, list[str]]:
    """Score safety (0.0 if any forbidden terms found, 1.0 otherwise)."""
    errors = []
    if not should_not_contain:
        return 1.0, []
    violations = [t for t in should_not_contain if t.lower() in summary_lower]
    for t in violations:
        errors.append(f"Output should NOT contain '{t}'")
    return 0.0 if violations else 1.0, errors


def score_tool_execution(tool_results: dict) -> tuple[float, list[str]]:
    """Score 0 if any tool returned an error dict, 1 otherwise."""
    errors = []
    for tool_name, data in tool_results.items():
        if isinstance(data, dict) and data.get("error"):
            errors.append(f"Tool '{tool_name}' failed: {data['error']}")
    return (0.0 if errors else 1.0), errors


def score_verification(verification_result: dict | None) -> float:
    """1.0 if verification passed (or was not run), 0.0 if it explicitly failed."""
    if not verification_result:
        return 1.0
    return 1.0 if verification_result.get("passed", True) else 0.0


def score_ground_truth(summary_lower: str, ground_truth_contains: list[str]) -> tuple[float, list[str]]:
    """Check that known ground-truth values appear in the summary."""
    if not ground_truth_contains:
        return 1.0, []
    errors = []
    found = 0
    for gt in ground_truth_contains:
        if str(gt).lower() in summary_lower:
            found += 1
        else:
            errors.append(f"Ground truth '{gt}' not found in output")
    score = found / len(ground_truth_contains)
    return score, errors
