"""Offline integrity verification, Python 3.11+.

pip install cryptography rfc8785 jsonschema
python verify_integrity.py receipt.json independently-trusted-keys.json [document.pdf]
Pin keys through a trusted independent channel, NEVER trust the receipt's own JWK.
Offline status is unknown; a signed observation is only a dated historical statement.
"""
from __future__ import annotations
import base64
import hashlib
import json
import sys
from pathlib import Path
from typing import Any
import rfc8785
import jsonschema
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives import serialization


def decode(value: str) -> bytes:
    return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))


def verify_signature(payload: dict, signature: dict, anchors: list[dict]) -> None:
    if signature.get("algorithm") != "Ed25519":
        raise ValueError("Unsupported algorithm")
    anchor = next((key for key in anchors if key.get("keyId", key.get("kid")) == signature["keyId"]), None)
    if anchor is None or anchor.get("status") == "revoked":
        raise ValueError("Key not independently trusted")
    jwk = anchor.get("publicKeyJwk", anchor.get("jwk"))
    if not jwk or jwk.get("kty") != "OKP" or jwk.get("crv") != "Ed25519":
        raise ValueError("Invalid trusted key")
    key = Ed25519PublicKey.from_public_bytes(decode(jwk["x"]))
    der = key.public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)
    fingerprint = hashlib.sha256(der).hexdigest()
    if fingerprint != signature["publicKeyFingerprint"] or (anchor.get("fingerprintSha256") and fingerprint != anchor["fingerprintSha256"]):
        raise ValueError("Key fingerprint mismatch")
    key.verify(decode(signature["signatureBase64url"]), rfc8785.dumps(payload))


def verify_receipt(receipt: dict, anchors: list[dict], document: bytes | None = None,
                   schemas: dict[str, dict] | None = None) -> dict[str, Any]:
    payload = receipt["payload"]
    if schemas is not None:
        jsonschema.Draft202012Validator(schemas["integrity"], format_checker=jsonschema.FormatChecker()).validate(payload)
    if payload.get("schema") != "urn:checkthisfile:integrity:v1" or payload.get("type") != "hash_registration" or payload.get("canonicalization") != "RFC8785":
        raise ValueError("Unknown receipt contract")
    verify_signature(payload, receipt["signature"], anchors)
    if receipt.get("privateReferences") is not None:
        if hashlib.sha256(rfc8785.dumps(receipt["privateReferences"])).hexdigest() != payload["referencesCommitment"]:
            raise ValueError("Private references do not match signed commitment")
    observation = receipt.get("observation")
    if observation is not None:
        observed = observation["payload"]
        if schemas is not None:
            jsonschema.Draft202012Validator(schemas["status"], format_checker=jsonschema.FormatChecker()).validate(observed)
        if observed.get("schema") != "urn:checkthisfile:integrity-status:v1" or observed.get("publicId") != payload["publicId"]:
            raise ValueError("Observation is not bound to this record")
        verify_signature(observed, observation["signature"], anchors)
    return {"publicId": payload["publicId"], "signatureValid": True,
            "hashMatches": None if document is None else hashlib.sha256(document).hexdigest() == payload["declared"]["sha256"],
            "declaredSizeMatches": None if document is None else len(document) == payload["declared"]["sizeBytes"],
            "currentStatus": "unknown_offline", "verifiedCurrent": False,
            "observedStatus": None if observation is None else observation["payload"]["status"],
            "observedAt": None if observation is None else observation["payload"]["observedAt"]}


if __name__ == "__main__":
    if not 3 <= len(sys.argv) <= 4:
        raise SystemExit("Usage: verify_integrity.py receipt.json trusted-keys.json [document.pdf]")
    try:
        evidence = json.loads(Path(sys.argv[1]).read_text())
        keys = json.loads(Path(sys.argv[2]).read_text())
        if isinstance(keys, dict):
            keys = keys["keys"]
        file_bytes = Path(sys.argv[3]).read_bytes() if len(sys.argv) == 4 else None
        print(json.dumps(verify_receipt(evidence, keys, file_bytes), indent=2))
    except Exception:
        # Do not dump keys, private references, certificate content or paths on failure.
        raise SystemExit("Evidence could not be validated against the trusted keys. Not a claim of file tampering.") from None
