#!/bin/bash
# backfill-exploitability.sh
# Adds exploitability scoring to existing advisories in feed.json that don't have it yet.
# Historical maintenance utility: normal advisory generation should use
# poll-nvd workflow (init/reset when rebuilding) or populate-local-feed.sh.
#
# Usage: ./scripts/backfill-exploitability.sh [--dry-run] [--feed PATH]
#   --dry-run   Show what would be updated without making changes
#   --feed PATH Use specified feed file (default: advisories/feed.json)

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# shellcheck source=./feed-utils.sh
source "$SCRIPT_DIR/feed-utils.sh"

# Configuration
init_feed_paths "$PROJECT_ROOT"
ANALYZER="$PROJECT_ROOT/utils/analyze_exploitability.py"
SIGNING_PRIVATE_KEY="${CLAWSEC_FEED_SIGNING_PRIVATE_KEY_PATH:-${CLAWSEC_SIGNING_PRIVATE_KEY_PATH:-$PROJECT_ROOT/clawsec-signing-private.pem}}"
SIGNING_PUBLIC_KEY="${CLAWSEC_FEED_SIGNING_PUBLIC_KEY_PATH:-${CLAWSEC_SIGNING_PUBLIC_KEY_PATH:-$PROJECT_ROOT/clawsec-signing-public.pem}}"
SIGNING_PASSPHRASE="${CLAWSEC_FEED_SIGNING_PRIVATE_KEY_PASSPHRASE:-${CLAWSEC_SIGNING_PRIVATE_KEY_PASSPHRASE:-}}"

sign_and_verify_feed_signature() {
  local feed_file="$1"
  local signature_file="$2"
  local tmp_dir
  local tmp_signature
  local signature_bin
  local passin_file

  tmp_dir=$(mktemp -d)
  tmp_signature="${signature_file}.tmp.$$"
  signature_bin="$tmp_dir/signature.bin"
  passin_file="$tmp_dir/passin.txt"

  if [ -n "$SIGNING_PASSPHRASE" ]; then
    printf '%s' "$SIGNING_PASSPHRASE" > "$passin_file"
    chmod 600 "$passin_file"

    if ! openssl pkeyutl -sign -rawin -inkey "$SIGNING_PRIVATE_KEY" -passin "file:$passin_file" -in "$feed_file" \
      | openssl base64 -A > "$tmp_signature"; then
      rm -rf "$tmp_dir"
      rm -f "$tmp_signature"
      echo "Error: Failed to sign $feed_file" >&2
      return 1
    fi
  elif ! openssl pkeyutl -sign -rawin -inkey "$SIGNING_PRIVATE_KEY" -in "$feed_file" \
    | openssl base64 -A > "$tmp_signature"; then
    rm -rf "$tmp_dir"
    rm -f "$tmp_signature"
    echo "Error: Failed to sign $feed_file" >&2
    return 1
  fi

  if ! openssl base64 -d -A -in "$tmp_signature" -out "$signature_bin"; then
    rm -rf "$tmp_dir"
    rm -f "$tmp_signature"
    echo "Error: Failed to decode generated signature for $feed_file" >&2
    return 1
  fi

  if ! openssl pkeyutl -verify -rawin -pubin -inkey "$SIGNING_PUBLIC_KEY" -sigfile "$signature_bin" -in "$feed_file" >/dev/null; then
    rm -rf "$tmp_dir"
    rm -f "$tmp_signature"
    echo "Error: Signature verification failed after signing $feed_file" >&2
    return 1
  fi

  mv "$tmp_signature" "$signature_file"
  rm -rf "$tmp_dir"
  echo "✓ Re-signed and verified: $signature_file"
}

# Parse args
DRY_RUN=false
REQUIRE_SIGNING=false

while [[ $# -gt 0 ]]; do
  case $1 in
    --dry-run)
      DRY_RUN=true
      shift
      ;;
    --feed)
      FEED_PATH="$2"
      shift 2
      ;;
    *)
      echo "Unknown option: $1"
      echo "Usage: $0 [--dry-run] [--feed PATH]"
      exit 1
      ;;
  esac
done

echo "=== ClawSec Exploitability Backfill ==="
echo "Feed path: $FEED_PATH"
echo "Dry run: $DRY_RUN"
echo ""

# Verify prerequisites
if [ ! -f "$FEED_PATH" ]; then
  echo "Error: Feed file not found: $FEED_PATH"
  exit 1
fi

if [ ! -f "$ANALYZER" ]; then
  echo "Error: Analyzer script not found: $ANALYZER"
  exit 1
fi

# Check Python availability
if ! command -v python3 &> /dev/null; then
  echo "Error: python3 is required but not found in PATH"
  exit 1
fi

# Verify analyzer works
if ! python3 "$ANALYZER" --help &> /dev/null; then
  echo "Error: Analyzer script failed to run. Check Python environment."
  exit 1
fi

# Determine whether detached signatures must be regenerated.
# Runtime agents that only have public keys should run in dry-run mode.
if [ "$DRY_RUN" = "false" ]; then
  if [ -f "${FEED_PATH}.sig" ]; then
    REQUIRE_SIGNING=true
  fi
fi

