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

BACKUP_ROOT=/home/admin/backups/sub2api
DATE_TAG=$(date +%Y%m%d)
TS=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="$BACKUP_ROOT/backup-date-$DATE_TAG"
PG_DIR="$BACKUP_DIR/postgres"
REDIS_DIR="$BACKUP_DIR/redis"
META_DIR="$BACKUP_DIR/meta"
LOG_FILE="$BACKUP_ROOT/backup.log"
CRON_LOG="$BACKUP_ROOT/cron.log"
TRASH_DIR=/home/admin/.trash
RETAIN_DAYS=2
PIGZ_LEVEL=3

POSTGRES_CONTAINER=postgres
POSTGRES_DB=sub2api
REDIS_CONTAINER=redis-base
REDIS_CONF=/home/admin/docker-projects/redis-base/redis.conf

FAILED_LINE="?"
PG_SNAPSHOT_ID=""
PG_SNAPSHOT_PID=""
PG_ENV_FILE=""

log() {
  printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE"
}

fail() {
  log "FATAL: $*"
  exit 1
}

move_to_trash() {
  local src="$1"
  local base
  local dest
  base=$(basename "$src")
  dest="$TRASH_DIR/${base}_$(date +%Y%m%d_%H%M%S)"

  mkdir -p "$TRASH_DIR" 2>/dev/null || true
  if mv "$src" "$dest" 2>/dev/null; then
    return 0
  fi

  if command -v sudo >/dev/null 2>&1; then
    sudo -n mkdir -p "$TRASH_DIR"
    sudo -n mv "$src" "$dest"
    return 0
  fi

  return 1
}

archive_dir_if_exists() {
  local src="$1"
  [ -d "$src" ] || return 0
  move_to_trash "$src"
}

