#!/usr/bin/env bash
# 02-backup — banco e storage ANTES de qualquer ação de código/build/down.
#
# Ordem: retenção do WAL (barata/best-effort) → pg_dump validado → snapshot do RustFS
# (parando só o storage) → snapshot do storage Laravel → mídia offsite (quando há bucket).
# O dump é validado com `pg_restore --list`; se o backup local falhar, a fase aborta e o
# update não começa. O storage é parado apenas durante o tar e religado sempre.
set -Eeuo pipefail
# shellcheck source=../lib/common.sh
. "$(dirname "$0")/../lib/common.sh"

phase_begin "02-backup"

project_resolve
if [ "${#STACK_COMPOSE[@]}" -gt 0 ]; then
    COMPOSE=("${STACK_COMPOSE[@]}")
fi

TS="$(date -u +%Y%m%dT%H%M%SZ)"
DB_FILE="$BACKUP_DIR/db-${TS}.dump"
RUSTFS_FILE="$BACKUP_DIR/rustfs-${TS}.tar.gz"
STORAGE_FILE="$BACKUP_DIR/storage-${TS}.tar.gz"

storage_local=0
case "$AWS_ENDPOINT_V" in
    *storage:9000* | *//localhost* | *127.0.0.1*) storage_local=1 ;;
esac

log "stack: ${STACK_PROJECT} · destino: ${BACKUP_DIR}"

if [ "$DRY_RUN" = 1 ]; then
    log "[dry-run] tools/wal-retention.sh --json (best-effort, antes do dump)"
    log "[dry-run] pg_dump -Fc → ${DB_FILE} (validado com pg_restore --list)"
    log "[dry-run] docker compose stop storage; tar → ${RUSTFS_FILE}; start storage"
    log "[dry-run] tar de ${STORAGE_ABS} (sem backups/caches/logs) → ${STORAGE_FILE}"
    if [ -n "$OFFSITE_MEDIA_BUCKET_V" ]; then
        log "[dry-run] php artisan media:backup (offsite: ${OFFSITE_MEDIA_BUCKET_V})"
    else
        log "[dry-run] sem OFFSITE_MEDIA_BUCKET: só o snapshot local do RustFS"
    fi
    log "[dry-run] retenção: manter ${BACKUP_KEEP} de cada tipo"
    phase_end
    exit 0
fi

# ------------------------------------------------- 0) WAL (não aborta o update)
if ! COMPOSE_PROJECT_NAME="$STACK_PROJECT" CONTAINER_RUNTIME=docker \
    bash "$ROOT/tools/wal-retention.sh" --json >>"${LOG_FILE:-/dev/null}" 2>&1; then
    warn "retenção do WAL falhou (não aborta; o pg_dump abaixo é o backup que importa)"
fi

ensure_dir "$BACKUP_DIR"

# ------------------------------------------------------------------ 1) banco
db_tmp="${DB_FILE}.partial"
if ! "${COMPOSE[@]}" exec -T postgres sh -c \
    'PGPASSWORD="$POSTGRES_PASSWORD" pg_dump -Fc --no-owner --no-acl -U "$POSTGRES_USER" -d "$POSTGRES_DB"' >"$db_tmp"; then
    rm -f "$db_tmp"
    die "pg_dump falhou — atualização abortada (sem backup não há mudança)"
fi
if [ ! -s "$db_tmp" ]; then
    rm -f "$db_tmp"
    die "dump do banco vazio"
fi
if ! "${COMPOSE[@]}" exec -T postgres pg_restore --list <"$db_tmp" >/dev/null; then
    rm -f "$db_tmp"
    die "dump ilegível (pg_restore --list falhou)"
fi
mv -f "$db_tmp" "$DB_FILE"
chmod 600 "$DB_FILE"
db_sha="$(sha256sum "$DB_FILE" | cut -d' ' -f1)"
printf '%s  %s\n' "$db_sha" "$(basename "$DB_FILE")" >"${DB_FILE}.sha256"
chmod 600 "${DB_FILE}.sha256"
state_set BACKUP_DB "$DB_FILE"
state_set BACKUP_SHA "$db_sha"
ok "banco: ${DB_FILE} ($(wc -c <"$DB_FILE" | tr -d ' ') bytes, sha256 ${db_sha:0:12}…)"

if [ "$(env_value BACKUP_OFFSITE 0)" = 1 ]; then
    offsite_disk="$(env_value BACKUP_DISK s3)"
    if [ "$offsite_disk" = "s3" ] && [ "$storage_local" = 1 ]; then
        warn "BACKUP_OFFSITE=1 com BACKUP_DISK=s3 aponta para o RustFS local — não é cópia fora do host (G-15)."
    fi
    log "cópia do dump para o disco '${offsite_disk}' (BACKUP_OFFSITE=1)"
    if ! "${COMPOSE[@]}" exec -T php-fpm php artisan core:backup:push "backups/$(basename "$DB_FILE")" --disk="$offsite_disk"; then
        warn "push do dump para '${offsite_disk}' falhou; o dump local está íntegro"
    fi
    "${COMPOSE[@]}" exec -T php-fpm php artisan core:backup:prune --keep="$BACKUP_KEEP" --disk="$offsite_disk" >/dev/null 2>&1 ||
        warn "prune remoto falhou (não aborta; o dump local está íntegro)"