if [ "$REQUIRE_SIGNING" = "true" ]; then
  if ! command -v openssl &> /dev/null; then
    echo "Error: openssl is required for detached signature signing/verification"
    exit 1
  fi

  if [ ! -f "$SIGNING_PRIVATE_KEY" ]; then
    echo "Error: Signing private key not found: $SIGNING_PRIVATE_KEY"
    echo "This backfill updates signed feed artifacts. Use --dry-run in public-key-only environments."
    exit 1
  fi

  if [ ! -f "$SIGNING_PUBLIC_KEY" ]; then
    echo "Error: Signing public key not found: $SIGNING_PUBLIC_KEY"
    exit 1
  fi
fi

# Create temp directory
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT

echo "=== Analyzing Feed ==="

# Extract advisories without exploitability_score
jq '.advisories | map(select(.exploitability_score == null or .exploitability_score == ""))' \
  "$FEED_PATH" > "$TEMP_DIR/missing_exploitability.json"

MISSING_COUNT=$(jq 'length' "$TEMP_DIR/missing_exploitability.json")
TOTAL_COUNT=$(jq '.advisories | length' "$FEED_PATH")
ALREADY_DONE=$((TOTAL_COUNT - MISSING_COUNT))

echo "Total advisories: $TOTAL_COUNT"
echo "Already have exploitability: $ALREADY_DONE"
echo "Missing exploitability: $MISSING_COUNT"
echo ""

if [ "$MISSING_COUNT" -eq 0 ]; then
  echo "✓ All advisories already have exploitability scores!"
  exit 0
fi

if [ "$DRY_RUN" = "true" ]; then
  echo "=== Dry Run - Would Update These Advisories ==="
  jq -r '.[] | .id' "$TEMP_DIR/missing_exploitability.json"
  echo ""
  echo "Total advisories to update: $MISSING_COUNT"
  exit 0
fi

echo "=== Processing Advisories ==="

# Process each advisory
PROCESSED=0
FAILED=0

# Read original feed to preserve all metadata
cp "$FEED_PATH" "$TEMP_DIR/feed_working.json"

while IFS= read -r advisory; do
  CVE_ID=$(echo "$advisory" | jq -r '.id')

  echo -n "Processing $CVE_ID... "

  # Prepare input for analyzer
  ANALYZER_INPUT=$(echo "$advisory" | jq '{
    cve_id: .id,
    cvss_score: (.cvss_score // 0.0),
    type: .type,
    description: .description,
    references: (.references // [])
  }')

  # Run analyzer
  if ANALYSIS=$(echo "$ANALYZER_INPUT" | python3 "$ANALYZER" --json --check-exploits 2>/dev/null); then
    # Extract exploitability fields
    EXPL_SCORE=$(echo "$ANALYSIS" | jq -r '.exploitability_score // "unknown"')
    EXPL_RATIONALE=$(echo "$ANALYSIS" | jq -r '.exploitability_rationale // "No rationale available"')

    # Update advisory in working feed
    jq --arg id "$CVE_ID" \
       --arg score "$EXPL_SCORE" \
       --arg rationale "$EXPL_RATIONALE" \
       '(.advisories[] | select(.id == $id)) |= (. + {
         exploitability_score: $score,
         exploitability_rationale: $rationale
       })' "$TEMP_DIR/feed_working.json" > "$TEMP_DIR/feed_updated.json"

    mv "$TEMP_DIR/feed_updated.json" "$TEMP_DIR/feed_working.json"

    echo "✓ $EXPL_SCORE"
    PROCESSED=$((PROCESSED + 1))
  else
    echo "✗ Failed"
    FAILED=$((FAILED + 1))
  fi
done < <(jq -c '.[]' "$TEMP_DIR/missing_exploitability.json")

# Check if loop executed successfully
if [ ! -f "$TEMP_DIR/feed_working.json" ]; then
  echo "Error: Feed processing failed"
  exit 1
fi

echo ""
echo "=== Processing Complete ==="
echo "Processed: $PROCESSED"
echo "Failed: $FAILED"
echo ""

# Write updated feed
echo "Writing updated feed to: $FEED_PATH"
cp "$TEMP_DIR/feed_working.json" "$FEED_PATH"

# Update feed version and timestamp
CURRENT_VERSION=$(jq -r '.version' "$FEED_PATH")
UPDATED_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)

jq --arg ts "$UPDATED_TS" '.updated = $ts' "$FEED_PATH" > "$TEMP_DIR/feed_final.json"
mv "$TEMP_DIR/feed_final.json" "$FEED_PATH"

echo "✓ Updated feed version: $CURRENT_VERSION"
echo "✓ Updated timestamp: $UPDATED_TS"
echo ""

if [ "$REQUIRE_SIGNING" = "true" ]; then
  echo ""
  echo "=== Re-signing Advisory Feed ==="

  if [ -f "${FEED_PATH}.sig" ]; then
    if ! sign_and_verify_feed_signature "$FEED_PATH" "${FEED_PATH}.sig"; then
      exit 1
    fi
  fi
fi

echo ""
echo "=== Summary ==="
echo "✓ Backfill complete!"
echo "✓ $PROCESSED advisories updated with exploitability scores"

if [ "$FAILED" -gt 0 ]; then
  echo "⚠ $FAILED advisories failed analysis (kept original data)"
fi

# Verify final state
FINAL_MISSING=$(jq '.advisories | map(select(.exploitability_score == null or .exploitability_score == "")) | length' "$FEED_PATH")
echo "✓ Advisories still missing exploitability: $FINAL_MISSING"
