#!/usr/bin/env bash
set -uo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
release_tool="${script_dir}/web-release.sh"
work="${XDG_CACHE_HOME:-$HOME/.cache}/overdeck/tests/web-release/run-$$-$RANDOM"
mkdir -p "$work"
failures=0
locker_pid=""
trap '[[ -z "$locker_pid" ]] || kill "$locker_pid" 2>/dev/null; rm -rf "$work"' EXIT

ok() { printf 'ok   %s\n' "$1"; }
no() { printf 'FAIL %s: %s\n' "$1" "$2" >&2; failures=$((failures + 1)); }
make_release() { mkdir -p "$1/server"; printf 'entry\n' >"$1/server/entry.mjs"; }

export OVERDECK_WEB_RELEASES_DIR="$work/releases"
export OVERDECK_WEB_CURRENT_LINK="$work/releases/current"
export OVERDECK_WEB_PREVIOUS_LINK="$work/releases/previous"
export OVERDECK_WEB_RELEASE_LOCK="$work/releases/.release.lock"
mkdir -p "$OVERDECK_WEB_RELEASES_DIR"
make_release "$work/releases/one"
make_release "$work/releases/two"

"$release_tool" promote "$work/releases/one" >/dev/null
"$release_tool" promote "$work/releases/two" >/dev/null
if [[ "$(readlink -f "$OVERDECK_WEB_CURRENT_LINK")" == "$work/releases/two" && "$(readlink -f "$OVERDECK_WEB_PREVIOUS_LINK")" == "$work/releases/one" ]]; then
  ok "promotion records one coherent current/previous pair"
else
  no "promotion records one coherent current/previous pair" "wrong links"
fi

"$release_tool" rollback >/dev/null
if [[ "$(readlink -f "$OVERDECK_WEB_CURRENT_LINK")" == "$work/releases/one" && "$(readlink -f "$OVERDECK_WEB_PREVIOUS_LINK")" == "$work/releases/two" ]]; then
  ok "rollback atomically swaps current and previous"
else
  no "rollback atomically swaps current and previous" "wrong links"
fi

make_release "$work/outside"
"$release_tool" promote "$work/outside" >/dev/null 2>&1
[[ $? -ne 0 && "$(readlink -f "$OVERDECK_WEB_CURRENT_LINK")" == "$work/releases/one" ]] && ok "outside release is rejected" || no "outside release is rejected" "current changed"

make_release "$work/releases/symlink-entry"
rm "$work/releases/symlink-entry/server/entry.mjs"
ln -s "$work/releases/one/server/entry.mjs" "$work/releases/symlink-entry/server/entry.mjs"
"$release_tool" promote "$work/releases/symlink-entry" >/dev/null 2>&1
[[ $? -ne 0 && "$(readlink -f "$OVERDECK_WEB_CURRENT_LINK")" == "$work/releases/one" ]] && ok "symlink server entry is rejected" || no "symlink server entry is rejected" "current changed"

(
  exec 9>"$OVERDECK_WEB_RELEASE_LOCK"
  flock 9
  printf locked >"$work/locked"
  sleep 2
) &
locker_pid=$!
for _ in {1..20}; do [[ -f "$work/locked" ]] && break; sleep 0.05; done
OVERDECK_WEB_RELEASE_LOCK_WAIT=0.1 "$release_tool" promote "$work/releases/two" >/dev/null 2>&1
lock_rc=$?
wait "$locker_pid"
locker_pid=""
if [[ $lock_rc -ne 0 && "$(readlink -f "$OVERDECK_WEB_CURRENT_LINK")" == "$work/releases/one" ]]; then
  ok "concurrent transition cannot race pointer state"
else
  no "concurrent transition cannot race pointer state" "transition bypassed lock"
fi

make_release "$work/releases/three"
printf '#!/usr/bin/env bash\nprintf restarted >>"$MARKER"\n' >"$work/restart"
printf '#!/usr/bin/env bash\nexit "${VERIFY_RC:-0}"\n' >"$work/verify"
chmod +x "$work/restart" "$work/verify"
export MARKER="$work/restarts" VERIFY_RC=1
OVERDECK_WEB_RELEASE_RESTART_CMD="$work/restart" OVERDECK_WEB_RELEASE_VERIFY_CMD="$work/verify" "$release_tool" activate "$work/releases/three" >/dev/null 2>&1
activate_rc=$?
if [[ $activate_rc -ne 0 && "$(readlink -f "$OVERDECK_WEB_CURRENT_LINK")" == "$work/releases/one" && "$(<"$work/restarts")" == restartedrestarted ]]; then
  ok "failed smoke restores prior release and restarts service"
