#!/usr/bin/env python3
"""Verify a QShield endurance run log (client.jsonl).

Usage: python3 verify_qshield_log.py client.jsonl

Every check is recomputed from the log itself. The script prints one line per check and a final
summary. Exit status is 0 only when every check passes. No network, no dependencies beyond Python 3.
"""
import hashlib
import json
import sys
from datetime import datetime, timezone

EXPECTED_SAMPLES = 4107
EXPECTED_ELAPSED_S = 259219
MIN_GAP_S = 60
MAX_GAP_S = 64


def parse_ts(value):
    return datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)


def main(path):
    raw = open(path, "rb").read()
    digest = hashlib.sha256(raw).hexdigest()
    results = []

    def check(name, ok, detail):
        results.append((name, bool(ok), detail))

    lines = [ln for ln in raw.decode("utf-8", "strict").split("\n") if ln.strip()]
    events = []
    parse_ok = True
    for ln in lines:
        try:
            events.append(json.loads(ln))
        except json.JSONDecodeError:
            parse_ok = False
            break
    check("01 log parses as one JSON object per line", parse_ok and len(events) == len(lines), f"{len(lines)} lines")
    if not parse_ok:
        return finish(results, digest)

    samples = [e for e in events if e.get("event") == "sample"]
    done = [e for e in events if e.get("event") == "done"]
    check("02 exactly one done event, and it is the last line", len(done) == 1 and events[-1].get("event") == "done", f"done events={len(done)}")
    d = done[0] if done else {}
    check("03 sample count matches the done event and the published total", len(samples) == d.get("samples") == EXPECTED_SAMPLES, f"samples={len(samples)} done.samples={d.get('samples')}")
    check("04 sample numbers are contiguous from 1", [s.get("sample") for s in samples] == list(range(1, len(samples) + 1)), "1.." + str(len(samples)))
    check("05 every sample result is ok with an empty reason", all(s.get("result") == "ok" and not s.get("reason") for s in samples), f"non-ok={sum(1 for s in samples if s.get('result') != 'ok')}")
    check("06 ping loss is 0 on every sample", all(str(s.get("ping_loss_pct")) == "0" for s in samples), "ping_loss_pct")
    check("07 every data-integrity check moved 65536 bytes with a hash", all(s.get("tcp_bytes") == 65536 and len(str(s.get("tcp_sha256", ""))) == 64 for s in samples), "tcp_bytes=65536")
    elapsed = [int(s.get("elapsed_s")) for s in samples]
    check("08 elapsed seconds start at 0 and strictly increase", elapsed[:1] == [0] and all(b > a for a, b in zip(elapsed, elapsed[1:])), f"first={elapsed[:1]} last={elapsed[-1:]}")
    gaps = [b - a for a, b in zip(elapsed, elapsed[1:])]
    check(f"09 every interval between checks is {MIN_GAP_S}-{MAX_GAP_S} s", bool(gaps) and min(gaps) >= MIN_GAP_S and max(gaps) <= MAX_GAP_S, f"min={min(gaps) if gaps else None} max={max(gaps) if gaps else None}")
    check("10 done elapsed matches the published duration and follows the last sample", d.get("elapsed_s") == EXPECTED_ELAPSED_S and d.get("result") == "ok" and 0 < d.get("elapsed_s", 0) - (elapsed[-1] if elapsed else 0) <= MAX_GAP_S, f"done.elapsed_s={d.get('elapsed_s')}")
    try:
        wall = (parse_ts(d["ts"]) - parse_ts(samples[0]["ts"])).total_seconds()
        wall_ok = abs(wall - d["elapsed_s"]) <= 5
        wall_detail = f"wall={int(wall)} s monotonic={d['elapsed_s']} s"
    except Exception as exc:  # noqa: BLE001
        wall_ok, wall_detail = False, f"error {exc}"
    check("11 wall-clock span agrees with the monotonic duration (within 5 s)", wall_ok, wall_detail)
    return finish(results, digest)


def finish(results, digest):
    passed = sum(1 for _, ok, _ in results if ok)
    for name, ok, detail in results:
        print(f"{'PASS' if ok else 'FAIL'}  {name}  ({detail})")
    print(f"log sha256 {digest}")
    print(f"{passed} of {len(results)} checks passed")
    return 0 if passed == len(results) else 1


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(__doc__)
        sys.exit(2)
    sys.exit(main(sys.argv[1]))
