#!/usr/bin/env bash
#ddev-generated
## Description: Escrow a provisioned baseline outside the project and its mounts
## Usage: ai-eval-escrow [fixture...] [--force]
set -euo pipefail
export LC_ALL=C
umask 077
APPROOT=$(realpath -e "${DDEV_APPROOT:-.}")
PROJECT=${DDEV_PROJECT:-${DDEV_SITENAME:-}}
[[ "$PROJECT" =~ ^[a-zA-Z0-9_-]+$ ]] || { echo APG_ESCROW_PROJECT_REQUIRED >&2; exit 1; }
ROOT=$(realpath -m "${AI_PG_ESCROW_DIR:-$HOME/.ai-proving-ground-escrow}")
unsafe() { echo APG_ESCROW_UNSAFE >&2; exit 1; }
[[ "$ROOT" != "$APPROOT" && "$ROOT" != "$APPROOT/"* ]] || unsafe
# Recognize short volume syntax and long source syntax. Do not evaluate YAML
# or shell expressions. Unknown interpolation fails closed instead of guessing.
mount_sources=()
shopt -s nullglob
for compose in "$APPROOT"/.ddev/docker-compose.*.yaml; do
  while IFS= read -r source; do
    source=${source//\$\{HOME\}/$HOME}
    source=${source//\$HOME/$HOME}
    source=${source/#\~/$HOME}
    [[ "$source" != *'$'* ]] || unsafe
    case "$source" in
      /*) ;;
      ./*|../*) source="$APPROOT/.ddev/$source" ;;
      *) continue ;; # Named volumes are not host bind sources.
    esac
    source=$(realpath -m "$source")
    mount_sources+=("$source")
    [[ "$source" != / && "$ROOT" != "$source" && "$ROOT" != "$source/"* ]] || unsafe
  done < <(sed -nE "s/^[[:space:]]*(-[[:space:]]*)?source:[[:space:]]*['\"]?([^'\" #]+).*/\2/p; s/^[[:space:]]*-[[:space:]]*['\"]?([^'\":]+):[^:].*/\1/p" "$compose")
done
force=0
fixtures=()
for arg in "$@"; do
  if [[ "$arg" == --force ]]; then force=1; else fixtures+=("$arg"); fi
done
if [[ ${#fixtures[@]} == 0 ]]; then read -r -a fixtures <<< "${AI_PG_FIXTURES:-kb_docs canvas_home}"; fi
fixture_json=$(printf '%s\n' "${fixtures[@]}" | jq -Rsc 'split("\n")[:-1] | sort | unique')
# This runs only during trusted setup, using provision's real YAML parser.
# Tests put a ddev executable on PATH; production has no hash override.
key=$(ddev exec drush php:script --script-path="${AI_PG_ROOT:-/var/www/html/recipes/ai_proving_ground}/scripts" hash.php -- "${fixtures[@]}")
[[ "$key" =~ ^[0-9a-f]{12}$ ]] || { echo APG_ESCROW_KEY_FAILED >&2; exit 1; }
DEST="$ROOT/$PROJECT/$key"
for source in "${mount_sources[@]}"; do
  [[ "$DEST" != "$source" && "$DEST" != "$source/"* ]] || unsafe
done
# Resolve the final location too, including preexisting project/set symlinks.
resolved=$(realpath -m "$DEST")
[[ "$resolved" == "$ROOT/$PROJECT/$key" ]] || unsafe
if [[ -e "$DEST" && "$force" == 0 ]]; then echo APG_ESCROW_EXISTS >&2; exit 1; fi
baseline=${AI_PG_BASELINE_DIR:-$APPROOT/.ai-proving-ground}
# Accept the same /var/www/html paths used by container-side case runners.
baseline=${baseline/#\/var\/www\/html/$APPROOT}
baseline=$(realpath -m "$baseline")
[[ "$baseline" == "$APPROOT/"* ]] || unsafe
tag=${AI_PG_BASELINE_REF:-ai-pg-baseline-$key}
git check-ref-format "refs/tags/$tag" >/dev/null
commit=$(git -C "$APPROOT" rev-parse --verify "refs/tags/$tag^{commit}")
[[ -f "$baseline/baseline.$key.sql.gz" ]] || {
  echo APG_ESCROW_BASELINE_REQUIRED >&2; exit 1;
}
mkdir -p "$ROOT/$PROJECT"
TMP=$(mktemp -d "$ROOT/$PROJECT/.capture.XXXXXX")
trap 'rm -rf "$TMP"' EXIT
printf '{}\n' > "$TMP/files.json"
for name in "baseline.$key.sql.gz" "grader.$key.sha256" "grader.$key.files" "versions.$key.json" "files.$key.tar.gz" "files.$key.json" "files.$key.list" "composer.$key.state.json" "composer.$key.json" "composer.$key.lock" "site.$key.json" "site.$key.packages.json.gz"; do
  if [[ ! -f "$baseline/$name" ]]; then echo "APG_ESCROW_SKIPPED $name" >&2; continue; fi
  cp -- "$baseline/$name" "$TMP/$name"
  jq --arg name "$name" --arg path "${baseline#"$APPROOT/"}/$name" '. + {($name):$path}' "$TMP/files.json" > "$TMP/next.json"
  mv "$TMP/next.json" "$TMP/files.json"
done
# The PHP listing keys name logical roots. Resolve module installation paths
# from their info files; ambiguity requires the operator to fix the bed.
printf '%s\n' '.ddev/commands' '.ddev/agents' > "$TMP/roots"
if [[ -f "$baseline/grader.$key.files" ]]; then
  jq -er 'keys[] | split("/")[0:2] | join("/")' "$baseline/grader.$key.files" | sort -u > "$TMP/logical-roots"
else
  # Legacy provision may lack the sidecar. Keep the recipe and discover the
  # two grader modules on the host, rather than silently omitting judging code.
  printf '%s\n' recipes/ai_proving_ground > "$TMP/logical-roots"
  for module in ai_eval droost; do
    if [[ -n "$(find -L "$APPROOT" -name "$module.info.yml" -print -quit)" ]]; then
      printf 'modules/%s\n' "$module" >> "$TMP/logical-roots"
    fi
  done
fi
while IFS= read -r logical; do
  case "$logical" in
    recipes/*) physical="$logical" ;;
    modules/*)
      module=${logical#modules/}
      find -L "$APPROOT" -name .git -prune -o -name "$module.info.yml" -print0 > "$TMP/module-paths"
      mapfile -d '' candidates < "$TMP/module-paths"
      matches=()
      declare -A seen=()
      for candidate in "${candidates[@]}"; do
        canonical=$(realpath -e "$candidate")
        if [[ -z "${seen[$canonical]:-}" ]]; then matches+=("$candidate"); seen[$canonical]=1; fi
      done
      rm "$TMP/module-paths"
      [[ ${#matches[@]} == 1 ]] || { echo "APG_ESCROW_ROOT_AMBIGUOUS $logical" >&2; exit 1; }
      physical=$(dirname "${matches[0]#"$APPROOT/"}") ;;
    *) echo APG_ESCROW_ROOT_INVALID >&2; exit 1 ;;
  esac
  [[ -d "$APPROOT/$physical" ]] || { echo APG_ESCROW_ROOT_MISSING >&2; exit 1; }
  printf '%s\n' "$physical" >> "$TMP/roots"
done < "$TMP/logical-roots"
# Include the verifier source even if a future grader listing omits it.
script_dir="$APPROOT/recipes/ai_proving_ground/scripts"
cp -- "$script_dir/escrow-verify.sh" "$TMP/escrow-verify.sh"
git -C "$APPROOT" bundle create "$TMP/baseline.bundle" "refs/tags/$tag"
jq -n --arg project "$PROJECT" --arg key "$key" --arg tag "$tag" --arg commit "$commit" --arg created "$(date -u +%FT%TZ)" --argjson fixtures "$fixture_json" --slurpfile files "$TMP/files.json" --rawfile roots "$TMP/roots" '{project:$project,set_key:$key,tag:$tag,commit:$commit,created:$created,fixtures:$fixtures,baseline_files:$files[0],grader_roots:($roots | split("\n")[:-1] | unique)}' > "$TMP/escrow.json"
bash "$TMP/escrow-verify.sh" "$APPROOT" "$TMP" --capture > "$TMP/judging.json"
rm "$TMP/files.json" "$TMP/roots" "$TMP/logical-roots"
printf '{}\n' > "$TMP/hashes.json"
for path in "$TMP"/*; do
  name=${path##*/}
  [[ "$name" != escrow.json && "$name" != hashes.json && "$name" != next.json ]] || continue
  hash=$(sha256sum -- "$path" | cut -d ' ' -f1)
  jq --arg name "$name" --arg hash "$hash" '. + {($name):$hash}' "$TMP/hashes.json" > "$TMP/next.json"
  mv "$TMP/next.json" "$TMP/hashes.json"
done
jq --slurpfile hashes "$TMP/hashes.json" '.sha256=$hashes[0]' "$TMP/escrow.json" > "$TMP/next.json"
mv "$TMP/next.json" "$TMP/escrow.json"
rm "$TMP/hashes.json"
# Publish only a complete capture. --force is explicit replacement consent.
if [[ -e "$DEST" ]]; then rm -rf -- "$DEST"; fi
mv "$TMP" "$DEST"
echo "APG_ESCROW_SAVED $DEST" >&2
