#!/bin/sh
# Apply pending Prisma migrations, then hand off to the API.
#
# WHY THE CONTAINER AND NOT THE DEPLOY WORKFLOW
#
# The workflow still has its own `prisma migrate deploy` step and that is not
# redundant: it runs BEFORE the new images are pulled, i.e. while the OLD code
# is still serving, which is the order expand/contract migrations are designed
# around (see docs/DEPLOY_AND_MIGRATIONS.md). Doing it here as well closes the
# gaps that step cannot:
#
#   - The runner has to reach MySQL directly. When it cannot, the deploy is run
#     with `skip_migrate=true` and the schema silently does not move — the app
#     then boots against a schema that is one release behind, which surfaces
#     later as a column-not-found at runtime rather than as a failed deploy.
#   - A container restarted by `restart: unless-stopped` after a host reboot,
#     or a stack re-applied by hand in Portainer, never goes through the
#     workflow at all.
#
# `migrate deploy` is idempotent, so running it in both places costs one
# no-op query on the normal path.
#
# THE API DOES NOT START IF THE MIGRATION FAILS. That is deliberate: serving
# against a schema that does not match the code is worse than being down,
# because the failures it produces are partial and silent (a write to a column
# that does not exist yet fails for one feature while the rest of the app looks
# healthy). The container exits non-zero and Docker's restart policy retries.
set -e

SCHEMA=/repo/packages/database/prisma/schema.prisma

log() { echo "[entrypoint] $*"; }

if [ "${RUN_MIGRATIONS:-true}" != "true" ]; then
  log "RUN_MIGRATIONS=${RUN_MIGRATIONS} — skipping migrations, starting the API"
  exec "$@"
fi

if [ -z "${DATABASE_URL}" ]; then
  log "DATABASE_URL is unset; cannot migrate." >&2
  log "Set it, or set RUN_MIGRATIONS=false if you migrate out of band." >&2
  exit 1
fi

# pnpm's hoisted linker normally puts the CLI in the root .bin, but the
# workspace package keeps its own link too. Accept either rather than pinning
# a layout detail of the installer — the Dockerfile asserts at build time that
# one of these exists, so a missing CLI breaks the image build, not a deploy.
PRISMA=""
for candidate in /repo/node_modules/.bin/prisma \
                 /repo/packages/database/node_modules/.bin/prisma; do
  if [ -x "$candidate" ]; then
    PRISMA="$candidate"
    break
  fi
done
if [ -z "$PRISMA" ]; then
  log "prisma CLI not found in this image; cannot migrate." >&2
  exit 1
fi

# Retry ONLY a connection failure (P1001). On a full bring-up the database
# container can still be starting, and on galactus the API additionally has to
# resolve the host's MagicDNS name — a lookup that is unreliable for the first
# moments after a host reboot (see the dns block in the app compose file, and
# docs/DEPLOY_AND_MIGRATIONS.md).
#
# Every other failure exits immediately. Retrying a migration that is actually
# broken just delays the same error behind a minute of noise, and P3005 in
# particular needs a human.
attempt=1
max="${MIGRATE_MAX_ATTEMPTS:-20}"
delay="${MIGRATE_RETRY_SECONDS:-3}"

while : ; do
  log "prisma migrate deploy (attempt ${attempt}/${max})"
  if output=$("$PRISMA" migrate deploy --schema "$SCHEMA" 2>&1); then
    printf '%s\n' "$output"
    log "migrations up to date"
    break
  fi
  printf '%s\n' "$output" >&2

  if ! printf '%s' "$output" | grep -q 'P1001'; then
    log "migrate deploy FAILED — refusing to start the API." >&2
    if printf '%s' "$output" | grep -q 'P3005'; then
      log "P3005: the database has tables but no migration history. This is a" >&2
      log "database that predates Prisma migrations. Baseline it ONCE with:" >&2
      log "  npx prisma@5 migrate resolve --applied 0000_init --schema $SCHEMA" >&2
    fi
    exit 1
  fi

  if [ "$attempt" -ge "$max" ]; then
    log "database unreachable after ${max} attempts — giving up." >&2
    exit 1
  fi
  attempt=$((attempt + 1))
  sleep "$delay"
done

exec "$@"
