#!/usr/bin/env bash
set -Eeuo pipefail

# Backup completo PowerUp:
# - codice e configurazioni locali di /var/www/html
# - dump SQL dei database Postgres Docker
# - dati persistenti condivisi in backend_hub_state_data
#
# Uso:
#   /var/www/html/backup
#
# Variabili opzionali:
#   APP_DIR=/var/www/html
#   BACKUP_ROOT=/var/www/powerup-backups
#   RETENTION_DAYS=0        # 0 = non cancellare backup vecchi

APP_DIR="${APP_DIR:-/var/www/html}"
BACKUP_ROOT="${BACKUP_ROOT:-/var/www/powerup-backups}"
RETENTION_DAYS="${RETENTION_DAYS:-0}"
APP_PARENT="$(dirname "${APP_DIR}")"
APP_BASENAME="$(basename "${APP_DIR}")"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
FINAL_DIR="${BACKUP_ROOT}/${STAMP}"
TMP_DIR=""

umask 077

log() {
  printf '[%s] %s\n' "$(date -u +%H:%M:%S)" "$*"
}

fail() {
  printf 'ERRORE: %s\n' "$*" >&2
  exit 1
}

cleanup() {
  local status=$?
  if [[ $status -ne 0 && -n "${TMP_DIR}" && -d "${TMP_DIR}" ]]; then
    rm -rf "${TMP_DIR}"
  fi
}
trap cleanup EXIT

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || fail "comando mancante: $1"
}

require_running_service() {
  local service="$1"
  local cid
  cid="$(docker compose ps -q "${service}")"
  [[ -n "${cid}" ]] || fail "servizio Docker non trovato: ${service}"
  [[ "$(docker inspect -f '{{.State.Running}}' "${cid}")" == "true" ]] || fail "servizio Docker non avviato: ${service}"
}

dump_postgres() {
  local service="$1"
  local output="$2"

  require_running_service "${service}"
  log "Dump SQL ${service}"
  docker compose exec -T "${service}" sh -ec '
    : "${POSTGRES_USER:?POSTGRES_USER mancante}"
    : "${POSTGRES_DB:?POSTGRES_DB mancante}"
    pg_dump \
      --username "$POSTGRES_USER" \
      --dbname "$POSTGRES_DB" \
      --clean \
      --if-exists \
      --no-owner \
      --no-privileges
  ' | gzip -9 > "${output}"

  [[ -s "${output}" ]] || fail "dump vuoto: ${output}"
  gzip -t "${output}"
}

backup_backend_state() {
  local output="$1"

  require_running_service "backend-hub"
  log "Backup volume backend_hub_state_data"
  docker compose exec -T backend-hub sh -ec '
    cd /data
    tar -czf - .
  ' > "${output}"

  [[ -s "${output}" ]] || fail "backup volume vuoto: ${output}"
  tar -tzf "${output}" >/dev/null
}

write_restore_notes() {
  cat > "${TMP_DIR}/RESTORE.txt" <<'EOF'
Ripristino backup PowerUp
=========================

ATTENZIONE: i file di backup possono contenere .env, token API, segreti WhatsApp e dati clienti.
Tieni questa cartella privata e non caricarla su GitHub.

1. Ripristino codice
--------------------
cd /var/www
mv html "html_broken_$(date -u +%Y%m%dT%H%M%SZ)"
tar -xzf /var/www/powerup-backups/NOME_BACKUP/app-files.tar.gz -C /var/www
cd /var/www/html

2. Riavvio container
--------------------
docker compose up -d --build

3. Ripristino DB Prenotazioni
-----------------------------
cd /var/www/html
gzip -dc /var/www/powerup-backups/NOME_BACKUP/prenotazioni-db.sql.gz | \
  docker compose exec -T prenotazioni-db sh -c 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'

4. Ripristino DB Ordini
-----------------------
cd /var/www/html
gzip -dc /var/www/powerup-backups/NOME_BACKUP/ordini-db.sql.gz | \
  docker compose exec -T ordini-db sh -c 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'

5. Ripristino dati backend hub
------------------------------
cd /var/www/html
gzip -dc /var/www/powerup-backups/NOME_BACKUP/backend-hub-state-data.tar.gz | \
  docker compose exec -T backend-hub sh -c 'cd /data && tar -xpf -'

6. Controllo finale
-------------------
docker compose ps
EOF
}

