"""Evidence Fabric Receipt Verifier -- standalone auditor-side CLI.

Customer-facing single-file tool. The third-party-auditability claim of
Evidence Fabric receipts rests on the customer being able to verify a
receipt WITHOUT trusting the same codebase that signed it. This script
depends ONLY on the `cryptography` library (Ed25519 verification);
no Vertical Edge AI source, no project layout assumptions, no internal
modules.

Usage:

  # Single-receipt verification (with public key file)
  python verifier_cli.py \\
      --receipt receipt.json \\
      --public-key-file public_key.json

  # With public key as hex
  python verifier_cli.py \\
      --receipt receipt.json \\
      --public-key-hex 4f5c6fa86f4ded58a1b2...

  # Manifest verification (all artifact hashes + manifest signature)
  python verifier_cli.py \\
      --manifest manifest.json \\
      --public-key-file public_key.json

Exit codes:
  0 -- PASS (signature valid; manifest hashes match if --manifest)
  1 -- FAIL (signature invalid OR manifest hash mismatch)
  2 -- argument / environment error

Verification procedure (matches the gateway's signing path 1:1):

  1. Load receipt (or manifest) JSON
  2. Extract signature, strip "ed25519:" prefix, hex-decode to bytes
  3. Set signature field to "" in the payload
  4. Canonicalize: json.dumps(payload, sort_keys=True,
                              separators=(",", ":"), ensure_ascii=False).encode("utf-8")
  5. Ed25519PublicKey.from_public_bytes(public_key_bytes).verify(
       signature_bytes, canonical_payload)
  6. For --manifest: also recompute each artifact's SHA-256 and compare

License: same as the upstream verifier in the Vertical Edge AI VeilEngine
codebase. Distribute freely. Do not modify the verification logic without
re-publishing a documented diff -- this tool's value rests on its
byte-for-byte alignment with the gateway's signing path.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import sys
from pathlib import Path
from typing import Any, Dict, Tuple


# ---------------------------------------------------------------------------
# Canonical-JSON helper (must match signer)
# ---------------------------------------------------------------------------

def canonical_json(obj: Any) -> bytes:
    return json.dumps(
        obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False
    ).encode("utf-8")


def sha256_hex(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


# ---------------------------------------------------------------------------
# Public key loading
# ---------------------------------------------------------------------------

def load_public_key_bytes(
    public_key_file: str | None,
    public_key_hex: str | None,
) -> bytes:
    if public_key_hex:
        return bytes.fromhex(public_key_hex)
    if public_key_file:
        with open(public_key_file, "r", encoding="utf-8") as f:
            doc = json.load(f)
        return bytes.fromhex(doc["public_key_hex"])
    sys.stderr.write(
        "ERROR: must supply --public-key-file or --public-key-hex\n"
    )
    sys.exit(2)


def load_ed25519_verifier(public_key_bytes: bytes):
    try:
        from cryptography.hazmat.primitives.asymmetric.ed25519 import (
            Ed25519PublicKey,
        )
    except ImportError:
        sys.stderr.write(
            "ERROR: `cryptography` package required. Install: pip install cryptography\n"
        )
        sys.exit(2)
    return Ed25519PublicKey.from_public_bytes(public_key_bytes)


# ---------------------------------------------------------------------------
# Receipt verification
# ---------------------------------------------------------------------------

def verify_receipt(
    receipt_path: str, public_key_bytes: bytes, verbose: bool
) -> Tuple[bool, str]:
    with open(receipt_path, "r", encoding="utf-8") as f:
        receipt: Dict[str, Any] = json.load(f)

    sig_field = receipt.get("verification", {}).get("signature", "")
    if not sig_field or sig_field.startswith("PLACEHOLDER"):
        return False, (
            f"signature field is PLACEHOLDER or empty; "
            f"run `sign_and_verify.py --sign` first"
        )
    if not sig_field.startswith("ed25519:"):
        return False, f"unsupported signature prefix: {sig_field[:32]}"

    signature_hex = sig_field[len("ed25519:"):]
    signature_bytes = bytes.fromhex(signature_hex)

    # Reconstruct canonical payload (signature stripped)
    receipt_for_canonical = json.loads(json.dumps(receipt))
    receipt_for_canonical["verification"]["signature"] = ""
    canonical = canonical_json(receipt_for_canonical)

    verifier = load_ed25519_verifier(public_key_bytes)
    try:
        verifier.verify(signature_bytes, canonical)
    except Exception as exc:
        return False, f"signature verification failed: {exc}"

    receipt_id = receipt.get("receipt_id", "<unknown>")
    signer_key_id = receipt.get("verification", {}).get("signer_key_id", "<unknown>")

    if verbose:
        print(f"  receipt_id: {receipt_id}")
        print(f"  signer_key_id: {signer_key_id}")
        print(f"  signature_algorithm_category: ed25519")
        print(f"  result: PASS")

    return True, f"receipt {receipt_id} verified (signer {signer_key_id})"


# ---------------------------------------------------------------------------
# Manifest verification
# ---------------------------------------------------------------------------

def verify_manifest(
    manifest_path: str, public_key_bytes: bytes, verbose: bool
) -> Tuple[bool, str]:
    manifest_p = Path(manifest_path).resolve()
    with manifest_p.open("r", encoding="utf-8") as f:
        manifest: Dict[str, Any] = json.load(f)

    sig_field = manifest.get("manifest_signature", "")
    if not sig_field or sig_field.startswith("PLACEHOLDER"):
        return False, (
            f"manifest_signature field is PLACEHOLDER or empty; "
            f"run `sign_and_verify.py --sign` first"
        )
    if not sig_field.startswith("ed25519:"):
        return False, f"unsupported manifest signature prefix: {sig_field[:32]}"

    signature_hex = sig_field[len("ed25519:"):]
    signature_bytes = bytes.fromhex(signature_hex)

    # Reconstruct canonical payload (signature stripped)
    manifest_for_canonical = json.loads(json.dumps(manifest))
    manifest_for_canonical["manifest_signature"] = ""
    canonical = canonical_json(manifest_for_canonical)

    verifier = load_ed25519_verifier(public_key_bytes)
    try:
        verifier.verify(signature_bytes, canonical)
    except Exception as exc:
        return False, f"manifest signature verification failed: {exc}"

    # Verify each artifact hash
    package_dir = manifest_p.parent
    artifacts = manifest.get("artifacts", [])
    mismatches = []
    for artifact in artifacts:
        artifact_p = package_dir / artifact["path"]
        if not artifact_p.exists():
            mismatches.append(f"  MISSING: {artifact['path']}")
            continue
        expected = artifact.get("sha256", "")
        if expected.startswith("PLACEHOLDER"):
            mismatches.append(
                f"  PLACEHOLDER (run sign): {artifact['path']}"
            )
            continue
        actual = sha256_hex(artifact_p.read_bytes())
        if actual != expected:
            mismatches.append(
                f"  HASH MISMATCH: {artifact['path']} "
                f"(expected {expected[:16]}..., got {actual[:16]}...)"
            )
        elif verbose:
            print(f"  {artifact['path']}: PASS")

    if mismatches:
        return False, "manifest verification failed:\n" + "\n".join(mismatches)

    return True, f"manifest {manifest.get('manifest_id', '<unknown>')} verified"


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main() -> int:
    parser = argparse.ArgumentParser(
        description="Verify an Evidence Fabric receipt or manifest.",
    )
    parser.add_argument("--receipt", help="Path to receipt.json")
    parser.add_argument("--manifest", help="Path to manifest.json")
    parser.add_argument("--public-key-file", help="Path to public_key.json")
    parser.add_argument("--public-key-hex", help="Public key as hex string")
    parser.add_argument(
        "--verbose", "-v", action="store_true", help="Verbose output"
    )
    args = parser.parse_args()

    if not args.receipt and not args.manifest:
        sys.stderr.write(
            "ERROR: must supply --receipt or --manifest\n"
        )
        return 2

    print("== VeilEngine Receipt Verifier ==")

    public_key_bytes = load_public_key_bytes(
        args.public_key_file, args.public_key_hex
    )

    overall_pass = True

    if args.receipt:
        print(f"Loading receipt: {args.receipt}")
        ok, message = verify_receipt(args.receipt, public_key_bytes, args.verbose)
        print(f"Receipt: {'VERIFIED' if ok else 'FAILED'}")
        if not ok:
            print(f"  reason: {message}")
        overall_pass = overall_pass and ok

    if args.manifest:
        print(f"Loading manifest: {args.manifest}")
        ok, message = verify_manifest(args.manifest, public_key_bytes, args.verbose)
        print(f"Manifest: {'VERIFIED' if ok else 'FAILED'}")
        if not ok:
            print(f"  reason: {message}")
        overall_pass = overall_pass and ok

    print()
    print(f"Overall: {'PASS' if overall_pass else 'FAIL'}")
    return 0 if overall_pass else 1


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