#!/usr/bin/env python3
"""E2E parity harness for the Worker /api/intel/sign endpoint (TEST ONLY).

Subcommands:
  make-manifest   Build a small SYNTHETIC unsigned intel manifest (nested
                  sources/files objects with intentionally UNSORTED keys, to
                  exercise the recursive canonicalization parity) and print it.
  sign-and-verify Read an unsigned manifest, sign it via the live Worker
                  (bin/intel-pipeline.sign_manifest_via_worker), then verify the
                  signed manifest with the PRODUCT verifier (lib/intel_snapshot)
                  using the test public keys from env. Asserts signature_ok and
                  integrity_ok. Also runs the TAMPER check.

This is the whole point of the task: prove the JS-canonicalized+signed manifest
verifies under the Python verifier across a NESTED manifest.
"""
from __future__ import annotations

import json
import os
import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = SCRIPT_DIR.parent
sys.path.insert(0, str(PROJECT_ROOT / "lib"))
sys.path.insert(0, str(SCRIPT_DIR))

import importlib.util

# Import the pipeline module (filename has a hyphen).
_spec = importlib.util.spec_from_file_location("intel_pipeline", SCRIPT_DIR / "intel-pipeline.py")
intel_pipeline = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(intel_pipeline)

import intel_snapshot
from intel_snapshot import verify_snapshot


def make_manifest() -> dict:
    """Synthetic UNSIGNED manifest. Keys are deliberately out of order and the
    nested `sources`/`files` objects have unsorted keys, so a correct signer
    MUST recursively sort to match the verifier."""
    # all-ASCII values: hex hashes, ISO timestamps, integer counts, ascii names.
    return {
        "snapshot_version": "1.0",
        "generated_at": "2026-06-10T00:00:00+00:00",
        "platform_version": "7.5.0",
        "created_by": "intel-worker-e2e",
        # nested object with unsorted keys (nvd_cves before cisa_kev; inside,
        # 'url' before 'authority') to stress recursive key sorting.
        "sources": {
            "nvd_cves": {
                "url": "https://services.nvd.nist.gov/rest/json/cves/2.0/",
                "authority": "NIST",
                "records": 12,
                "age_hours": 1.5,
                "license": "Public Domain",
            },
            "cisa_kev": {
                "records": 3,
                "authority": "CISA",
                "age_hours": 2.0,
            },
        },
        "files": {
            "vuln_db/vuln_intel.db": {
                "size_bytes": 1024,
                "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
            },
        },
        "totals": {"sources": 2, "records": 15, "files": 1, "size_bytes": 1024},
    }


def cmd_make_manifest(out_path: str) -> int:
    Path(out_path).write_text(json.dumps(make_manifest(), indent=2), encoding="utf-8")
    print(f"wrote unsigned manifest: {out_path}")
    return 0


def cmd_sign_and_verify(manifest_path: str, worker_url: str, admin_key: str) -> int:
    failures = 0

    def check(name, cond, detail=""):
        nonlocal failures
        print(f"{'PASS' if cond else 'FAIL'}  {name}{('  :: ' + detail) if detail else ''}")
        if not cond:
            failures += 1

    manifest = json.loads(Path(manifest_path).read_text("utf-8"))

    # --- Sign via the LIVE Worker ---
    try:
        signed = intel_pipeline.sign_manifest_via_worker(manifest, worker_url, admin_key)
    except RuntimeError as exc:
        print(f"FAIL  worker signing  :: {exc}")
        return 1

    check("worker returned classical sig", bool(signed.get("signatures", {}).get("classical")))
    check("worker returned pqc sig", bool(signed.get("signatures", {}).get("pqc")))
    check("signatures block has exactly {classical,pqc}",
          set(signed.get("signatures", {}).keys()) == {"classical", "pqc"},
          str(list(signed.get("signatures", {}).keys())))

    # Write the signed manifest where the verifier expects it, alongside the
    # data file it references so integrity (hash) checking is real.
    work = Path(manifest_path).parent
    data_file = work / "vuln_db" / "vuln_intel.db"
    data_file.parent.mkdir(parents=True, exist_ok=True)
    # 1024 zero bytes -> sha256 e3b0... is the EMPTY-string hash, so use empty
    # content to match the manifest's sha256 (empty file hash).
    data_file.write_bytes(b"")
    signed_path = work / intel_snapshot.SNAPSHOT_MANIFEST_NAME
    signed_path.write_text(json.dumps(signed, indent=2), encoding="utf-8")

    # --- Verify with the PRODUCT verifier (test pubkeys via env) ---
    # Generous SLA so freshness never masks a signature failure in this test.
    result = verify_snapshot(signed_path, data_dir=work, max_age_days=36500, check_hashes=True)
    rd = result.to_dict()
    print("  verify result: " + json.dumps(rd))
    check("PARITY: signature_ok == True", result.signature_ok is True, str(rd["reasons"]))
    check("integrity_ok == True", result.integrity_ok is True, str(rd["reasons"]))
    check("trusted == True (sig+integrity+fresh)", result.trusted is True, str(rd["reasons"]))

    # --- TAMPER: flip a byte in the manifest AFTER signing -> sig must fail ---
    tampered = json.loads(json.dumps(signed))
    tampered["sources"]["nvd_cves"]["records"] = 9999  # was 12
    tampered_path = work / "tampered-manifest.json"
    tampered_path.write_text(json.dumps(tampered, indent=2), encoding="utf-8")
    tres = verify_snapshot(tampered_path, data_dir=work, max_age_days=36500, check_hashes=False)
    check("TAMPER: signature_ok == False after byte flip", tres.signature_ok is False, str(tres.to_dict()["reasons"]))

    print(f"\n{'ALL PARITY/TAMPER CHECKS PASSED' if failures == 0 else str(failures) + ' CHECK(S) FAILED'}")
    return 0 if failures == 0 else 1


def main() -> int:
    if len(sys.argv) < 2:
        print("usage: _intel_worker_e2e.py {make-manifest OUT | sign-and-verify MANIFEST URL KEY}", file=sys.stderr)
        return 2
    cmd = sys.argv[1]
    if cmd == "make-manifest":
        return cmd_make_manifest(sys.argv[2])
    if cmd == "sign-and-verify":
        return cmd_sign_and_verify(sys.argv[2], sys.argv[3], sys.argv[4])
    print(f"unknown command: {cmd}", file=sys.stderr)
    return 2


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