#!/usr/bin/env bash
# Deploy this working tree to production. Stream E, task E4b. Run from the dev server:
#
#     deploy/production.sh                # sync + build + up + health check
#     deploy/production.sh --seed-db backups/dfp-nightly-<date>.dump   # first deploy only
#     deploy/production.sh --caddy        # also (re)build and reload the droplet's Caddy
#
# What it does, in order: dumps the production database (always: it is small and a
# migration may ride), rsyncs the code and the read-only inputs over Tailscale, builds
# and recreates the containers there, waits for /api/health, prints the version it
# reports. Exits non-zero on any failure. It never copies .app.env or data/: production
# keeps its own secrets and its own database.
set -euo pipefail

HOST="${DFP_PROD_HOST:-deploy@bwlive}"        # Tailscale name; set DFP_PROD_HOST to override
REMOTE_DIR=/srv/apps/dutyfreeprofessor
PORT=3149
SEED_DB=""
CADDY=0
while [ $# -gt 0 ]; do
  case "$1" in
    --seed-db) SEED_DB="$2"; shift 2 ;;
    --caddy) CADDY=1; shift ;;
    *) echo "unknown argument: $1" >&2; exit 2 ;;
  esac
done

cd "$(dirname "$0")/.."
here=$(pwd)
say() { printf '\n== %s\n' "$*"; }
remote() { ssh -o BatchMode=yes -o ConnectTimeout=15 "$HOST" "$@"; }

say "preflight"
git status --porcelain | grep -q . && echo "note: working tree has uncommitted changes" || true
# A deploy recreates the app container and kills any collection or verify running there.
# Refuse while one is running; the window file in .logs/runs/ says when it ends.
if remote "pgrep -f 'app.cli (collect|verify)' >/dev/null 2>&1"; then
  echo "REFUSED: a collection or verify is running on $HOST (pgrep app.cli). A deploy would kill it." >&2
  echo "Wait for the open window in .logs/runs/window-*.md to say finished, then deploy." >&2
  exit 1
fi
remote "test -f $REMOTE_DIR/.app.env" || { echo "production .app.env missing on $HOST (stage it: RUNBOOK 'Production')" >&2; exit 1; }
remote "grep -c REPLACE_WITH_ $REMOTE_DIR/.app.env || true" | grep -q '^0$' || { echo "production .app.env still has placeholders" >&2; exit 1; }

if remote "docker ps --format '{{.Names}}' | grep -q '^dutyfreeprofessor-db$'"; then
  say "dump production database first"
  remote "mkdir -p $REMOTE_DIR/backups && docker exec dutyfreeprofessor-db pg_dump -U dfp -Fc dfp > $REMOTE_DIR/backups/dfp-\$(date -u +%Y-%m-%d-%H%M)-pre-deploy.dump"
fi

say "sync code and inputs"
rsync -az --delete \
  --exclude node_modules --exclude __pycache__ --exclude .pytest_cache --exclude .mypy_cache \
  --exclude web/dist --exclude static --exclude '*.pyc' \
  "$here/main/" "$HOST:$REMOTE_DIR/main/"
rsync -az --delete "$here/import/" "$HOST:$REMOTE_DIR/import/"
rsync -az --delete "$here/public/" "$HOST:$REMOTE_DIR/public/"
rsync -az "$here/uploads/" "$HOST:$REMOTE_DIR/uploads/"     # additive: production may hold newer uploads
rsync -az "$here/docker-compose.yml" "$here/docker-compose.production.yml" "$here/.env" "$HOST:$REMOTE_DIR/"

if [ -n "$SEED_DB" ]; then
  say "seed the production database from $SEED_DB"
  rsync -az "$SEED_DB" "$HOST:$REMOTE_DIR/backups/seed.dump"
  remote "cd $REMOTE_DIR && docker compose up -d db && sleep 5 && docker exec -i dutyfreeprofessor-db pg_restore -U dfp -d dfp --clean --if-exists --no-owner < backups/seed.dump || true"
fi

if [ "$CADDY" = 1 ]; then
  say "caddy"
  rsync -az "$here/deploy/caddy/" --exclude cloudflare.env.example "$HOST:/srv/caddy/"
  remote "test -f /srv/caddy/cloudflare.env" || { echo "/srv/caddy/cloudflare.env missing on $HOST" >&2; exit 1; }
  remote "cd /srv/caddy && docker compose up -d --build && docker exec caddy caddy reload --config /etc/caddy/Caddyfile"
fi

say "build and recreate"
remote "cd $REMOTE_DIR && docker compose -f docker-compose.yml -f docker-compose.production.yml up -d --build --remove-orphans"

say "health"
for i in $(seq 1 30); do
  if body=$(remote "curl -s --max-time 5 http://172.17.0.1:$PORT/api/health"); then
    echo "$body"; break
  fi
  sleep 2
  [ "$i" = 30 ] && { echo "health never answered" >&2; remote "cd $REMOTE_DIR && docker compose logs --tail 50 app"; exit 1; }
done
say "done"