else
  no "failed smoke restores prior release and restarts service" "release or service was not restored"
fi
rm -f "$work/restarts"
printf '%s\n' \
  '#!/usr/bin/env bash' \
  'count=0' \
  '[[ ! -f "$COUNT" ]] || read -r count <"$COUNT"' \
  'count=$((count + 1))' \
  'printf %s "$count" >"$COUNT"' \
  '((count > 1))' >"$work/restart-once"
chmod +x "$work/restart-once"
export COUNT="$work/count" VERIFY_RC=0
OVERDECK_WEB_RELEASE_RESTART_CMD="$work/restart-once" OVERDECK_WEB_RELEASE_VERIFY_CMD="$work/verify" "$release_tool" activate "$work/releases/three" >/dev/null 2>&1
activate_rc=$?
if [[ $activate_rc -ne 0 && "$(readlink -f "$OVERDECK_WEB_CURRENT_LINK")" == "$work/releases/one" && "$(<"$work/count")" == 2 ]]; then
  ok "failed restart restores prior release and restarts service"
else
  no "failed restart restores prior release and restarts service" "release or service was not restored"
fi

rm -f "$work/count"
printf '#!/usr/bin/env bash\ncount=0\n[[ ! -f "$COUNT" ]] || read -r count <"$COUNT"\ncount=$((count + 1))\nprintf %%s "$count" >"$COUNT"\n((count >= 3))\n' >"$work/verify-delayed"
chmod +x "$work/verify-delayed"
OVERDECK_WEB_RELEASE_VERIFY_TIMEOUT=3 OVERDECK_WEB_RELEASE_VERIFY_DELAY=1 \
  OVERDECK_WEB_RELEASE_RESTART_CMD="$work/restart" OVERDECK_WEB_RELEASE_VERIFY_CMD="$work/verify-delayed" \
  "$release_tool" activate "$work/releases/three" >/dev/null 2>&1
activate_rc=$?
if [[ $activate_rc -eq 0 && "$(readlink -f "$OVERDECK_WEB_CURRENT_LINK")" == "$work/releases/three" && "$(<"$work/count")" == 3 ]]; then
  ok "activation waits for delayed service readiness"
else
  no "activation waits for delayed service readiness" "release was not activated after becoming ready"
fi

make_release "$work/releases/.build-old-aaaaaa"
mkdir -p "$work/releases/.build-old-aaaaaa/node_modules"
chmod a-w "$work/releases/.build-old-aaaaaa/node_modules"
mkdir -p "$work/releases/.deps-old-bbbbbb" "$work/releases/.bootstrap-old-cccccc" "$work/releases/.deps-fresh-dddddd"
touch -d '2 hours ago' "$work/releases/.build-old-aaaaaa" "$work/releases/.deps-old-bbbbbb" "$work/releases/.bootstrap-old-cccccc"
"$release_tool" prune >/dev/null
if [[ ! -e "$work/releases/.build-old-aaaaaa" && ! -e "$work/releases/.deps-old-bbbbbb" && ! -e "$work/releases/.bootstrap-old-cccccc" ]]; then
  ok "prune removes aged retired staging dirs including read-only trees"
else
  no "prune removes aged retired staging dirs including read-only trees" "retired dirs survived"
fi
if [[ -d "$work/releases/.deps-fresh-dddddd" && -d "$work/releases/three" && -d "$work/releases/one" ]]; then
  ok "prune keeps current, previous, and fresh in-flight dirs"
else
  no "prune keeps current, previous, and fresh in-flight dirs" "protected dir was removed"
fi

ln -s "$work/releases/three" "$work/releases/.build-link-eeeeee"
touch -h -d '2 hours ago' "$work/releases/.build-link-eeeeee"
"$release_tool" prune >/dev/null
if [[ -L "$work/releases/.build-link-eeeeee" && -d "$work/releases/three" ]]; then
  ok "prune never follows a symlinked staging entry"
else
  no "prune never follows a symlinked staging entry" "symlink or its target was removed"
fi
rm -f "$work/releases/.build-link-eeeeee"

if ((failures == 0)); then
  echo "web-release: all tests passed"
  exit 0
fi
echo "web-release: ${failures} test(s) failed" >&2
exit 1
