#!/usr/bin/env bash
# scripts/validate.sh -- D-011 validation orchestrator (modular check runner).
#
# Runs a PROFILE (named set) or an explicit list of check scripts from
# scripts/checks/, applies the standard exit contract (see lib-validate.sh),
# and prints ONE structured report + a single overall verdict. Each check is
# ALSO runnable standalone; this composes them for a full acceptance run.
#
# Usage:
#   validate.sh [--profile NAME | --checks id,id,...] [options]
# Options:
#   --profile NAME        run a named profile (default: full-d011)
#   --checks a,b,c        run an explicit comma-list of check ids (overrides profile)
#   --include-disruptive  permit checks' destructive paths (amphora failover, etc.)
#   --stop-on-fail        halt after the first FAIL/HOLD (default: continue, report all)
#   --list                list profiles + discovered checks, then exit 0
#   -h|--help             this help
#
# Profiles (data, not code -- extend freely):
#   full-d011     the complete amended D-011 bar (1-6; item 4 needs --include-disruptive)
#   post-restart  quick confidence after a restart (charms, VIP jumphost, VIP tenant)
#   lb-health     octavia LB pattern only (non-disruptive unless --include-disruptive)
#   isolation     tenant e2e incl. cross-tenant isolation (item 5)
#
# Exit (overall verdict, worst-wins with MANUAL as a distinct tier):
#   0 PASS               every check PASS (or SKIPPED); nothing outstanding
#   1 FAIL               at least one check FAIL
#   2 HOLD               no FAIL, but at least one HOLD (undetermined)
#   3 PASS_PENDING_MANUAL no FAIL/HOLD, but a manual item outstanding
# ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/lib-validate.sh
. "$HERE/lib-validate.sh"
CHECKDIR="${VR_CHECKDIR:-$HERE/checks}"

PROFILE="full-d011"; EXPLICIT=""; STOP=0; LIST=0
export VR_DISRUPTIVE="${VR_DISRUPTIVE:-0}"
while [ $# -gt 0 ]; do
  case "$1" in
    --profile) PROFILE="${2:-}"; shift 2 ;;
    --checks)  EXPLICIT="${2:-}"; shift 2 ;;
    --include-disruptive) VR_DISRUPTIVE=1; shift ;;
    --stop-on-fail) STOP=1; shift ;;
    --list) LIST=1; shift ;;
    -h|--help) sed -n '2,40p' "$HERE/$(basename "${BASH_SOURCE[0]}")" | sed 's/^# \{0,1\}//'; exit 0 ;;
    *) echo "unknown arg: $1" >&2; exit 2 ;;
  esac
done

# --- profiles: name -> ordered check ids -------------------------------------
profile_checks() {
  case "$1" in
    full-d011)    echo "d011-01-charms d011-02-vip-jumphost d011-03-vip-tenant d011-04-octavia-lb d011-05-magnum-e2e d011-06-vault-unseal" ;;
    post-restart) echo "d011-01-charms d011-02-vip-jumphost d011-03-vip-tenant" ;;
    lb-health)    echo "d011-04-octavia-lb" ;;
    isolation)    echo "d011-05-magnum-e2e" ;;
    *) return 1 ;;
  esac
}

if [ "$LIST" = 1 ]; then
  echo "profiles:"; for p in full-d011 post-restart lb-health isolation; do printf '  %-14s %s\n' "$p" "$(profile_checks "$p")"; done
  echo "discovered checks in $CHECKDIR:"
  shopt -s nullglob; for c in "$CHECKDIR"/*.sh; do echo "  $(basename "$c" .sh)"; done
  exit 0
fi

# resolve the ordered list
if [ -n "$EXPLICIT" ]; then
  IFS=',' read -r -a CHECKS <<< "$EXPLICIT"
else
  read -r -a CHECKS <<< "$(profile_checks "$PROFILE")" || { echo "unknown profile: $PROFILE (try --list)" >&2; exit 2; }
  [ "${#CHECKS[@]}" -gt 0 ] || { echo "unknown profile: $PROFILE (try --list)" >&2; exit 2; }
fi

# --- run loop ----------------------------------------------------------------
declare -i n_pass=0 n_fail=0 n_hold=0 n_manual=0 n_skip=0
REPORT=()
worst=$VR_PASS
note_worst() { # keep the worst by a custom precedence: FAIL > HOLD > MANUAL > PASS/SKIP
  local c="$1"
  case "$c" in
    "$VR_FAIL") worst=$VR_FAIL ;;
    "$VR_HOLD") [ "$worst" = "$VR_FAIL" ] || worst=$VR_HOLD ;;
    "$VR_MANUAL") { [ "$worst" = "$VR_FAIL" ] || [ "$worst" = "$VR_HOLD" ]; } || worst=$VR_MANUAL ;;
  esac
}

for id in "${CHECKS[@]}"; do
  script="$CHECKDIR/$id.sh"
  if [ ! -f "$script" ]; then
    line="RESULT $id HOLD 2 - check script not found: $script"; rc=$VR_HOLD
  else
    out="$(VR_DISRUPTIVE="$VR_DISRUPTIVE" bash "$script" 2>&1)"; rc=$?
    # the check's own RESULT line is authoritative for the message; the EXIT CODE
    # is authoritative for the verdict. Prefer the emitted line but trust rc.
    line="$(printf '%s\n' "$out" | grep -E '^RESULT ' | tail -1)"
    [ -n "$line" ] || line="RESULT $id ${VR_WORD[$rc]:-UNKNOWN} $rc - (no RESULT line emitted)"
    # print the check's human output above its result line
    printf '%s\n' "$out" | grep -vE '^RESULT ' | sed 's/^/  /'
  fi
  case "$rc" in
    "$VR_PASS")   n_pass+=1 ;;
    "$VR_FAIL")   n_fail+=1 ;;
    "$VR_HOLD")   n_hold+=1 ;;
    "$VR_MANUAL") n_manual+=1 ;;
    "$VR_SKIP")   n_skip+=1 ;;
    *) # non-contract exit (127 tool-missing, 141 SIGPIPE, 139 crash, ...):
       # annotate the line BEFORE appending and force the verdict to HOLD --
       # pre-fix (DOCFIX-114) the rewrite was dead code after REPORT+= and
       # note_worst matched nothing, so a crashed check yielded OVERALL PASS.
       n_hold+=1; line="${line/ $rc / $rc(nonstd) }"; rc=$VR_HOLD ;;
  esac
  REPORT+=("$line")
  note_worst "$rc"
  if [ "$STOP" = 1 ] && { [ "$rc" = "$VR_FAIL" ] || [ "$rc" = "$VR_HOLD" ]; }; then
    echo "  [stop-on-fail: halting after $id]"; break
  fi
done

echo
echo "================ VALIDATION REPORT (profile: ${EXPLICIT:+explicit}${EXPLICIT:-$PROFILE}) ================"
printf '%s\n' "${REPORT[@]}"
echo "----------------------------------------------------------------"
printf 'totals: PASS=%d FAIL=%d HOLD=%d MANUAL=%d SKIP=%d\n' "$n_pass" "$n_fail" "$n_hold" "$n_manual" "$n_skip"
echo "OVERALL: ${VR_WORD[$worst]} (exit $worst)"
exit "$worst"
