"""Rule steps 4-5, the voids V1-V3 and the printed-only tables. V4 is fixtures.py.
Usage: python -B analyse.py SCAN_JSONL BODIES_DIR > results.json"""
import collections, json, os, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import rule

SCHEMA = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json"
WALLS = {401, 403, 429, 503}


def holds(r, key):
    return r["reachable"] and r["paths"][key]["ok"]


def two_files(r):
    return holds(r, "mcp_card") or holds(r, "api_catalog")


def primary(r):
    return two_files(r) or (holds(r, "ai_catalog") and r["paths"]["ai_catalog"].get("has_mcp", False))


def share(rows, test):
    n = len(rows)
    k = sum(1 for r in rows if test(r))
    lo, hi = rule.wilson(k, n)
    return {"k": k, "n": n, "rate": k / n if n else None, "lo": lo, "hi": hi}


def main(path, bodies):
    rows = [json.loads(l) for l in open(path)]
    a = [r for r in rows if r["a"]]
    b = [r for r in rows if r["b"]]
    ra = [r for r in a if r["reachable"]]
    rb = [r for r in b if r["reachable"]]
    p = share(ra, primary)
    walls = sum(1 for r in ra if r["control"] in WALLS) / len(ra)
    llms_b = share(rb, lambda r: holds(r, "llms"))
    voids = {"V1": len(ra) / len(a) < 0.60, "V2": walls > 0.40, "V3": llms_b["rate"] < 0.02}
    out = {
        "frame": {"sample_a": len(a), "reachable_a": len(ra), "sample_b": len(b), "reachable_b": len(rb),
                  "control_wall_share_a": walls},
        "primary": p, "bar": rule.TENFOLD, "voids": voids,
        "verdict": "void" if any(voids.values()) else rule.verdict(p["k"], p["n"]),
        "printed_only": {
            "two_files_alone_a": share(ra, two_files),
            "per_path_a": {k: share(ra, lambda r, k=k: holds(r, k)) for k in rule.PATHS},
            "per_path_b": {k: share(rb, lambda r, k=k: holds(r, k)) for k in rule.PATHS},
            "primary_b": share(rb, primary),
            "ranks_1_10000_a": share([r for r in ra if r["rank"] <= 10000], primary),
            "ranks_rest_a": share([r for r in ra if r["rank"] > 10000], primary),
            "status_a": {k: collections.Counter(str(r["paths"][k]["status"]) for r in ra).most_common(6) for k in rule.PATHS},
            "control_a": collections.Counter(str(r["control"]) for r in ra).most_common(8),
        },
    }
    pos = []
    for r in rows:
        if not r["reachable"]:
            continue
        for k, q in r["paths"].items():
            if q["ok"] and k != "llms":
                body = open(os.path.join(bodies, q["sha"] + ".body"), "rb").read()
                try:
                    d = json.loads(body.decode("utf-8-sig"))
                except ValueError:
                    d = {}
                pos.append({"domain": r["domain"], "rank": r["rank"], "a": r["a"], "b": r["b"], "path": k, "sha": q["sha"],
                            "final": q["final"], "size": q["size"], "schema_exact": d.get("$schema") == SCHEMA,
                            "has_mcp": q.get("has_mcp")})
    shas = collections.Counter(x["sha"] for x in pos)
    out["printed_only"]["same_body"] = shas.most_common(8)
    out["printed_only"]["schema_exact_a"] = sum(1 for x in pos if x["a"] and x["path"] == "mcp_card" and x["schema_exact"])
    out["positives"] = pos
    json.dump(out, sys.stdout, indent=1)


if __name__ == "__main__":
    main(*sys.argv[1:3])
