import json, sys

CONTRACT = "1f916.inbox.since_last_visit.v3"

def decide(r):
    if r.get("contract") != CONTRACT:
        return {"decision":"UNKNOWN", "reason":"UNKNOWN_CONTRACT"}
    mode = r.get("mode")
    offers = r.get("processed_offers")
    bound = r.get("bound_offer")
    prov = r.get("provenance_kind")
    if not isinstance(offers, list) or not offers or any(not isinstance(x, dict) for x in offers):
        return {"decision":"UNKNOWN", "reason":"INCOMPLETE_PROVENANCE"}
    if prov != "server_bound":
        return {"decision":"UNKNOWN", "reason":"CALLER_ASSERTED_PROVENANCE"}
    if mode == "singleton":
        if len(offers) != 1:
            return {"decision":"UNKNOWN", "reason":"SINGLETON_CARDINALITY"}
        if bound != offers[0]:
            return {"decision":"REJECT", "reason":"BOUND_OFFER_MISMATCH"}
        return {"decision":"ACK", "reason":"EXACT_SINGLETON"}
    if mode == "batch":
        if not isinstance(bound, dict):
            return {"decision":"UNKNOWN", "reason":"MISSING_BOUND_OFFER"}
        # componentwise MIN is safe only when the server-bound processed set is complete.
        keys = ("comments", "mentions")
        if any(k not in x or not isinstance(x[k], int) for x in offers for k in keys):
            return {"decision":"UNKNOWN", "reason":"MALFORMED_OFFER"}
        expected = {k:min(x[k] for x in offers) for k in keys}
        if bound != expected:
            return {"decision":"REJECT", "reason":"BOUND_MIN_MISMATCH"}
        return {"decision":"ACK", "reason":"COMPLETE_SERVER_BOUND_BATCH"}
    return {"decision":"UNKNOWN", "reason":"UNKNOWN_MODE"}

def main():
    cases=json.load(sys.stdin)
    out=[]
    for name, req, expected in cases:
        got=decide(req)
        ok=got==expected
        out.append({"case":name,"ok":ok,"got":got,"expected":expected})
    print(json.dumps({"contract":CONTRACT,"cases":out,"all_pass":all(x["ok"] for x in out)},sort_keys=True))
    return 0 if all(x["ok"] for x in out) else 1
if __name__ == "__main__": sys.exit(main())
