#!/usr/bin/env python3
"""
pvgs_receipt_hash.py — Python companion for PVGSReceipt hash computation.

This module provides canonical JSON serialization and SHA-256 hashing for
PVGSReceipt structures generated by section7_master_receipt.lean.

USAGE:
    from pvgs_receipt_hash import receipt_to_canonical, hash_receipt

    r = generate_receipt(...)  # from Lean-generated JSON
    canonical = receipt_to_canonical(r)
    h = hash_receipt(r)

    # Or command-line:
    python pvgs_receipt_hash.py < receipt.json

The canonical form sorts keys and removes whitespace to ensure
deterministic hashing across Python versions and platforms.

RECEIPT: section-7-python-hash-companion-2026-06-21
"""

import hashlib
import json
from typing import Any


# --------------------------------------------------------------------
# Canonical JSON Serialization
# --------------------------------------------------------------------

def receipt_to_canonical(r: dict[str, Any]) -> str:
    """Convert a PVGSReceipt dictionary to a canonical JSON string.

    The canonical form:
      - Sorts all object keys alphabetically
      - Removes all whitespace (separators=(',',':'))
      - Converts rational numbers to strings (preserving exact values)
      - Flattens theoremStatus from list of pairs to a dict

    Args:
        r: A dictionary with the PVGSReceipt structure. Expected keys:
           version, stellarRank, classification, energy, sieveValue,
           rrcEvidence (dict with typeAdmissible, projectionAdmissible,
           mergeAdmissible), helstromBound, bakerBound, theoremStatus
           (list of [name, status] pairs), sha256.

    Returns:
        A deterministic JSON string suitable for cryptographic hashing.

    Example:
        >>> r = {
        ...     "version": "PVGS_DQ_Bridge:v3",
        ...     "stellarRank": 0,
        ...     "classification": "Gaussian",
        ...     "energy": 0,
        ...     "sieveValue": "1/31",
        ...     "rrcEvidence": {
        ...         "typeAdmissible": True,
        ...         "projectionAdmissible": True,
        ...         "mergeAdmissible": True
        ...     },
        ...     "helstromBound": "0.25",
        ...     "bakerBound": "1/10",
        ...     "theoremStatus": [
        ...         ["pvgs_energy_to_dq", "PROVEN"],
        ...         ["variety_isomorphism", "PARTIAL"]
        ...     ],
        ...     "sha256": "TBD"
        ... }
        >>> receipt_to_canonical(r)
        '{"baker":"1/10","classification":"Gaussian","energy":0,"helstrom":"0.25","rrc":{"merge":true,"projection":true,"type":true},"sha256":"TBD","sieveValue":"1/31","stellarRank":0,"theorems":{"pvgs_energy_to_dq":"PROVEN","variety_isomorphism":"PARTIAL"},"version":"PVGS_DQ_Bridge:v3"}'
    """
    # Extract RRC evidence sub-fields
    rrc = r.get("rrcEvidence", r.get("rrc", {}))
    theorems_raw = r.get("theoremStatus", r.get("theorems", []))

    # Convert theoremStatus list of pairs to a dict
    theorems: dict[str, str] = {}
    if isinstance(theorems_raw, dict):
        theorems = theorems_raw
    elif isinstance(theorems_raw, list):
        for entry in theorems_raw:
            if isinstance(entry, (list, tuple)) and len(entry) == 2:
                theorems[entry[0]] = entry[1]
            elif isinstance(entry, str):
                # Handle "name:status" strings
                parts = entry.split(":", 1)
                if len(parts) == 2:
                    theorems[parts[0]] = parts[1]

    # Build the canonical dictionary with sorted keys
    canonical: dict[str, Any] = {
        "baker": str(r.get("bakerBound", r.get("baker", "0"))),
        "classification": r.get("classification", ""),
        "energy": r.get("energy", 0),
        "helstrom": str(r.get("helstromBound", r.get("helstrom", "0"))),
        "rrc": {
            "merge": rrc.get("mergeAdmissible", rrc.get("merge", False)),
            "projection": rrc.get("projectionAdmissible", rrc.get("projection", False)),
            "type": rrc.get("typeAdmissible", rrc.get("type", False)),
        },
        "sha256": r.get("sha256", "TBD"),
        "sieveValue": str(r.get("sieveValue", "0")),
        "stellarRank": r.get("stellarRank", r.get("stellar_rank", 0)),
        "theorems": theorems,
        "version": r.get("version", ""),
    }

    # Serialize to compact, sorted JSON
    return json.dumps(canonical, sort_keys=True, separators=(",", ":"))


# --------------------------------------------------------------------
# SHA-256 Hash Computation
# --------------------------------------------------------------------

def hash_receipt(r: dict[str, Any]) -> str:
    """Compute the SHA-256 hash of a receipt's canonical JSON form.

    Args:
        r: A PVGSReceipt dictionary (same format as receipt_to_canonical).

    Returns:
        A 64-character hex string representing the SHA-256 digest.

    Example:
        >>> r = {"version": "PVGS_DQ_Bridge:v3", ...}
        >>> h = hash_receipt(r)
        >>> len(h)
        64
        >>> all(c in '0123456789abcdef' for c in h)
        True
    """
    canonical = receipt_to_canonical(r)
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()


def hash_string(s: str) -> str:
    """Compute SHA-256 of an arbitrary string.

    Utility function for hashing canonical forms produced externally.
    """
    return hashlib.sha256(s.encode("utf-8")).hexdigest()


