#!/usr/bin/env python3
"""Judgement Spine - Demo Evidence Pack Generator (Public)

This is a simple, self-contained generator that:
- Loads a scenario definition from ./input_scenarios
- Generates a small evidence pack bundle in ./output
- Mints a manifest + signature (when cryptography is available)
- Optionally generates Board/Regulator/Dispute PDFs (when reportlab is available)
- Runs verification on the output bundle

Usage:
  python generate_demo_pack.py --scenario <slug>
  python generate_demo_pack.py --scenario platform_execution_ai_release_automation

"""

from __future__ import annotations

import argparse
import base64
import datetime as _dt
import hashlib
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import Dict, List


ROOT = Path(__file__).resolve().parent
INPUT_DIR = ROOT / "input_scenarios"
OUTPUT_DIR = ROOT / "output"


def sha256_file(path: Path) -> str:
    h = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            h.update(chunk)
    return h.hexdigest()


def write_json(path: Path, obj: Dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(obj, indent=2), encoding="utf-8")


def build_manifest(root: Path, exclude: List[str]) -> Dict:
    exclude_set = set(exclude)
    files = []
    for p in sorted(root.rglob("*")):
        if p.is_dir():
            continue
        rel = p.relative_to(root).as_posix()
        if rel in exclude_set:
            continue
        files.append({
            "path": rel,
            "sha256": sha256_file(p),
            "bytes": p.stat().st_size
        })
    return {
        "manifest_version": "JS-MANIFEST-1.0",
        "generated_at": _dt.datetime.now(_dt.timezone.utc).isoformat().replace("+00:00","Z"),
        "file_count": len(files),
        "files": files
    }


def sign_manifest(manifest_sha_path: Path, out_public_key: Path, out_signature_b64: Path) -> bool:
    try:
        from cryptography.hazmat.primitives.asymmetric import rsa, padding
        from cryptography.hazmat.primitives import serialization, hashes
    except Exception:
        return False

    msg = manifest_sha_path.read_bytes()

    private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    public_key = private_key.public_key()

    pub_pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
    out_public_key.write_bytes(pub_pem)

    sig = private_key.sign(msg, padding.PKCS1v15(), hashes.SHA256())
    out_signature_b64.write_text(base64.b64encode(sig).decode("ascii") + "\n", encoding="utf-8")
    return True


def copy_verifier(out_dir: Path) -> None:
    # Copy verifier from the parent bundle (one directory up)
    # If missing, write a minimal verifier.
    src = ROOT.parent.parent / "verify_manifest.py"
    if src.exists():
        shutil.copy(src, out_dir / "verify_manifest.py")
        os.chmod(out_dir / "verify_manifest.py", 0o755)
        return

    # Fallback minimal verifier (hashes only)
    (out_dir / "verify_manifest.py").write_text(
        "#!/usr/bin/env python3\nprint('Verifier not available')\n",
        encoding="utf-8"
    )
    os.chmod(out_dir / "verify_manifest.py", 0o755)


def generate_pdfs_if_available(scn: Dict, exports_dir: Path, pack_id: str) -> None:
    try:
        from reportlab.lib.pagesizes import A4
        from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
        from reportlab.lib.styles import getSampleStyleSheet
        from reportlab.lib import colors
    except Exception:
        return

    styles = getSampleStyleSheet()
    title = styles["Title"]
    body = styles["BodyText"]

    def mk_pdf(filename: str, header: str, lines: List[str]):
        path = exports_dir / filename
        doc = SimpleDocTemplate(str(path), pagesize=A4, leftMargin=54, rightMargin=54, topMargin=72, bottomMargin=54)
        story = [Paragraph(header, title), Spacer(1, 12)]
        for l in lines:
            story.append(Paragraph(l, body))
            story.append(Spacer(1, 6))
        story.append(Spacer(1, 12))
        story.append(Paragraph(f"Evidence Pack ID: {pack_id}", body))
        doc.build(story)

    mk_pdf("BOARD_VIEW.pdf", "BOARD VIEW (Generated)", [
        f"Scenario: {scn['name']}",
        f"World: {scn['world']}",
        f"Outcome: {scn['decision']['outcome']}",
        f"Intent: {scn['proposed_action']}",
        "This is a generated demo export.",
    ])
    mk_pdf("REGULATOR_VIEW.pdf", "REGULATOR VIEW (Generated)", [
        "Includes authority + bounds + integrity references.",
        "See evidence_pack.json for machine-readable fields.",
    ])
    mk_pdf("DISPUTE_PACK.pdf", "DISPUTE PACK (Generated)", [
        "Designed to be portable: inspect without access to source systems.",
    ])


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--scenario", required=True, help="Scenario slug (filename without .json) in input_scenarios/")
    args = ap.parse_args()

    scenario_path = INPUT_DIR / f"{args.scenario}.json"
    if not scenario_path.exists():
        print(f"FAIL: scenario not found: {scenario_path}")
        print("Available scenarios:")
        for p in sorted(INPUT_DIR.glob("*.json")):
            print(" -", p.stem)
        return 2

    scn = json.loads(scenario_path.read_text(encoding="utf-8"))

    ts = _dt.datetime.now(_dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    out_dir = OUTPUT_DIR / f"demo_pack_{ts}_{scn['slug']}"
    if out_dir.exists():
        shutil.rmtree(out_dir)
    out_dir.mkdir(parents=True)

    # Minimal evidence pack
    pack_id = f"JSP-DEMO-{ts}-{scn['slug'].upper()[:12]}-0001"
    evidence_dir = out_dir / "evidence"
    exports_dir = out_dir / "exports"
    evidence_dir.mkdir()
    exports_dir.mkdir()

    write_json(evidence_dir / "evidence_pack.json", {
        "schema_version": "JS-EVIDENCE-PACK-PUBLIC-1.0",
        "pack_id": pack_id,
        "generated_at": ts,
        "world": scn["world"],
        "suite": scn["suite"],
        "scenario": scn["name"],
        "intent": {"proposed_action": scn["proposed_action"]},
        "decision": scn["decision"],
        "notes": "Generated demo pack (public / illustrative)"
    })

    # Optionally generate PDFs
    generate_pdfs_if_available(scn, exports_dir, pack_id)

    # Copy verifier
    copy_verifier(out_dir)

    # Build manifest
    exclude = ["manifest.json", "manifest.sha256", "signature.sig"]
    manifest = build_manifest(out_dir, exclude)
    manifest_path = out_dir / "manifest.json"
    manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8")
    sha = sha256_file(manifest_path)
    (out_dir / "manifest.sha256").write_text(sha + "\n", encoding="utf-8")

    # Sign (if possible)
    signed = sign_manifest(out_dir / "manifest.sha256", out_dir / "public_key.pem", out_dir / "signature.sig")
    if not signed:
        print("WARN: cryptography not available; bundle will verify hashes but not signature.")

    # Run verification (best effort)
    try:
        proc = subprocess.run([sys.executable, str(out_dir / "verify_manifest.py")], cwd=str(out_dir), capture_output=True, text=True)
        print(proc.stdout)
        if proc.returncode != 0:
            print(proc.stderr)
            print("FAIL: verification did not pass")
            return 2
    except Exception as e:
        print("WARN: could not run verifier:", e)

    print("DONE:", out_dir)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