cleanup_local_old_backups() {
  local dirs=()
  local idx
  mapfile -t dirs < <(find "$BACKUP_ROOT" -maxdepth 1 -mindepth 1 -type d -name 'backup-date-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]' | sort)
  if [ "${#dirs[@]}" -le "$RETAIN_DAYS" ]; then
    log "本地保留策略检查完成：当前 ${#dirs[@]} 天，无需归档"
    return 0
  fi

  for ((idx=0; idx<${#dirs[@]}-RETAIN_DAYS; idx++)); do
    archive_dir_if_exists "${dirs[$idx]}"
    log "本地旧备份已归档: ${dirs[$idx]}"
  done
}

finish_pg_snapshot() {
  if [ -n "$PG_SNAPSHOT_PID" ]; then
    {
      printf 'COMMIT;\n\\q\n' >&"${PG_SNAPSHOT[1]}"
    } 2>/dev/null || true
    wait "$PG_SNAPSHOT_PID" 2>/dev/null || true
    PG_SNAPSHOT_PID=""
    PG_SNAPSHOT_ID=""
  fi
}

cleanup() {
  local rc=$?
  finish_pg_snapshot
  if [ -n "$PG_ENV_FILE" ] && [ -f "$PG_ENV_FILE" ]; then
    move_to_trash "$PG_ENV_FILE" || true
  fi
  if [ "$rc" -ne 0 ]; then
    log "backup script aborted rc=$rc line=$FAILED_LINE"
  fi
  exit "$rc"
}

trap 'FAILED_LINE=$LINENO' ERR
trap cleanup EXIT

mkdir -p "$PG_DIR" "$REDIS_DIR" "$META_DIR" "$TRASH_DIR"
touch "$LOG_FILE" "$CRON_LOG"

log '===== 开始本地备份（已拆分远端同步） ====='
log "backup_dir=$BACKUP_DIR"
log "retention_days=$RETAIN_DAYS"
log "pigz_level=$PIGZ_LEVEL"

command -v docker >/dev/null 2>&1 || fail 'docker 不存在'
command -v redis-cli >/dev/null 2>&1 || fail 'redis-cli 不存在'
command -v pigz >/dev/null 2>&1 || fail 'pigz 不存在'

docker ps --format '{{.Names}}' | grep -qx "$POSTGRES_CONTAINER" || fail "PostgreSQL 容器未运行: $POSTGRES_CONTAINER"
docker ps --format '{{.Names}}' | grep -qx "$REDIS_CONTAINER" || fail "Redis 容器未运行: $REDIS_CONTAINER"

PG_ENV_FILE=$(mktemp)
docker inspect "$POSTGRES_CONTAINER" --format '{{range .Config.Env}}{{println .}}{{end}}' > "$PG_ENV_FILE"
PGUSER=$(awk -F= '/^POSTGRES_USER=/{print $2}' "$PG_ENV_FILE")
PGPASS=$(awk -F= '/^POSTGRES_PASSWORD=/{print $2}' "$PG_ENV_FILE")
[ -n "$PGUSER" ] || fail '未获取到 POSTGRES_USER'
[ -n "$PGPASS" ] || fail '未获取到 POSTGRES_PASSWORD'

REDIS_PW=$(awk '/^requirepass /{print $2}' "$REDIS_CONF")
[ -n "$REDIS_PW" ] || fail '未获取到 Redis requirepass'

log "正在备份 PostgreSQL: db=$POSTGRES_DB (consistent snapshot)"
coproc PG_SNAPSHOT { docker exec -i -e PGPASSWORD="$PGPASS" "$POSTGRES_CONTAINER" psql -X -qAt -U "$PGUSER" -d "$POSTGRES_DB"; }
printf 'BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY;\nSELECT pg_export_snapshot();\n' >&"${PG_SNAPSHOT[1]}"
IFS= read -r PG_SNAPSHOT_ID <&"${PG_SNAPSHOT[0]}" || fail '获取 PostgreSQL snapshot 失败'
[ -n "$PG_SNAPSHOT_ID" ] || fail 'PostgreSQL snapshot 为空'

PG_FILE="$PG_DIR/sub2api_${TS}.sql.gz"
docker exec -e PGPASSWORD="$PGPASS" "$POSTGRES_CONTAINER" pg_dump --snapshot="$PG_SNAPSHOT_ID" -U "$PGUSER" -d "$POSTGRES_DB" | pigz -${PIGZ_LEVEL} > "$PG_FILE"
pigz -t "$PG_FILE"
log "PostgreSQL 备份成功 ($(du -h "$PG_FILE" | awk '{print $1}'))"

PG_COUNTS_RAW=$(docker exec -i -e PGPASSWORD="$PGPASS" "$POSTGRES_CONTAINER" psql -X -qAt -U "$PGUSER" -d "$POSTGRES_DB" <<SQL
BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY;
SET TRANSACTION SNAPSHOT '$PG_SNAPSHOT_ID';
select current_database() || '|' || current_user;
select 'users|' || count(*) from public.users;
select 'user_subscriptions|' || count(*) from public.user_subscriptions;
select 'usage_logs|' || count(*) from public.usage_logs;
select 'billing_usage_entries|' || count(*) from public.billing_usage_entries;
select 'settings|' || count(*) from public.settings;
COMMIT;
SQL
)
finish_pg_snapshot
PG_COUNTS=$(printf '%s\n' "$PG_COUNTS_RAW" | sed '/^BEGIN$/d;/^SET$/d;/^COMMIT$/d;/^$/d')

REDIS_FILE="$REDIS_DIR/dump_${TS}.rdb"
log '正在备份 Redis'
redis-cli -a "$REDIS_PW" --rdb "$REDIS_FILE" >/dev/null
[ -s "$REDIS_FILE" ] || fail 'Redis RDB 文件为空'
log "Redis 备份成功 ($(du -h "$REDIS_FILE" | awk '{print $1}'))"

MYSQL_STATUS_FILE="$META_DIR/mysql-status_${TS}.txt"
{
  echo 'mysql_backup=skipped'
  echo 'reason=no_live_mysql_on_80'
  echo 'note=之前 all-databases_*.sql.gz 实际是 mysqldump usage 文本，不能再伪造成功'
} > "$MYSQL_STATUS_FILE"
log 'MySQL 已跳过：80 当前没有真实可备份的本机 MySQL 实例，避免继续产出假备份'

SUMMARY_FILE="$META_DIR/summary_${TS}.txt"
{
  echo "timestamp=$TS"
  echo "backup_dir=$BACKUP_DIR"
  echo "postgres_file=$(basename "$PG_FILE")"
  echo "redis_file=$(basename "$REDIS_FILE")"
  echo "mysql_status=$(basename "$MYSQL_STATUS_FILE")"
  echo "postgres_db=$POSTGRES_DB"
  echo 'postgres_counts_begin'
  printf '%s\n' "$PG_COUNTS"
  echo 'postgres_counts_end'
  echo 'redis_verify_status=deferred_to_daily_verify'
} > "$SUMMARY_FILE"

cleanup_local_old_backups

log '===== 本地备份完成 ====='
log "本地备份位置: $BACKUP_DIR"
