#!/usr/bin/env bash
# CVE-scan spike (design/16) — made runnable. Captures the THREE facts that size the CVE-scan build, on a real
# NCS/ESP build, and saves the real SPDX so it can replace the representative fixtures in
# src/services/cra/parity.node-test.ts. Cannot run in repo CI (needs the toolchains + a built project).
#
# Usage:
#   scripts/cve-scan-spike.sh --nrf-build <path/to/nrf/build> [--esp-build <path/to/esp/build>] \
#       [--disabled-symbol <sym>] [--enabled-symbol <sym>] [--nm <nm-binary>] [--out spike-out]
#
# Prereqs: west (nRF) / esp-idf-sbom (ESP), osv-scanner, jq, the toolchain nm (e.g. arm-zephyr-eabi-nm).
set -euo pipefail

NRF_BUILD=""; ESP_BUILD=""; DISABLED_SYM=""; ENABLED_SYM=""; NM="nm"; OUT="spike-out"
while [ $# -gt 0 ]; do
  case "$1" in
    --nrf-build) NRF_BUILD="$2"; shift 2;;
    --esp-build) ESP_BUILD="$2"; shift 2;;
    --disabled-symbol) DISABLED_SYM="$2"; shift 2;;
    --enabled-symbol) ENABLED_SYM="$2"; shift 2;;
    --nm) NM="$2"; shift 2;;
    --out) OUT="$2"; shift 2;;
    *) echo "Unknown arg: $1" >&2; exit 2;;
  esac
done
mkdir -p "$OUT"
SUMMARY="$OUT/spike-summary.txt"; : > "$SUMMARY"
log() { echo "$@" | tee -a "$SUMMARY"; }
have() { command -v "$1" >/dev/null 2>&1; }

log "== CVE-scan spike $(date -u +%Y-%m-%dT%H:%M:%SZ) =="
for t in osv-scanner jq; do have "$t" || log "WARN: '$t' not found — some steps will be skipped."; done

count_ids() { # $1 = spdx file ; prints "total purl cpe"
  local f="$1"
  echo "$(grep -c '^PackageName:' "$f" || true) $(grep -c 'ExternalRef:.*purl' "$f" || true) $(grep -c 'ExternalRef:.*cpe' "$f" || true)"
}

# ── Fact 1 — identifier coverage (PURL vs CPE) ───────────────────────────────────────────────────────────────
log ""; log "── Fact 1: identifier coverage (the gating fact) ──"
if [ -n "$NRF_BUILD" ] && have west; then
  ( cd "$NRF_BUILD/.." 2>/dev/null || cd "$NRF_BUILD"; west ncs-sbom -d "$NRF_BUILD" --output-spdx "$OLDPWD/$OUT/nrf.spdx" ) \
    && log "nRF: $(count_ids "$OUT/nrf.spdx")  (total purl cpe)  → saved $OUT/nrf.spdx" \
    || log "nRF: west ncs-sbom failed (check the build dir / CONFIG_BUILD_OUTPUT_META=y)."
else
  log "nRF: skipped (no --nrf-build or no west)."
fi
if [ -n "$ESP_BUILD" ] && have esp-idf-sbom; then
  esp-idf-sbom create "$ESP_BUILD/project_description.json" -o "$OUT/esp.spdx" \
    && log "ESP: $(count_ids "$OUT/esp.spdx")  (total purl cpe)  → saved $OUT/esp.spdx" \
    || log "ESP: esp-idf-sbom failed."
else
  log "ESP: skipped (no --esp-build or no esp-idf-sbom)."
fi
log "INTERPRET: high PURL → OSV works as-is; low PURL / high CPE (expected on ESP) → normalizer needs a curated"
log "           component→PURL map. Drop nrf.spdx + esp.spdx into parity.node-test.ts to replace the representative fixtures."

# ── Fact 2 — osv-scanner match quality / false-positive rate ─────────────────────────────────────────────────
log ""; log "── Fact 2: osv-scanner match quality (false-positive rate) ──"
if have osv-scanner && have jq && [ -f "$OUT/nrf.spdx" ]; then
  osv-scanner --sbom="$OUT/nrf.spdx" --format=json > "$OUT/osv-nrf.json" 2>/dev/null || true
  if [ -s "$OUT/osv-nrf.json" ]; then
    log "nRF scanned components: $(jq '[.results[].packages[]] | length' "$OUT/osv-nrf.json" 2>/dev/null || echo '?')"
    log "nRF components with hits: $(jq '[.results[].packages[] | select(.vulnerabilities|length>0)] | length' "$OUT/osv-nrf.json" 2>/dev/null || echo '?')"
    log "Top hits (spot-check 5 for real-vs-false-positive):"
    jq -r '.results[].packages[] | select(.vulnerabilities|length>0) | "  \(.package.name)@\(.package.version): \([.vulnerabilities[].id]|join(","))"' "$OUT/osv-nrf.json" 2>/dev/null | head -5 | tee -a "$SUMMARY" || true
  else
    log "osv-scanner produced no JSON (offline? no hits?)."
  fi
else
  log "Fact 2 skipped (need osv-scanner + jq + $OUT/nrf.spdx)."
fi
log "INTERPRET: a noisy raw list ⇒ the applicability engine is launch-MANDATORY (expected). Note any component"
log "           osv-scanner SKIPPED (no id) → that's the 'matched X of Y; Z had no id' coverage line."

# ── Fact 3 — does --gc-sections strip unused symbols? (validates linked-symbol exclusion) ─────────────────────
log ""; log "── Fact 3: --gc-sections symbol stripping ──"
ELF="$NRF_BUILD/zephyr/zephyr.elf"
if [ -f "$ELF" ] && have "$NM"; then
  if [ -n "$DISABLED_SYM" ]; then
    if "$NM" "$ELF" | grep -qi "$DISABLED_SYM"; then
      log "DISABLED symbol '$DISABLED_SYM' is PRESENT → gc-sections did NOT strip it ⇒ linked-symbol is NOT a reliable"
      log "         exclusion; demote it to informational-only and lean on the config-gate signal."
    else
      log "DISABLED symbol '$DISABLED_SYM' is ABSENT → stripped as hoped ⇒ the 'not-linked ⇒ likely not reachable'"
      log "         hedged exclusion is sound. Ship it (still hedged, never a confident 'not reachable')."
    fi
  else
    log "Pass --disabled-symbol <sym-from-a-disabled-feature> to test stripping."
  fi
  if [ -n "$ENABLED_SYM" ]; then
    "$NM" "$ELF" | grep -qi "$ENABLED_SYM" \
      && log "ENABLED symbol '$ENABLED_SYM' is PRESENT (sanity check OK)." \
      || log "ENABLED symbol '$ENABLED_SYM' is ABSENT — unexpected; check the symbol name / build."
  fi
else
  log "Fact 3 skipped (no $ELF or no '$NM' on PATH — point --nm at e.g. arm-zephyr-eabi-nm)."
fi

log ""; log "Done. Summary: $SUMMARY"
log "Next: paste these numbers into design/16, swap nrf.spdx/esp.spdx into parity.node-test.ts (remove the"
log "      REPRESENTATIVE banner), and verify CONFIG-gate vs linked-symbol entries before adding any to advisoryHints.ts."
