#!/usr/bin/env bash
#
# db-restore-drill.sh — ensaio de restore do dump do banco (ADR-042 / F6.I2.T2).
#
# Restaura o backup mais recente (ou o indicado) numa base DESCARTÁVEL, prova que o schema está
# íntegro (`migrate:status` sem pendências + contagem de tabelas) e mede o RTO. Nada toca a base
# de produção: o restore acontece em `concordia_restore_<ts>`, removida no fim.
#
# Uso:
#   tools/db-restore-drill.sh                 # dump mais recente em $STORAGE_PATH/backups
#   tools/db-restore-drill.sh --file <caminho>  # dump específico
#   tools/db-restore-drill.sh --keep          # não remove a base restaurada (inspeção manual)
#   tools/db-restore-drill.sh --from-offsite  # baixa o dump mais recente do disco `offsite` antes
#   tools/db-restore-drill.sh --from-offsite --disk=s3   # ensaio com o backend S3 disponível
#
set -Eeuo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"

DUMP=""
KEEP=0
FROM_OFFSITE=0
DISK="offsite"

usage() {
    sed -n '2,15p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}

while [ $# -gt 0 ]; do
    case "$1" in
        --file) shift; DUMP="${1:-}" ;;
        --file=*) DUMP="${1#*=}" ;;
        --keep) KEEP=1 ;;
        --from-offsite) FROM_OFFSITE=1 ;;
        --disk) shift; DISK="${1:-}" ;;
        --disk=*) DISK="${1#*=}" ;;
        -h | --help) usage; exit 0 ;;
        *) echo "flag desconhecida: $1" >&2; usage >&2; exit 2 ;;
    esac
    shift
done

[ -f .env ] || { echo "'.env' ausente." >&2; exit 2; }

# Lê uma chave do `.env` (última ocorrência, sem imprimir segredos).
env_value() {
    sed -n "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*//p" .env | tail -1 | sed 's/[[:space:]]*$//' | tr -d '"'
}

DB_NAME="$(env_value DB_DATABASE)"; DB_NAME="${DB_NAME:-concordia}"
DB_USER="$(env_value DB_USERNAME)"; DB_USER="${DB_USER:-concordia}"
STORAGE_PATH_V="$(env_value STORAGE_PATH)"; STORAGE_PATH_V="${STORAGE_PATH_V:-./storage}"
BACKUP_DIR="${STORAGE_PATH_V%/}/backups"

PROJECT_NAME="$(bash "$ROOT/tools/project-name.sh")"
# shellcheck source=tools/runtime.sh
. "$ROOT/tools/runtime.sh"
COMPOSE=("${ENGINE[@]}" compose -p "$PROJECT_NAME" --env-file .env "${COMPOSE_FILES[@]}")
# Artefato único (ADR-032): `APP_ENV` é o ÚNICO interruptor — não há `docker-compose.prod.yml`.

# Ensaio a partir da **cópia fora do host** (UPDATE_INFRA I7.T1): baixa antes de escolher o dump.
if [ "$FROM_OFFSITE" = 1 ] && [ -z "$DUMP" ]; then
    echo "==> baixando o dump mais recente de '$DISK' (cópia fora do host)"
    if ! "${COMPOSE[@]}" exec -T php-fpm php artisan core:backup:pull --disk="$DISK"; then
        echo "FALHA: não foi possível baixar o dump de '$DISK' (nenhum restore foi feito)." >&2
        exit 1
    fi
fi

if [ -z "$DUMP" ]; then
    DUMP="$(ls -1 "$BACKUP_DIR"/db-*.dump 2>/dev/null | sort | tail -1 || true)"
fi
[ -n "$DUMP" ] && [ -s "$DUMP" ] || { echo "nenhum dump em $BACKUP_DIR (rode scripts/update_prod.sh --execute --backup-only ou --file)." >&2; exit 2; }

TS="$(date -u +%Y%m%dT%H%M%SZ)"
SCRATCH="concordia_restore_${TS}"
SCRATCH_LC="$(printf '%s' "$SCRATCH" | tr '[:upper:]' '[:lower:]')"

cleanup() {
    if [ "$KEEP" = 0 ]; then
        "${COMPOSE[@]}" exec -T postgres psql -v ON_ERROR_STOP=1 -U "$DB_USER" -d postgres \
            -c "DROP DATABASE IF EXISTS \"${SCRATCH_LC}\" WITH (FORCE);" >/dev/null 2>&1 || true
    fi
}
trap cleanup EXIT

echo "==> dump: $DUMP ($(wc -c < "$DUMP" | tr -d ' ') bytes)"
echo "==> base descartável: ${SCRATCH_LC}"

started=$(date +%s)

"${COMPOSE[@]}" exec -T postgres psql -v ON_ERROR_STOP=1 -U "$DB_USER" -d postgres \
    -c "DROP DATABASE IF EXISTS \"${SCRATCH_LC}\" WITH (FORCE);" \
    -c "CREATE DATABASE \"${SCRATCH_LC}\";" >/dev/null

# O dump entra por stdin: o arquivo está no host (nunca copiado para dentro do container).
"${COMPOSE[@]}" exec -T postgres pg_restore --no-owner --no-acl -U "$DB_USER" -d "${SCRATCH_LC}" \
    --single-transaction < "$DUMP"

tables="$("${COMPOSE[@]}" exec -T postgres psql -tA -U "$DB_USER" -d "${SCRATCH_LC}" -c \
    "SELECT count(*) FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog','information_schema');")"
[ "${tables:-0}" -gt 0 ] || { echo "FALHA: restore sem tabelas." >&2; exit 1; }

migrations="$("${COMPOSE[@]}" exec -T postgres psql -tA -U "$DB_USER" -d "${SCRATCH_LC}" -c \
    "SELECT count(*) FROM migrations;")"

# `migrate:status` contra a base restaurada: schema tem de estar em dia (zero pendências).
status="$("${COMPOSE[@]}" exec -T -e "DB_DATABASE=${SCRATCH_LC}" php-fpm \
    php artisan migrate:status --no-interaction 2>&1 || true)"
pending="$(printf '%s' "$status" | grep -c 'Pending' || true)"

finished=$(date +%s)
rto=$((finished - started))

printf '==> tabelas: %s · migrations aplicadas: %s · pendentes: %s\n' "$tables" "$migrations" "$pending"
printf '{"event":"db.restore_drill","dump":"%s","tables":%s,"migrations":%s,"pending":%s,"rto_s":%s,"scratch":"%s"}\n' \
    "$(basename "$DUMP")" "$tables" "$migrations" "$pending" "$rto" "$SCRATCH_LC"

if [ "$pending" -gt 0 ]; then
    echo "FALHA: base restaurada com migrações pendentes (dump incompleto?)." >&2
    exit 1
fi

echo "==> OK — restore íntegro em ${rto}s (RTO do ensaio)."
[ "$KEEP" = 1 ] && echo "==> base mantida para inspeção: ${SCRATCH_LC} (remova com DROP DATABASE)."
exit 0
