#!/usr/bin/env bash

# Migrations: the half of an upgrade `./install` cannot do.
#
# The installer only ever adds. Migrations are the one mechanism that can
# remove a file, disable a unit, or repair a symlink on a machine that already
# exists -- which means they run, unattended, against machines nobody testing
# them has ever seen. The properties that make that safe:
#
#   1. Applied exactly once. A marker is written on success and never again.
#   2. A FAILURE LEAVES NO MARKER. A repair that could not complete has not
#      happened, and recording it as done would hide it forever.
#   3. Ordered, and stopped at the first failure. Later migrations may assume
#      earlier ones landed; running them anyway turns one stuck repair into an
#      unpredictable machine.
#   4. A fresh install runs none of them. The machine was just built from this
#      checkout, so every repair they describe is already true of it.
#   5. Root work goes through `panama-sudo --reason`, never bare sudo, so the
#      password prompt names the repair.
#
# Driven against fixture migrations in a throwaway state directory. The real
# migrations are only checked for the properties every one of them must have.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
runner="$repo_dir/bin/panama-migrate"
authoring="$repo_dir/bin/panama-dev-migration"
installer="$repo_dir/install"

findings=()
note() { findings+=("$1"); }

[[ -x "$runner" ]] || { printf 'migrations contract: %s is not executable\n' "$runner" >&2; exit 1; }
[[ -x "$authoring" ]] || note 'panama-dev-migration is missing or not executable'

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT

fake_repo="$work/repo"
state="$work/state/panama/migrations"
mkdir -p "$fake_repo/bin" "$fake_repo/migrations"
cp "$runner" "$fake_repo/bin/panama-migrate"

# Fixture migrations, named the way the real ones are: epoch seconds, so glob
# order is chronological.
ran="$work/ran"
write_migration() {
    local stamp="$1" body="$2"
    cat >"$fake_repo/migrations/$stamp.sh" <<MIGRATION
#!/usr/bin/env bash
printf '%s\n' "$stamp" >>"$ran"
$body
MIGRATION
}

run() {
    PANAMA_PATH="$fake_repo" XDG_STATE_HOME="$work/state" \
        "$fake_repo/bin/panama-migrate" "$@" 2>&1
}

# ── 1. Applied exactly once, in order ────────────────────────────────────────

: >"$ran"
write_migration 1000000001 'exit 0'
write_migration 1000000002 'exit 0'

run run >/dev/null || note 'a run of two good migrations failed'
[[ "$(cat "$ran")" == $'1000000001\n1000000002' ]] \
    || note "migrations did not run oldest-first (got: $(tr '\n' ' ' <"$ran"))"

: >"$ran"
run run >/dev/null || note 'a second run failed'
[[ -s "$ran" ]] && note 'a migration ran twice'

# ── 2 & 3. A failure leaves no marker, and stops the ones after it ───────────

: >"$ran"
write_migration 1000000003 'exit 1'
write_migration 1000000004 'exit 0'

run run >/dev/null && note 'a failing migration reported success'
grep -qx 1000000003 "$ran" || note 'the failing migration did not run at all'
grep -qx 1000000004 "$ran" && note 'a migration after a failure still ran'
[[ -e "$state/1000000003.sh" ]] && note 'a failed migration was marked applied'

# It retries, and the one behind it is still waiting.
: >"$ran"
write_migration 1000000003 'exit 0'
run run >/dev/null || note 'a repaired migration did not succeed on retry'
grep -qx 1000000003 "$ran" || note 'the previously failed migration was not retried'
grep -qx 1000000004 "$ran" || note 'the migration behind the failure never ran'

# ── 4. Fresh installs run nothing ────────────────────────────────────────────

rm -rf "$work/state"
: >"$ran"
run --baseline >/dev/null || note 'baseline failed'
[[ -s "$ran" ]] && note 'baseline ran a migration instead of marking it'
: >"$ran"
run run >/dev/null || note 'a run after baseline failed'
[[ -s "$ran" ]] && note 'a migration ran on a machine that was baselined'

# ── --pending is the notifier's question ─────────────────────────────────────

run --pending >/dev/null && note '--pending reported work when everything is applied'
write_migration 1000000005 'exit 0'
run --pending >/dev/null || note '--pending reported nothing when a migration is waiting'

# ── 5. Everything the repository actually ships ──────────────────────────────

shopt -s nullglob
for migration in "$repo_dir"/migrations/*.sh; do
    name="$(basename "$migration")"
    [[ -x "$migration" ]] || note "$name is not executable"
    [[ "$name" =~ ^[0-9]{10,}\.sh$ ]] \
        || note "$name is not named with an epoch timestamp, so its order is undefined"

    # Bare sudo would prompt with polkit's generic text, in a script the user
    # did not run by hand. panama-sudo makes the prompt name the repair.
    if grep -qE '(^|[^-[:alnum:]])sudo ' "$migration" && ! grep -q 'panama-sudo' "$migration"; then
        note "$name calls bare sudo; root work goes through panama-sudo --reason"
    fi
done

# ── The installer wires it in ────────────────────────────────────────────────

grep -q 'panama-migrate' "$installer" \
    || note 'install never runs or baselines migrations, so a fresh machine is never marked'
grep -q -- '--baseline' "$installer" \
    || note 'install does not baseline a fresh machine, so it would run repairs it never needed'

notifier="$repo_dir/bin/panama-migrate-notify"
if [[ ! -x "$notifier" ]]; then
    note 'panama-migrate-notify is missing, so pending repairs announce themselves to nobody'
else
    grep -q 'org.freedesktop.Notifications' "$notifier" \
        || note 'the notifier does not wait for the notification server, so an early session swallows it'
fi
grep -q 'panama-migrate-notify' "$repo_dir/config/dot/hypr/autostart.lua" \
    || note 'nothing starts the migration notifier at login'

if (( ${#findings[@]} > 0 )); then
    printf 'migrations contract: %d finding(s)\n' "${#findings[@]}" >&2
    printf '  - %s\n' "${findings[@]}" >&2
    exit 1
fi

printf 'migrations contract: PASS\n'
