#!/bin/sh
set -eu

ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env"
TEST_IMAGE="${TEST_IMAGE:-who-need-help:test}"
MODE=${1:-all}

case "$MODE" in
  all | --database-only) ;;
  *)
    echo "Usage: $0 [--database-only]" >&2
    exit 1
    ;;
esac

if [ ! -f "$ENV_FILE" ]; then
  echo "Missing $ENV_FILE. Copy .env.example to .env first." >&2
  exit 1
fi

for command in docker openssl perl; do
  if ! command -v "$command" >/dev/null 2>&1; then
    echo "Required command is unavailable: $command" >&2
    exit 1
  fi
done

set -a
# shellcheck source=/dev/null
. "$ENV_FILE"
set +a

if [ "${DATABASE_MODE:-container}" != container ]; then
  echo "Local PostgreSQL rotation requires DATABASE_MODE=container." >&2
  echo "Rotate external database credentials at their owning provider instead." >&2
  exit 1
fi

: "${POSTGRES_USER:?POSTGRES_USER is missing from .env}"
: "${POSTGRES_DB:?POSTGRES_DB is missing from .env}"

case "$POSTGRES_USER" in
  *[!A-Za-z0-9_]* | "") echo "POSTGRES_USER contains unsupported characters." >&2; exit 1 ;;
esac

case "$POSTGRES_DB" in
  *[!A-Za-z0-9_]* | "") echo "POSTGRES_DB contains unsupported characters." >&2; exit 1 ;;
esac

generate_phoenix_secret() {
  docker run --rm "$TEST_IMAGE" mix phx.gen.secret
}

new_postgres_password=$(openssl rand -hex 32)
new_database_url="ecto://${POSTGRES_USER}:${new_postgres_password}@db/${POSTGRES_DB}"

tmp_env=$(mktemp "${ENV_FILE}.rotate.XXXXXX")
trap 'rm -f "$tmp_env"' EXIT HUP INT TERM
chmod 600 "$tmp_env"

if [ "$MODE" = "--database-only" ]; then
  NEW_POSTGRES_PASSWORD=$new_postgres_password \
  NEW_DATABASE_URL=$new_database_url \
    perl -pe '
      s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$ENV{NEW_POSTGRES_PASSWORD}/;
      s/^DATABASE_URL=.*/DATABASE_URL=$ENV{NEW_DATABASE_URL}/;
    ' "$ENV_FILE" >"$tmp_env"
else
  new_secret_key_base=$(generate_phoenix_secret)
  new_handover_secret=$(generate_phoenix_secret)
  new_release_cookie=$(generate_phoenix_secret)

  NEW_SECRET_KEY_BASE=$new_secret_key_base \
  NEW_HANDOVER_SECRET=$new_handover_secret \
  NEW_RELEASE_COOKIE=$new_release_cookie \
  NEW_POSTGRES_PASSWORD=$new_postgres_password \
  NEW_DATABASE_URL=$new_database_url \
    perl -pe '
      s/^SECRET_KEY_BASE=.*/SECRET_KEY_BASE=$ENV{NEW_SECRET_KEY_BASE}/;
      s/^HANDOVER_SECRET=.*/HANDOVER_SECRET=$ENV{NEW_HANDOVER_SECRET}/;
      s/^RELEASE_COOKIE=.*/RELEASE_COOKIE=$ENV{NEW_RELEASE_COOKIE}/;
      s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$ENV{NEW_POSTGRES_PASSWORD}/;
      s/^DATABASE_URL=.*/DATABASE_URL=$ENV{NEW_DATABASE_URL}/;
    ' "$ENV_FILE" >"$tmp_env"
fi

printf "ALTER ROLE \"%s\" WITH PASSWORD '%s';\n" \
  "$POSTGRES_USER" "$new_postgres_password" |
  docker compose --project-directory "$ROOT" exec -T db \
    psql --set ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \
    >/dev/null

mv "$tmp_env" "$ENV_FILE"
chmod 600 "$ENV_FILE"
trap - EXIT HUP INT TERM

if [ "$MODE" = "--database-only" ]; then
  echo "Local PostgreSQL password rotated without printing its value."
else
  echo "Local application and PostgreSQL secrets rotated without printing their values."
fi
echo "Run docker compose up -d --wait to apply the new application environment."
