"""
Solution: tools from several MCP servers, as the model reads them and as the harness runs them.
Lab 09's MCPTools (05_mcp.py), metrics.validate_args, and the fix the lab recommends but didn't measure:
our code decides which data each server may receive.
"""

HARNESS_ONLY = {"idempotency_key"}  # the harness fills these; the model never sees them


def tool_specs(servers: dict) -> tuple[list, dict]:
    """Turn every server's tools into the list the model reads, and remember which server owns each tool.

    `servers` maps a server's label to its tools: {"shop": [{"name", "description", "parameters"}, ...]}.
    Returns (specs, route):
      - specs: [{"type": "function", "function": {"name", "description", "parameters"}}, ...], in server order.
        The description is copied word for word: it's what the model reads. Remove HARNESS_ONLY keys from
        the parameters' "properties" (and from "required"), without changing the server's own dict.
      - route: {tool name: server label}
    """
    specs, route = [], {}
    for label, tools in servers.items():
        for t in tools:
            params = dict(t["parameters"])
            params["properties"] = {k: v for k, v in params.get("properties", {}).items() if k not in HARNESS_ONLY}
            if "required" in params:
                params["required"] = [k for k in params["required"] if k not in HARNESS_ONLY]
            specs.append({"type": "function", "function": {
                "name": t["name"], "description": t["description"], "parameters": params}})
            route[t["name"]] = label
    return specs, route


def check_args(schema: dict, args: dict) -> list:
    """Problems with a call's arguments, [] if none: "missing <key>" for a required key that's absent,
    "empty <key>" for a required string that's empty or only spaces. Required keys in schema order."""
    problems = []
    for key in schema.get("required", []):
        if key not in args:
            problems.append(f"missing {key}")
        elif isinstance(args[key], str) and not args[key].strip():
            problems.append(f"empty {key}")
    return problems


def blocked_fields(server: str, args: dict, may_receive: dict, private: set) -> list:
    """The private fields this call would send to a server that may not receive them, sorted.
    A field counts only if it's in `private`, has a non-empty value, and isn't in may_receive[server]
    (a server missing from may_receive may receive no private field)."""
    allowed = may_receive.get(server, set())
    return sorted(k for k, v in args.items() if k in private and k not in allowed and v not in ("", None))


def audit(runs: list, tool: str) -> dict:
    """Count what a third-party tool got, from recorded runs: (task_id, tools called in order, emails received).
    Returns {"called": runs that called `tool`, "first": runs whose first call was `tool`,
             "sent": runs whose call reached the server with an email, "emails": distinct emails received}."""
    return {
        "called": sum(tool in tools for _, tools, _ in runs),
        "first": sum(bool(tools) and tools[0] == tool for _, tools, _ in runs),
        "sent": sum(bool(emails) for _, _, emails in runs),
        "emails": len({e for _, _, emails in runs for e in emails}),
    }
