#!/usr/bin/env python3
"""Run six synthetic, offline checks on a read-only invoice lookup.

Run with no options for the six self-tests. Use --emit-fixture to export the
fake record, save and reload it with your adapter, then pass that JSON file to
--check-restored. The checks compare application data, never a provider
signature. No credentials, model responses or network are required.
"""

import argparse
from copy import deepcopy
import hashlib
import json
import sys


def json_digest(value):
    """Hash JSON values with object keys sorted and array order preserved."""
    encoded = json.dumps(
        value, sort_keys=True, separators=(",", ":"), ensure_ascii=False,
        allow_nan=False,
    ).encode("utf-8")
    return hashlib.sha256(encoded).digest()


def round_trip(record):
    """Reference adapter: serialize and restore one complete application record."""
    return json.loads(json.dumps(record, ensure_ascii=False))


def invoice_session():
    """Synthetic content only. The opaque sentinel has no API meaning."""
    return {
        "prefix": {
            "system": "Help the finance team reconcile invoice totals.",
            "tools": [{
                "name": "lookup_invoice",
                "description": "Read one recorded invoice total.",
                "input_schema": {
                    "type": "object",
                    "properties": {"invoice_id": {"type": "string"}},
                    "required": ["invoice_id"],
                },
            }],
            "messages": [{"role": "user", "content": "Check invoice DEMO-17."}],
        },
        "assistant": {
            "role": "assistant",
            "content": [
                {"type": "thinking", "thinking": "", "signature": "SYNTHETIC-OPAQUE"},
                {
                    "type": "tool_use", "id": "toolu_demo_17",
                    "name": "lookup_invoice", "input": {"invoice_id": "DEMO-17"},
                },
            ],
        },
        "next_message": {
            "role": "user",
            "content": [{
                "type": "tool_result", "tool_use_id": "toolu_demo_17",
                "content": "EUR 47.50",
            }],
        },
    }


def check_replay(saved, restored):
    """Return stable failure labels without logging record contents or hashes."""
    if not isinstance(restored, dict) or not isinstance(restored.get("prefix"), dict):
        return "missing saved prefix"
    for field in ("system", "tools", "messages"):
        if json_digest(saved["prefix"][field]) != json_digest(restored["prefix"].get(field)):
            return "changed prefix: " + field

    if json_digest(saved["assistant"]) != json_digest(restored.get("assistant")):
        return "changed assistant turn"

    calls = [
        block["id"] for block in restored["assistant"]["content"]
        if block["type"] == "tool_use"
    ]
    reply = restored.get("next_message")
    if not isinstance(reply, dict) or not isinstance(reply.get("content"), list):
        return "changed result turn"
    results = [
        block.get("tool_use_id") for block in reply["content"]
        if isinstance(block, dict) and block.get("type") == "tool_result"
    ]
    if reply.get("role") != "user" or any(not isinstance(item, str) for item in results):
        return "unmatched tool result"
    if len(set(calls)) != len(calls) or sorted(calls) != sorted(results):
        return "unmatched tool result"
    if json_digest(saved["next_message"]) != json_digest(reply):
        return "changed result turn"
    return "intact"


def run_self_tests():
    saved = invoice_session()
    cases = [("intact turn without visible text", deepcopy(saved), "intact")]

    system_edit = deepcopy(saved)
    system_edit["prefix"]["system"] += " Always round up."
    cases.append(("edited system instruction", system_edit, "changed prefix: system"))

    tool_edit = deepcopy(saved)
    tool_edit["prefix"]["tools"][0]["description"] += " Return tax too."
    cases.append(("changed tool definition", tool_edit, "changed prefix: tools"))

    history_edit = deepcopy(saved)
    history_edit["prefix"]["messages"][0]["content"] = "Check the invoice."
    cases.append(("rewritten earlier message", history_edit, "changed prefix: messages"))

    reordered = deepcopy(saved)
    reordered["assistant"]["content"].reverse()
    cases.append(("reordered assistant blocks", reordered, "changed assistant turn"))

    wrong_id = deepcopy(saved)
    wrong_id["next_message"]["content"][0]["tool_use_id"] = "toolu_other"
    cases.append(("mismatched result ID", wrong_id, "unmatched tool result"))

    failures = 0
    for label, candidate, expected in cases:
        restored = round_trip(candidate)
        observed = check_replay(saved, restored)
        passed = observed == expected
        failures += int(not passed)
        print(f"{'PASS' if passed else 'FAIL'}  {label}: expected {expected}, got {observed}")
    print(f"{len(cases) - failures}/{len(cases)} expected outcomes")
    return int(failures > 0)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    modes = parser.add_mutually_exclusive_group()
    modes.add_argument("--emit-fixture", action="store_true", help="print the synthetic record as JSON")
    modes.add_argument("--check-restored", metavar="PATH", help="check one restored synthetic record")
    args = parser.parse_args()
    if args.emit_fixture:
        print(json.dumps(invoice_session(), ensure_ascii=False, indent=2))
        return 0
    if args.check_restored is None:
        return run_self_tests()

    try:
        with open(args.check_restored, encoding="utf-8") as restored_file:
            restored = json.load(restored_file)
        outcome = check_replay(invoice_session(), restored)
    except OSError:
        print("Could not read the restored file.", file=sys.stderr)
        return 2
    except (ValueError, RecursionError):
        print("Invalid restored JSON.", file=sys.stderr)
        return 2
    print(outcome)
    return int(outcome != "intact")


if __name__ == "__main__":
    sys.exit(main())
