#!/usr/bin/env bash

# Versioned upgrades for settings.json.
#
# The schema says what a setting IS; it cannot say what a setting USED to be.
# Rename a key, change its units, or split one setting into two, and the stored
# value stops being recognized -- and unrecognized keys are deliberately carried
# through untouched, so the user's choice silently stops taking effect with
# nothing to explain it.
#
# The list of migrations is empty today, which is exactly why this is tested
# now: the first time it runs for real will be against somebody's actual
# settings during an upgrade, and that is a poor moment to discover how it
# behaves. The harness supplies fixture steps, including one that throws.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
harness="$repo_dir/config/dot/quickshell/migrations-harness.qml"

fail() {
    printf 'migrations contract: %s\n' "$1" >&2
    exit 1
}

[[ -r "$harness" ]] || fail "the harness is missing: $harness"

out="$(timeout 60 qs -p "$harness" 2>&1 | grep -o 'PANAMA-MIGRATIONS .*' | sed 's/^PANAMA-MIGRATIONS //')"
[[ -n "$out" ]] || fail 'the harness produced no result'
jq -e . >/dev/null 2>&1 <<<"$out" || fail "the harness did not emit JSON: $out"

check() {
    jq -e "$1" >/dev/null <<<"$out" || fail "$2 -- got $(jq -c "$3" <<<"$out")"
}

# A file written before versioning existed is stamped, NOT migrated. Running
# the list against it would apply upgrades designed for schemas it never had.
check '.unversioned.v == 1 and .unversioned.migrated == false and .unversioned.untouched == true' \
    'a file with no schemaVersion must be stamped at the baseline without being migrated' '.unversioned'

# The stamp has to reach disk. Reported as changed-but-not-migrated, it would
# otherwise live only in memory and be redone on every single launch.
check '.unversioned.changed == true' \
    'stamping a pre-versioning file must be reported as a change so it gets written' '.unversioned'

# A file from the future must not be rewritten at all.
check '.future.changed == false' \
    'a file from a newer version must not be written back' '.future'

# Every step above the stored version runs, in order.
check '.upgrade.v == 3 and .upgrade.b == 2 and .upgrade.c == "three" and .upgrade.count == 2' \
    'an older file must run each pending step in order and end at the current version' '.upgrade'

# Already current: nothing runs, nothing is touched.
check '.current.migrated == false and .current.kept == true and .current.b == true' \
    'a file already at the current version must be left alone' '.current'

# A file from a NEWER Panama is left completely alone. Downgrading keys is not
# something this can do correctly, and unknown keys are already preserved.
check '.future.v == 9 and .future.migrated == false and .future.kept == true' \
    'a file from a newer version must not be modified or downgraded' '.future'

# A failing step stops at the last good version. Skipping past it would lose
# the conversion forever; failing the whole load would cost every setting.
check '.failure.v == 3 and .failure.count == 2 and .failure.kept == true' \
    'a failing step must stop at the last good version, keeping the steps that succeeded' '.failure'

# The promise that makes rollback safe.
check '.preserved.kept == true' \
    'a migration must not discard keys it does not recognize' '.preserved'

printf 'migrations contract: PASS\n'