require_cmd docker
require_cmd gzip
require_cmd tar
require_cmd sha256sum

[[ -d "${APP_DIR}" ]] || fail "APP_DIR non esiste: ${APP_DIR}"
[[ -f "${APP_DIR}/docker-compose.yml" ]] || fail "docker-compose.yml non trovato in ${APP_DIR}"

mkdir -p "${BACKUP_ROOT}"
[[ ! -e "${FINAL_DIR}" ]] || fail "backup gia' esistente: ${FINAL_DIR}"
TMP_DIR="$(mktemp -d "${BACKUP_ROOT}/.${STAMP}.tmp.XXXXXX")"

cd "${APP_DIR}"
docker compose config --quiet

log "Creo backup in ${FINAL_DIR}"

log "Archivio codice e configurazioni"
tar \
  --one-file-system \
  --exclude="${APP_BASENAME}/.git" \
  --exclude="${APP_BASENAME}/.git/*" \
  --exclude='*/node_modules' \
  --exclude='*/node_modules/*' \
  --exclude='*/.next' \
  --exclude='*/.next/*' \
  --exclude='*/dist' \
  --exclude='*/dist/*' \
  --exclude='*/build' \
  --exclude='*/build/*' \
  --exclude='*/__pycache__' \
  --exclude='*/__pycache__/*' \
  --exclude='*/.pytest_cache' \
  --exclude='*/.pytest_cache/*' \
  -czf "${TMP_DIR}/app-files.tar.gz" \
  -C "${APP_PARENT}" \
  "${APP_BASENAME}"
tar -tzf "${TMP_DIR}/app-files.tar.gz" >/dev/null

dump_postgres "prenotazioni-db" "${TMP_DIR}/prenotazioni-db.sql.gz"
dump_postgres "ordini-db" "${TMP_DIR}/ordini-db.sql.gz"
backup_backend_state "${TMP_DIR}/backend-hub-state-data.tar.gz"

log "Scrivo metadati"
{
  printf 'backup_created_utc=%s\n' "${STAMP}"
  printf 'hostname=%s\n' "$(hostname)"
  printf 'app_dir=%s\n' "${APP_DIR}"
  printf 'backup_root=%s\n' "${BACKUP_ROOT}"
  printf 'git_commit=%s\n' "$(git rev-parse HEAD 2>/dev/null || printf 'not-a-git-repo')"
  printf '\n[docker compose ps]\n'
  docker compose ps
  printf '\n[git status]\n'
  git status --short 2>/dev/null || true
} > "${TMP_DIR}/manifest.txt"
docker compose config > "${TMP_DIR}/docker-compose.resolved.yml"
write_restore_notes

log "Calcolo checksum"
(
  cd "${TMP_DIR}"
  find . -maxdepth 1 -type f ! -name SHA256SUMS -print0 | sort -z | xargs -0 sha256sum > SHA256SUMS
)

mv "${TMP_DIR}" "${FINAL_DIR}"
TMP_DIR=""

if [[ "${RETENTION_DAYS}" =~ ^[0-9]+$ && "${RETENTION_DAYS}" -gt 0 ]]; then
  log "Pulizia backup piu' vecchi di ${RETENTION_DAYS} giorni"
  find "${BACKUP_ROOT}" -mindepth 1 -maxdepth 1 -type d -name '20*' -mtime +"${RETENTION_DAYS}" -exec rm -rf {} +
fi

log "Backup completato: ${FINAL_DIR}"
du -sh "${FINAL_DIR}"
