#!/usr/bin/env bash
# CrashMire crash test in a CI pipeline: re-tests one of your agents through the
# CrashMire API, waits for the verdict, and fails the job on a regression.
#
#   CRASHMIRE_KEY=cmk_… ./crashmire-ci.sh <test id> [fail_on]
#
#   <test id>   the id of the agent's first API crash test (ct_…)
#   fail_on     new (default: a flaw that was not there last time) | high | critical | any
#
# Optional: CRASHMIRE_REF (a commit, a tag: echoed in the report), CRASHMIRE_TIMEOUT
# (minutes, default 90), CRASHMIRE_URL (default https://crashmire.com),
# CRASHMIRE_PRINT_REPORT=1 to print the report link (keep it off on public CI logs).
# Needs curl and jq. Exit code: 0 pass, 1 fail, 2 could not decide.
set -euo pipefail

ID="${1:?usage: crashmire-ci.sh <test id> [new|high|critical|any]}"
FAIL_ON="${2:-new}"
BASE="${CRASHMIRE_URL:-https://crashmire.com}"
KEY="${CRASHMIRE_KEY:?set CRASHMIRE_KEY (a secret of your CI, never in the code)}"
REF="${CRASHMIRE_REF:-${GITHUB_SHA:-${CI_COMMIT_SHA:-}}}"
DEADLINE=$(( $(date +%s) + ${CRASHMIRE_TIMEOUT:-90} * 60 ))
# the same commit started twice (a re-run of the job) spends one credit, not two
IDEM=$(printf 'ci-%s-%s' "${REF:-$(date +%s)}" "$ID" | tr -c 'A-Za-z0-9_.:-' '_' | cut -c1-80)

case "$ID" in ct_*) ;; *) echo "crashmire: the test id starts with ct_" >&2; exit 2 ;; esac
case "$FAIL_ON" in new|high|critical|any) ;; *) echo "crashmire: fail_on is new, high, critical or any" >&2; exit 2 ;; esac
command -v jq >/dev/null || { echo "crashmire: jq is needed" >&2; exit 2; }

api() { # method path [json] — the key goes through a file descriptor, never on curl's command line (ps)
  curl -sS --proto '=https' --max-time 30 -X "$1" "$BASE/api/v1$2" \
    -H @<(printf 'Authorization: Bearer %s\n' "$KEY") -H "Content-Type: application/json" -H "Idempotency-Key: $IDEM" \
    ${3:+--data "$3"} -w '\n%{http_code}'
}

body=$(jq -nc --arg r "${REF:0:80}" 'if $r == "" then {} else {reference: $r} end')
out=$(api POST "/tests/$ID/retest" "$body")
code=${out##*$'\n'}; json=${out%$'\n'*}
case "$code" in
  200|202|409) ;;
  *) echo "crashmire: could not start ($code): $(jq -r '.message // .' <<<"$json")" >&2; exit 2 ;;
esac
run=$(jq -r '.id // empty' <<<"$json")
[ -n "$run" ] || { echo "crashmire: $(jq -r '.message // .' <<<"$json")" >&2; exit 2; }
echo "crashmire: crash test $run started, credits left: $(jq -r '.credits_left // "?"' <<<"$json")"

while :; do
  [ "$(date +%s)" -lt "$DEADLINE" ] || { echo "crashmire: no verdict in time (still running: $run)" >&2; exit 2; }
  sleep 30
  out=$(api GET "/tests/$run?fail_on=$FAIL_ON") || continue
  code=${out##*$'\n'}; json=${out%$'\n'*}
  [ "$code" = 200 ] || continue
  status=$(jq -r .status <<<"$json")
  case "$status" in
    done)
      jq -r '"crashmire: verdict \(.verdict) (fail_on=\(.fail_on)) — critical \(.by_severity.critical), high \(.by_severity.high), medium \(.by_severity.medium), low \(.by_severity.low); new flaws: \(.new | length); fixed: \(.fixed)"' <<<"$json"
      # the report link opens the full report: printed only on request (CI logs can be public)
      [ "${CRASHMIRE_PRINT_REPORT:-0}" = 1 ] && jq -r '"report: \(.report_url)"' <<<"$json"
      [ "$(jq -r .verdict <<<"$json")" = pass ] && exit 0 || exit 1 ;;
    failed) echo "crashmire: the crash test failed on our side; its credit was given back." >&2; exit 2 ;;
    *) echo "crashmire: $status…" ;;
  esac
done