fi

# ------------------------------------------------- 2) snapshot local do RustFS
storage_was_running=0
case "$(service_health storage)" in
    healthy | running) storage_was_running=1 ;;
esac

restart_storage() {
    if [ "$storage_was_running" = 1 ]; then
        "${COMPOSE[@]}" start storage >/dev/null
        wait_healthy storage 180
    fi
}

if [ "$storage_was_running" = 1 ]; then
    "${COMPOSE[@]}" stop storage
fi

rustfs_tmp="${RUSTFS_FILE}.partial"
if ! tar -C "$DATA_ABS" -czf "$rustfs_tmp" rustfs; then
    rm -f "$rustfs_tmp"
    restart_storage
    die "tar do RustFS falhou — atualização abortada"
fi
if ! tar -tzf "$rustfs_tmp" >/dev/null; then
    rm -f "$rustfs_tmp"
    restart_storage
    die "tar do RustFS ilegível — atualização abortada"
fi
mv -f "$rustfs_tmp" "$RUSTFS_FILE"
chmod 600 "$RUSTFS_FILE"
rustfs_sha="$(sha256sum "$RUSTFS_FILE" | cut -d' ' -f1)"
printf '%s  %s\n' "$rustfs_sha" "$(basename "$RUSTFS_FILE")" >"${RUSTFS_FILE}.sha256"
chmod 600 "${RUSTFS_FILE}.sha256"
state_set BACKUP_STORAGE "$RUSTFS_FILE"
ok "storage (RustFS): ${RUSTFS_FILE} ($(wc -c <"$RUSTFS_FILE" | tr -d ' ') bytes)"

restart_storage

# ------------------------------------- 3) snapshot do storage Laravel (app/cache)
storage_tmp="${STORAGE_FILE}.partial"
if ! tar -C "$STORAGE_ABS" -czf "$storage_tmp" \
    --exclude=backups \
    --exclude='framework/cache' \
    --exclude='framework/sessions' \
    --exclude='framework/views' \
    --exclude='framework/php-env' \
    --exclude='framework/vendor.*' \
    --exclude=logs \
    .; then
    rm -f "$storage_tmp"
    die "tar do storage Laravel falhou — atualização abortada"
fi
if ! tar -tzf "$storage_tmp" >/dev/null; then
    rm -f "$storage_tmp"
    die "tar do storage Laravel ilegível — atualização abortada"
fi
mv -f "$storage_tmp" "$STORAGE_FILE"
chmod 600 "$STORAGE_FILE"
storage_sha="$(sha256sum "$STORAGE_FILE" | cut -d' ' -f1)"
printf '%s  %s\n' "$storage_sha" "$(basename "$STORAGE_FILE")" >"${STORAGE_FILE}.sha256"
chmod 600 "${STORAGE_FILE}.sha256"
state_set BACKUP_STORAGE_APP "$STORAGE_FILE"
ok "storage Laravel: ${STORAGE_FILE} ($(wc -c <"$STORAGE_FILE" | tr -d ' ') bytes)"

# ------------------------------------------------- 4) mídia offsite (adicional)
if [ -n "$OFFSITE_MEDIA_BUCKET_V" ]; then
    log "media:backup → offsite (${OFFSITE_MEDIA_BUCKET_V})"
    if ! "${COMPOSE[@]}" exec -T php-fpm php artisan media:backup --no-interaction; then
        if [ "$storage_local" = 1 ]; then
            warn "media:backup falhou; o snapshot local do RustFS (acima) é o backup do storage."
        else
            die "media:backup falhou e o storage é externo — atualização abortada (sem backup do storage)"
        fi
    else
        ok "mídia copiada para o bucket offsite"
    fi
else
    if [ "$storage_local" = 1 ]; then
        log "sem OFFSITE_MEDIA_BUCKET: o snapshot local do RustFS é o backup do storage"
    else
        die "storage externo sem OFFSITE_MEDIA_BUCKET — atualização abortada (sem backup do storage)"
    fi
fi

# ------------------------------------------------------------------ 5) retenção
prune_backups() {
    local pattern="$1" old
    ls -1 "$BACKUP_DIR" 2>/dev/null | grep -E "$pattern" | sort -r | tail -n +"$((BACKUP_KEEP + 1))" |
        while IFS= read -r old; do
            rm -f "$BACKUP_DIR/$old" "$BACKUP_DIR/$old.sha256"
            log "backup antigo removido: $old"
        done || true
}

prune_backups '^db-[0-9TZ]+\.dump$'
prune_backups '^rustfs-[0-9TZ]+\.tar\.gz$'
prune_backups '^storage-[0-9TZ]+\.tar\.gz$'

phase_end
