#!/usr/bin/env python3
"""SBOM diff — ADR-002 build-time exclusion enforcement.

Asserts that the **deployment** SBOM is a strict subset of the **research** SBOM
and that the deployment SBOM does NOT contain components excluded for airborne
builds (R02 enforcement: `vins_mono`, `salad`, `c11_tile_manager`).

Bootstrap (AZ-263) ships the executable with a JSON-array contract so the CI
step can validate even before the build pipeline emits real SBOMs.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

EXCLUDED_FROM_DEPLOYMENT = frozenset({"vins_mono", "salad", "c11_tile_manager"})


def _component_name(item: object) -> str:
    """Extract a component name from any of the accepted SBOM item shapes."""
    if isinstance(item, str):
        return item
    if isinstance(item, dict):
        name = item.get("name")
        if isinstance(name, str) and name:
            return name
        # CycloneDX-style `purl` (e.g. `pkg:pypi/numpy@1.26.4`).
        purl = item.get("purl")
        if isinstance(purl, str) and "/" in purl:
            return purl.split("/", 1)[1].split("@", 1)[0]
    raise ValueError(f"Cannot extract component name from SBOM item: {item!r}")


def _load_components(path: Path) -> set[str]:
    if not path.exists():
        return set()
    data = json.loads(path.read_text())
    if isinstance(data, list):
        return {_component_name(c) for c in data}
    if isinstance(data, dict) and "components" in data:
        components = data["components"]
        if isinstance(components, list):
            return {_component_name(c) for c in components}
    raise ValueError(f"Unrecognised SBOM shape in {path}")


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Deployment ⊂ Research SBOM diff (ADR-002).")
    parser.add_argument("--deployment", type=Path, required=True)
    parser.add_argument("--research", type=Path, required=True)
    args = parser.parse_args(argv)

    deployment = _load_components(args.deployment)
    research = _load_components(args.research)

    extras = deployment - research
    forbidden = deployment & EXCLUDED_FROM_DEPLOYMENT

    if extras:
        print(
            f"FAIL: deployment SBOM has components not in research: {sorted(extras)}",
            file=sys.stderr,
        )
    if forbidden:
        print(
            f"FAIL: deployment SBOM contains forbidden components: {sorted(forbidden)}",
            file=sys.stderr,
        )
    if extras or forbidden:
        return 1
    print("OK: deployment ⊂ research and no R02-excluded components present.")
    return 0


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