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

root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
validator=$root/scripts/ci-validate-migrations.mjs
fixture_root=$(mktemp -d)
trap 'rm -rf "$fixture_root"' EXIT

new_repo() {
  local name=$1
  local repo=$fixture_root/$name

  mkdir -p "$repo/apps/api/migrations"
  git -C "$repo" init -q
  git -C "$repo" config user.name 'Release Migration Test'
  git -C "$repo" config user.email 'release-migration-test@example.invalid'
  printf 'CREATE TABLE accounts (id bigint PRIMARY KEY);\n' > \
    "$repo/apps/api/migrations/0001_init.sql"
  git -C "$repo" add apps/api/migrations/0001_init.sql
  git -C "$repo" commit -qm baseline
  printf '%s\n' "$repo"
}

commit_all() {
  local repo=$1
  local message=$2
  git -C "$repo" add -A
  git -C "$repo" commit -qm "$message"
}

expect_pass() {
  local repo=$1
  local base=$2
  (cd "$repo" && node "$validator" "$base")
}

expect_fail() {
  local repo=$1
  local base=$2
  local expected=$3
  local output status

  set +e
  output=$(cd "$repo" && node "$validator" "$base" 2>&1)
  status=$?
  set -e
  if [[ $status -eq 0 ]]; then
    printf 'migration validator unexpectedly passed: %s\n' "$expected" >&2
    exit 1
  fi
  grep -Fq "$expected" <<<"$output" || {
    printf 'missing expected migration failure %q in:\n%s\n' "$expected" "$output" >&2
    exit 1
  }
}

write_review() {
  local repo=$1
  local file=$2
  local sha256=$3
  local violations=$4

  mkdir -p "$repo/scripts"
  cat >"$repo/scripts/migration-safety-reviewed.json" <<JSON
{
  "version": 1,
  "migrations": {
    "$file": {
      "sha256": "$sha256",
      "allowedViolations": $violations,
      "reason": "Reviewed by the migration test fixture."
    }
  }
}
JSON
}

repo=$(new_repo safe)
base=$(git -C "$repo" rev-parse HEAD)
cat >"$repo/apps/api/migrations/0002_expand_accounts.sql" <<'SQL'
-- DROP in a comment and string is not executable.
ALTER TABLE accounts ADD COLUMN display_name text;
CREATE TABLE audit_events (id bigint PRIMARY KEY, note text);
INSERT INTO audit_events (id, note) VALUES (1, 'DROP TABLE is documentation');
UPDATE accounts SET display_name = 'ready' WHERE display_name IS NULL;
SQL
commit_all "$repo" safe
expect_pass "$repo" "$base"

repo=$(new_repo immutable)
base=$(git -C "$repo" rev-parse HEAD)
printf '\n-- retroactive edit\n' >>"$repo/apps/api/migrations/0001_init.sql"
commit_all "$repo" immutable
expect_fail "$repo" "$base" 'applied migrations are immutable (status M)'

repo=$(new_repo removed)
base=$(git -C "$repo" rev-parse HEAD)
git -C "$repo" rm -q apps/api/migrations/0001_init.sql
git -C "$repo" commit -qm removed
expect_fail "$repo" "$base" 'applied migrations are immutable (status D)'

repo=$(new_repo destructive)
base=$(git -C "$repo" rev-parse HEAD)
printf 'DROP TABLE accounts;\n' >"$repo/apps/api/migrations/0002_drop.sql"
commit_all "$repo" destructive
expect_fail "$repo" "$base" 'destructive DROP operation'

repo=$(new_repo incompatible)
base=$(git -C "$repo" rev-parse HEAD)
printf 'ALTER TABLE accounts ADD COLUMN external_id text NOT NULL;\n' > \
  "$repo/apps/api/migrations/0002_incompatible.sql"
commit_all "$repo" incompatible
expect_fail "$repo" "$base" 'non-null column addition'

repo=$(new_repo reviewed)
base=$(git -C "$repo" rev-parse HEAD)
printf 'ALTER TABLE accounts ADD COLUMN external_id text NOT NULL;\\n' > \
  "$repo/apps/api/migrations/0002_reviewed.sql"
digest=$(shasum -a 256 "$repo/apps/api/migrations/0002_reviewed.sql" | awk '{print $1}')
write_review \
  "$repo" \
  apps/api/migrations/0002_reviewed.sql \
  "$digest" \
  '["non-null column addition"]'
commit_all "$repo" reviewed
expect_pass "$repo" "$base"

repo=$(new_repo reviewed_tampered)
base=$(git -C "$repo" rev-parse HEAD)
printf 'ALTER TABLE accounts ADD COLUMN external_id text NOT NULL;\\n' > \
  "$repo/apps/api/migrations/0002_reviewed.sql"
write_review \
  "$repo" \
  apps/api/migrations/0002_reviewed.sql \
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
  '["non-null column addition"]'
commit_all "$repo" reviewed_tampered
expect_fail "$repo" "$base" 'checksum mismatch'

repo=$(new_repo transaction)
base=$(git -C "$repo" rev-parse HEAD)
printf 'ALTER TABLE accounts ADD COLUMN nickname text; COMMIT;\n' > \
  "$repo/apps/api/migrations/0002_transaction.sql"
commit_all "$repo" transaction
expect_fail "$repo" "$base" 'transaction control operation'

repo=$(new_repo filename)
base=$(git -C "$repo" rev-parse HEAD)
printf 'SELECT 1;\n' >"$repo/apps/api/migrations/next.SQL"
commit_all "$repo" filename
expect_fail "$repo" "$base" 'new migration must use NNNN_lowercase_name.sql'

repo=$(new_repo ordering)
base=$(git -C "$repo" rev-parse HEAD)
printf 'SELECT 1;\n' >"$repo/apps/api/migrations/0000_late.sql"
commit_all "$repo" ordering
expect_fail "$repo" "$base" 'new migration name must sort after 0001_init.sql'

repo=$(new_repo nested)
base=$(git -C "$repo" rev-parse HEAD)
mkdir -p "$repo/apps/api/migrations/archive"
printf 'SELECT 1;\n' >"$repo/apps/api/migrations/archive/0002_hidden.sql"
commit_all "$repo" nested
expect_fail "$repo" "$base" 'new migration must use NNNN_lowercase_name.sql'

repo=$(new_repo symlink)
base=$(git -C "$repo" rev-parse HEAD)
printf 'SELECT 1;\n' >"$repo/outside.sql"
ln -s ../../../outside.sql "$repo/apps/api/migrations/0002_symlink.sql"
commit_all "$repo" symlink
expect_fail "$repo" "$base" 'new migration must be a mode 100644 regular file'

repo=$(new_repo ancestry)
base=$(git -C "$repo" rev-parse HEAD)
git -C "$repo" switch -q --orphan unrelated
printf 'unrelated\n' >"$repo/unrelated"
git -C "$repo" add unrelated
git -C "$repo" commit -qm unrelated
expect_fail "$repo" "$base" 'release base is not an ancestor of HEAD'

repo=$(new_repo invalid_sha)
expect_fail "$repo" deadbeef 'must be a full lowercase commit SHA'

echo 'migration_tests=PASS'