# --------------------------------------------------------------------
# Receipt Builder (convenience)
# --------------------------------------------------------------------

def build_receipt(
    version: str = "PVGS_DQ_Bridge:v3",
    stellar_rank: int = 0,
    classification: str = "Gaussian",
    energy: int = 0,
    sieve_value: str = "0",
    rrc_type: bool = True,
    rrc_projection: bool = True,
    rrc_merge: bool = True,
    helstrom: str = "0",
    baker: str = "0",
    theorems: dict[str, str] | None = None,
    sha256: str = "TBD",
) -> dict[str, Any]:
    """Build a receipt dictionary from individual fields.

    Convenience function for constructing receipts without needing
    to remember the nested structure.

    Returns:
        A dictionary suitable for receipt_to_canonical and hash_receipt.
    """
    if theorems is None:
        theorems = {
            "pvgs_energy_to_dq": "PROVEN",
            "hermite_sieve_isomorphism": "CONJECTURE",
            "variety_isomorphism": "PARTIAL",
            "pvgs_always_better": "PROVEN",
            "bms_exhaustive_only_known": "COMPUTATIONAL",
        }
    return {
        "version": version,
        "stellarRank": stellar_rank,
        "classification": classification,
        "energy": energy,
        "sieveValue": sieve_value,
        "rrcEvidence": {
            "typeAdmissible": rrc_type,
            "projectionAdmissible": rrc_projection,
            "mergeAdmissible": rrc_merge,
        },
        "helstromBound": helstrom,
        "bakerBound": baker,
        "theoremStatus": [[k, v] for k, v in theorems.items()],
        "sha256": sha256,
    }


# --------------------------------------------------------------------
# Verification helpers
# --------------------------------------------------------------------

def verify_receipt_hash(r: dict[str, Any]) -> bool:
    """Verify that a receipt's sha256 matches its content.

    Returns True if the stored sha256 equals the computed hash of the
    canonical form (excluding the sha256 field itself).
    """
    stored_hash = r.get("sha256", "TBD")
    if stored_hash == "TBD":
        return False  # Hash not yet computed

    # Compute hash over canonical form with sha256 set to "TBD"
    r_copy = dict(r)
    r_copy["sha256"] = "TBD"
    computed = hash_receipt(r_copy)
    return computed == stored_hash


def receipt_equality(r1: dict[str, Any], r2: dict[str, Any]) -> bool:
    """Check if two receipts are equal by comparing their hashes."""
    return hash_receipt(r1) == hash_receipt(r2)


# --------------------------------------------------------------------
# Command-line interface
# --------------------------------------------------------------------

def main() -> None:
    """CLI: read receipt JSON from stdin, output canonical form and hash."""
    import sys

    if len(sys.argv) > 1 and sys.argv[1] in ("-h", "--help"):
        print("Usage: python pvgs_receipt_hash.py [receipt.json]")
        print("Reads receipt JSON and outputs canonical form + SHA-256.")
        sys.exit(0)

    if len(sys.argv) > 1:
        # Read from file
        with open(sys.argv[1], "r") as f:
            data = json.load(f)
    else:
        # Read from stdin
        data = json.load(sys.stdin)

    canonical = receipt_to_canonical(data)
    h = hash_receipt(data)

    print("=== Canonical JSON ===")
    print(canonical)
    print()
    print("=== SHA-256 ===")
    print(h)


# --------------------------------------------------------------------
# Self-test
# --------------------------------------------------------------------

def _self_test() -> None:
    """Run internal consistency checks."""
    print("=== PVGS Receipt Hash Self-Test ===")

    # Test 1: Basic receipt
    r1 = build_receipt(
        stellar_rank=0,
        classification="Gaussian",
        energy=0,
        sieve_value="1/31",
        rrc_type=True,
        rrc_projection=True,
        rrc_merge=True,
        helstrom="0.25",
        baker="1/10",
    )
    c1 = receipt_to_canonical(r1)
    h1 = hash_receipt(r1)
    print(f"Test 1 (Gaussian): hash={h1[:16]}...")
    assert len(h1) == 64, "Hash must be 64 hex chars"
    assert all(c in "0123456789abcdef" for c in h1), "Hash must be hex"

    # Test 2: Determinism
    h1b = hash_receipt(r1)
    assert h1 == h1b, "Hash must be deterministic"
    print("Test 2 (determinism): PASS")

    # Test 3: Different receipts → different hashes
    r2 = build_receipt(
        stellar_rank=1,
        classification="PAGS",
        energy=5,
        sieve_value="1/8191",
    )
    h2 = hash_receipt(r2)
    assert h1 != h2, "Different receipts must have different hashes"
    print(f"Test 3 (PAGS): hash={h2[:16]}...")

    # Test 4: Verify hash of hash itself
    r1_hashed = dict(r1)
    r1_hashed["sha256"] = h1
    # Verification should pass when sha256 matches
    assert verify_receipt_hash(r1_hashed), "Hash verification should pass"
    print("Test 4 (hash verification): PASS")

    # Test 5: Canonical form structure
    assert "version" in c1, "Canonical form must contain version"
    assert "stellarRank" in c1, "Canonical form must contain stellarRank"
    assert "rrc" in c1, "Canonical form must contain rrc"
    assert "theorems" in c1, "Canonical form must contain theorems"
    print("Test 5 (canonical structure): PASS")

    print("\nAll self-tests PASSED.")


if __name__ == "__main__":
    # Run self-test when executed directly
    _self_test()
