#!/usr/bin/env bash
# restore.sh — restaura um backup. É a operação mais perigosa do framework.
#
# Restaurar SOBRESCREVE dados que existem agora. Por isso, antes de tocar em
# qualquer coisa, este script faz um backup do estado ATUAL. Se a restauração
# for a errada, ainda dá para voltar.
#
# Guard rails:
#   - exige --confirmed E --i-understand com o nome do banco digitado
#   - verifica a integridade do arquivo ANTES de mexer no banco
#   - salva o estado atual antes de sobrescrever
#   - para a aplicação durante a restauração e sobe de volta no fim
#
# Uso:
#   restore.sh --host <ip> --user <deploy> --file db-20260805-030000.sql.gz \
#              --confirmed --i-understand <nome-do-banco>

set -uo pipefail

HOST=""; USER_NAME="deploy"; PORT=22; KEY_PATH=""
FILE=""; APP_PATH="/srv/app"; BACKUP_DIR="/srv/backups"
SERVICE="sshepherd-app"; CONFIRMED=0; UNDERSTAND=""

while [ $# -gt 0 ]; do
  case "$1" in
    --host) HOST="$2"; shift 2 ;;
    --user) USER_NAME="$2"; shift 2 ;;
    --port) PORT="$2"; shift 2 ;;
    --key) KEY_PATH="$2"; shift 2 ;;
    --file) FILE="$2"; shift 2 ;;
    --app-path) APP_PATH="$2"; shift 2 ;;
    --backup-dir) BACKUP_DIR="$2"; shift 2 ;;
    --service) SERVICE="$2"; shift 2 ;;
    --confirmed) CONFIRMED=1; shift ;;
    --i-understand) UNDERSTAND="$2"; shift 2 ;;
    *) echo "argumento desconhecido: $1" >&2; exit 2 ;;
  esac
done

[ -n "$HOST" ] || { echo "ERRO: --host é obrigatório" >&2; exit 2; }
[ -n "$FILE" ] || { echo "ERRO: --file é obrigatório" >&2; exit 2; }

# Tudo que entra em comando remoto é validado pela forma.
erro_arg() { echo "ERRO: $1" >&2; exit 2; }
if ! printf '%s' "$FILE" | grep -qE '^(db|pre-restore)-[0-9]{8}-[0-9]{6}\.sql\.gz$'; then
  erro_arg "--file precisa ser um dump gerado pelo SSHepherd, como db-20260805-030000.sql.gz"
fi
case "$APP_PATH" in /*) ;; *) erro_arg "--app-path precisa ser caminho absoluto" ;; esac
case "$APP_PATH" in *[!A-Za-z0-9._/-]*|*..*) erro_arg "--app-path com caractere inválido" ;; esac
case "$BACKUP_DIR" in /*) ;; *) erro_arg "--backup-dir precisa ser caminho absoluto" ;; esac
case "$BACKUP_DIR" in *[!A-Za-z0-9._/-]*|*..*) erro_arg "--backup-dir com caractere inválido" ;; esac
case "$SERVICE" in *[!A-Za-z0-9._@-]*|"") erro_arg "--service inválido" ;; esac

SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ForwardAgent=no -o ConnectTimeout=10 -p "$PORT")
[ -n "$KEY_PATH" ] && SSH_OPTS+=(-i "${KEY_PATH/#\~/$HOME}" -o IdentitiesOnly=yes)
TARGET="${USER_NAME}@${HOST}"

# ── 1. o arquivo existe e presta? ────────────────────────────────────────────
echo "== verificando o arquivo antes de tocar em qualquer coisa =="
INFO=$(ssh "${SSH_OPTS[@]}" "$TARGET" "
  F='${BACKUP_DIR}/${FILE}'
  [ -f \"\$F\" ] || { echo 'erro=arquivo_nao_existe'; exit 1; }
  echo \"tamanho=\$(stat -c%s \"\$F\")\"
  echo \"data=\$(stat -c%y \"\$F\" | cut -d. -f1)\"
  gzip -t \"\$F\" 2>/dev/null && echo 'integridade=ok' || echo 'integridade=CORROMPIDO'
  META='${APP_PATH}/shared/.sshepherd-db'
  if [ -f \"\$META\" ]; then
    echo \"engine=\$(grep -E '^engine=' \"\$META\" | head -n1 | cut -d= -f2 | tr -dc 'a-z')\"
    echo \"banco=\$(grep -E '^dbname=' \"\$META\" | head -n1 | cut -d= -f2 | tr -dc 'A-Za-z0-9_')\"
  else
    ENVFILE='${APP_PATH}/shared/.env'
    DBURL=\$(grep -E '^DATABASE_URL=' \"\$ENVFILE\" 2>/dev/null | head -n1 | cut -d= -f2-)
    case \"\$DBURL\" in
      postgres*) echo 'engine=postgres' ;;
      mysql*) echo 'engine=mysql' ;;
      *) echo 'engine=nenhum' ;;
    esac
    DBNAME=\"\${DBURL##*/}\"; DBNAME=\"\${DBNAME%%\\?*}\"; echo \"banco=\$(printf '%s' \"\$DBNAME\" | tr -dc 'A-Za-z0-9_')\"
  fi
" 2>&1)
CODE=$?
printf '%s\n' "$INFO"

if [ $CODE -ne 0 ] || printf '%s' "$INFO" | grep -q 'erro=arquivo_nao_existe'; then
  echo "ERRO: ${FILE} não existe em ${BACKUP_DIR}." >&2
  exit 2
