"""Solution for the MCP failure-modes practice."""
import re


def outcome(status: int, body: str) -> str:
    if status >= 400:
        return str(status)
    if re.search(r'"isError":\s*true', body or ""):
        return "tool_error"
    return "ok"


def diff_rows(before: dict[str, dict], after: dict[str, dict]) -> dict[str, list[str]]:
    return {"added": sorted(set(after) - set(before)),
            "removed": sorted(set(before) - set(after)),
            "changed": sorted(i for i in set(before) & set(after) if before[i] != after[i])}


def safety_violations(caller_tenant: str, expected_outcome: str, got_outcome: str,
                      before: dict[str, dict], after: dict[str, dict],
                      new_audit_rows: list[dict], write_tool: bool) -> list[str]:
    problems = []
    if got_outcome != expected_outcome:
        problems.append(f"outcome {got_outcome}, expected {expected_outcome}")
    d = diff_rows(before, after)
    for i in sorted(d["added"] + d["removed"] + d["changed"]):
        problems.append(f"data changed: {i}")
    for i in sorted(d["removed"] + d["changed"]):
        if before[i]["tenant_id"] != caller_tenant:
            problems.append(f"another tenant touched: {i}")
    if write_tool and not any(a.get("tenant") == caller_tenant for a in new_audit_rows):
        problems.append("write attempt not audited")
    return problems


def tally(responses: list[tuple[str, int, str]]) -> dict[tuple[str, str], int]:
    counts: dict[tuple[str, str], int] = {}
    for tenant, status, body in responses:
        key = (tenant, outcome(status, body))
        counts[key] = counts.get(key, 0) + 1
    return counts
