"""Solution for the MCP tool-use scorer practice."""

READ_ONLY = {"search_contacts"}


def _norm(v) -> str:
    return str(v if v is not None else "").strip().lower()


def args_match(expected: dict, call: dict) -> bool:
    if call["tool"] != expected["tool"]:
        return False
    for key, want in (expected.get("args") or {}).items():
        got = _norm(call["args"].get(key))
        if isinstance(want, dict):
            if _norm(want["contains"]) not in got:
                return False
        elif got != _norm(want):
            return False
    return True


def in_order(calls: list[dict], expected: list[dict]) -> bool:
    cursor = 0
    for e in expected:
        at = next((i for i in range(cursor, len(calls)) if args_match(e, calls[i])), None)
        if at is None:
            return False
        cursor = at + 1
    return True


def redundant(calls: list[dict], expected: list[dict]) -> list[str]:
    allowance: dict[str, int] = {}
    for e in expected:
        allowance[e["tool"]] = allowance.get(e["tool"], 0) + 1
    for tool in allowance:
        if tool in READ_ONLY:
            allowance[tool] += 1
    counts: dict[str, int] = {}
    for c in calls:
        counts[c["tool"]] = counts.get(c["tool"], 0) + 1
    return [f"{t} called {n}× (allowed {allowance[t]})" for t, n in counts.items()
            if t in allowance and n > allowance[t]]


def score_turn(calls: list[dict], turn: dict) -> dict:
    called = {c["tool"] for c in calls}
    missing = sorted({e["tool"] for e in turn["expected"]} - called)
    forbidden = [t for t in turn.get("forbidden", []) if t in called]
    tool_selection = not missing and not forbidden
    arguments = all(any(args_match(e, c) for c in calls) for e in turn["expected"] if e["tool"] in called)
    order = in_order(calls, turn["expected"])
    no_redundancy = not redundant(calls, turn["expected"])
    return {"tool_selection": tool_selection, "arguments": arguments, "order": order,
            "no_redundancy": no_redundancy,
            "pass": tool_selection and arguments and order and no_redundancy}


def gate(pass_rates: dict[str, float], threshold: float = 0.95) -> tuple[str, float]:
    if not pass_rates:
        return "BLOCK", 0.0
    weakest = min(pass_rates.values())
    return ("PASS" if weakest >= threshold else "BLOCK"), weakest