fi
if printf '%s' "$INFO" | grep -q 'integridade=CORROMPIDO'; then
  echo "ERRO: o arquivo está corrompido. Restaurar com ele destruiria os dados atuais" >&2
  echo "sem colocar nada no lugar. Escolha outro backup." >&2
  exit 20
fi

ENGINE=$(printf '%s' "$INFO" | grep '^engine=' | cut -d= -f2)
DBNAME=$(printf '%s' "$INFO" | grep '^banco=' | cut -d= -f2)
DATA=$(printf '%s' "$INFO" | grep '^data=' | cut -d= -f2-)

echo ""
echo "############################################"
echo " RESTAURAÇÃO — OPERAÇÃO DESTRUTIVA"
echo "############################################"
echo "  servidor : ${HOST}"
echo "  arquivo  : ${FILE}"
echo "  gerado em: ${DATA}"
echo "  banco    : ${DBNAME} (${ENGINE})"
echo ""
echo "  Tudo que entrou no banco DEPOIS de ${DATA} será perdido."
echo "  A aplicação vai ficar fora do ar durante a restauração."
echo "############################################"

if [ "$CONFIRMED" -ne 1 ]; then
  echo "" >&2
  echo "RECUSADO: exige --confirmed." >&2
  exit 10
fi
if [ "$UNDERSTAND" != "$DBNAME" ]; then
  echo "" >&2
  echo "RECUSADO: para confirmar, passe --i-understand ${DBNAME}" >&2
  echo "Isso existe para evitar restauração no servidor errado." >&2
  exit 10
fi

# ── 2. restaurar, com rede de segurança ──────────────────────────────────────
REMOTE=$(cat <<REMOTE_SCRIPT
set -uo pipefail
FILE="${BACKUP_DIR}/${FILE}"
ENGINE="${ENGINE}"; DBNAME="${DBNAME}"; SERVICE="${SERVICE}"
BACKUP_DIR="${BACKUP_DIR}"; APP_PATH="${APP_PATH}"
STAMP=\$(date -u +%Y%m%d-%H%M%S)

echo "== 1. salvando o estado ATUAL antes de sobrescrever =="
SEGURANCA="\$BACKUP_DIR/pre-restore-\$STAMP.sql.gz"
case "\$ENGINE" in
  postgres) sudo -n -u postgres pg_dump "\$DBNAME" 2>/dev/null | gzip > "\$SEGURANCA" ;;
  mysql) sudo -n mysqldump --single-transaction "\$DBNAME" 2>/dev/null | gzip > "\$SEGURANCA" ;;
esac
if [ -s "\$SEGURANCA" ] && gzip -t "\$SEGURANCA" 2>/dev/null; then
  echo "rede_de_seguranca=\$(basename "\$SEGURANCA")"
else
  echo "ERRO: não consegui salvar o estado atual. Abortando por segurança."
  rm -f "\$SEGURANCA"
  exit 21
fi

echo "== 2. parando a aplicação =="
sudo -n systemctl stop "\$SERVICE" 2>/dev/null && echo "servico=parado" || echo "servico=não estava rodando"

echo "== 3. restaurando =="
case "\$ENGINE" in
  postgres)
    if gunzip -c "\$FILE" | sudo -n -u postgres psql -d "\$DBNAME" >/dev/null 2>&1; then
      echo "restauracao=ok"
    else
      echo "restauracao=FALHOU"
      echo "== revertendo para o estado anterior =="
      gunzip -c "\$SEGURANCA" | sudo -n -u postgres psql -d "\$DBNAME" >/dev/null 2>&1 \\
        && echo "revertido=sim" || echo "revertido=FALHOU — use \$(basename "\$SEGURANCA") manualmente"
      sudo -n systemctl start "\$SERVICE" 2>/dev/null
      exit 22
    fi ;;
  mysql)
    if gunzip -c "\$FILE" | sudo -n mysql "\$DBNAME" 2>/dev/null; then
      echo "restauracao=ok"
    else
      echo "restauracao=FALHOU"
      echo "== revertendo para o estado anterior =="
      gunzip -c "\$SEGURANCA" | sudo -n mysql "\$DBNAME" 2>/dev/null \\
        && echo "revertido=sim" || echo "revertido=FALHOU — use \$(basename "\$SEGURANCA") manualmente"
      sudo -n systemctl start "\$SERVICE" 2>/dev/null
      exit 22
    fi ;;
esac

echo "== 4. subindo a aplicação =="
sudo -n systemctl start "\$SERVICE" 2>/dev/null
sleep 3
sudo -n systemctl is-active "\$SERVICE" 2>/dev/null | sed 's/^/servico=/'

echo "estado_anterior_guardado=\$(basename "\$SEGURANCA")"
echo "restauracao=concluída"
REMOTE_SCRIPT
)

OUT=$(ssh "${SSH_OPTS[@]}" "$TARGET" "bash -s" <<< "$REMOTE" 2>&1)
CODE=$?
printf '%s\n' "$OUT"

if [ $CODE -ne 0 ]; then
  echo "" >&2
  echo "A restauração falhou (código ${CODE})." >&2
  echo "O estado anterior foi salvo antes de qualquer alteração; leia a saída acima" >&2
  echo "para ver se a reversão automática funcionou." >&2
  exit $CODE
fi

SEGURANCA=$(printf '%s' "$OUT" | grep '^estado_anterior_guardado=' | cut -d= -f2)
echo ""
echo "Restaurado. O estado que existia antes ficou guardado em:"
echo "  ${BACKUP_DIR}/${SEGURANCA}"
echo ""
echo "Confirme que a aplicação voltou ao normal antes de apagar esse arquivo."
echo "Diagnóstico, se precisar: /sshp-doctor"
exit 0
