"""Local #302 STEP3 gate over a retained STEP2 receipt; never runs a collector/analyzer.

Run the unchanged step2-gate.py first, then:
  python step3-gate.py --step2 <STEP2-output>/gate-receipt.json --out <NEW-directory>
Exit 0=PASS, 1=FAIL, 2=INSUFFICIENT. All predecessor reasons remain in force.
"""
import argparse
import hashlib
import json
from pathlib import Path

HERE = Path(__file__).resolve().parent
STEP2_SHA256 = "e99958e4ca61cfac931da4ff79539bf420ccaec4556b9543a327a09b6c0f3bb9"
ANALYZER_SHA256 = "6314743db493b01ee01e550bcbed5558ee7e0d86334d019ff65d03e29cfad339"


def sha(data):
    return hashlib.sha256(data).hexdigest()


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--step2", required=True, type=Path, help="retained gate-receipt.json")
    parser.add_argument("--out", required=True, type=Path, help="NEW local directory")
    args = parser.parse_args()
    predecessor, out = args.step2.resolve(), args.out.resolve()
    receipt = {"schema": "spt-302-step3-gate-v1", "step2_receipt": str(predecessor),
               "step2_gate_sha256": STEP2_SHA256, "analyzer_sha256": ANALYZER_SHA256,
               "gate_sha256": sha(Path(__file__).read_bytes()), "hashes": {}, "reasons": [],
               "gateway_lookup_gate": {"status": "NOT_EVALUATED", "required_count": 0,
                                       "broker_net_count": None, "total_count": None,
                                       "scope": "selected_broker_run_entire_retained_log",
                                       "event": "NET_GATEWAY_LOOKUP_BEGIN",
                                       "runtime": "spt-broker-net"}}
    made_out = False

    def read_verified(path, expected=None):
        data = path.read_bytes()
        digest = sha(data)
        if expected is not None and digest != expected:
            raise ValueError("hash_mismatch:" + str(path))
        receipt["hashes"][str(path)] = digest
        return data

    def reason(kind, text):
        receipt["reasons"].append({"kind": kind, "reason": text})

    try:
        step2 = json.loads(read_verified(predecessor))
        protected = (predecessor.parent, Path(step2["capture"]).resolve(),
                     HERE / "raw-copy-resume-v1", HERE / "raw-copy-resume-v2")
        if any(out == path or path in out.parents for path in protected):
            raise ValueError("output_must_be_outside_predecessor_capture_and_frozen_analyzers")
        out.mkdir(parents=True, exist_ok=False)
        made_out = True
        if (step2["schema"] != "spt-302-step2-gate-v1"
                or step2["exit_code"] != {"PASS": 0, "FAIL": 1, "INSUFFICIENT": 2}[step2["status"]]):
            raise ValueError("invalid_step2_status")
        receipt["step2_status"] = step2["status"]
        receipt["expected"] = step2["expected"]
        receipt["reasons"].extend(step2["reasons"])
        # Status cannot turn green even if a malformed predecessor omitted reasons.
        if step2["status"] != "PASS" and not step2["reasons"]:
            reason(step2["status"], "step2_" + step2["status"].lower())
        if (step2["hashes"][str(HERE / "step2-gate.py")] != STEP2_SHA256
                or step2["analyzer"]["sha256"] != ANALYZER_SHA256):
            raise ValueError("unapproved_predecessor_code")
        for path, digest in step2["hashes"].items():
            read_verified(Path(path), digest)
        # Verify the retained normalized records, not a new parse or analyzer run.
        for relative, digest in step2["output_sha256"].items():
            read_verified(predecessor.parent / relative, digest)
        boundary_path = predecessor.parent / "analysis" / "boundaries.jsonl"
        boundaries = read_verified(boundary_path, step2["output_sha256"]["analysis/boundaries.jsonl"])
        expected = step2["expected"]
        epoch = step2.get("epoch")
        if not epoch or any(epoch[key] != expected[key] for key in ("broker_pid", "run_id")):
            reason("INSUFFICIENT", "gateway_count_missing_selected_epoch")
        else:
            operations = []
            for line in boundaries.decode("utf-8").splitlines():
                if not line.strip():
                    continue
                span = json.loads(line)
                begin = span["begin"]
                if (begin["event"] == "NET_GATEWAY_LOOKUP_BEGIN"
                        and begin["broker_pid"] == expected["broker_pid"]
                        and begin["run_id"] == expected["run_id"]):
                    # Count every BEGIN, including open, fast, startup, or non-worker
                    # operations. No duration, sample-window, execution, or TID filter:
                    # the new runtime-label predicate supplements the physical-TID gate.
                    operations.append(begin)
            broker_operations = [row for row in operations if row["runtime"] == "spt-broker-net"]
            receipt["gateway_lookup_gate"].update(
                status="FAIL" if broker_operations else "PASS",
                broker_net_count=len(broker_operations), total_count=len(operations),
                operations=operations)
            if broker_operations:
                reason("FAIL", "broker_runtime_gateway_lookups_nonzero:" + str(len(broker_operations)))
        for path, digest in receipt["hashes"].items():
            if sha(Path(path).read_bytes()) != digest:
                raise ValueError("input_changed_during_gate:" + path)
    except FileNotFoundError as error:
        reason("INSUFFICIENT", "missing_input:" + str(error.filename))
    except FileExistsError:
        reason("FAIL", "output_already_exists_refusing_overwrite")
    except (OSError, ValueError, TypeError, KeyError, AttributeError) as error:
        reason("FAIL", "invalid_evidence:" + str(error))
    kinds = {row["kind"] for row in receipt["reasons"]}
    receipt["status"] = "FAIL" if "FAIL" in kinds else "INSUFFICIENT" if "INSUFFICIENT" in kinds else "PASS"
    receipt["exit_code"] = {"PASS": 0, "FAIL": 1, "INSUFFICIENT": 2}[receipt["status"]]
    rendered = json.dumps(receipt, indent=2, sort_keys=True) + "\n"
    if made_out:
        with (out / "gate-receipt.json").open("x", encoding="utf-8", newline="\n") as stream:
            stream.write(rendered)
    print(rendered, end="")
    return receipt["exit_code"]


if __name__ == "__main__":
    raise SystemExit(main())
