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

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=/dev/null
source "$SCRIPT_DIR/libenv.sh"

log() {
  local msg="[limristem-mail-backup] $*"
  printf '%s\n' "$msg"
  printf '[%s] %s\n' "$(date -Iseconds)" "$msg" >> /var/log/limristem-mail-backup.log
}

fail_with_error() {
  local message=$1
  trap - ERR
  log "ERROR: ${message}"
  exit 1
}

handle_error() {
  local exit_code=$?
  local line_no=$1
  printf '[limristem-mail-backup] ERROR: backup failed unexpectedly on line %s.\n' "$line_no" >&2
  exit "$exit_code"
}

trap 'handle_error $LINENO' ERR

require_writable_directory() {
  local dir_path=$1
  local label=$2
  if limristem_mail_path_is_writable "$dir_path/.limristem-mail-backup-write-test"; then
    return 0
  fi
  fail_with_error "${label} is not writable in the current environment: ${dir_path}"
}

STATE_DIR=$(limristem_mail_resolve_managed_config_dir)
SCHEDULES_FILE=$STATE_DIR/backup-schedules.json
STORAGES_FILE=$STATE_DIR/backup-storages.json

require_root() {
  if [[ $EUID -ne 0 ]]; then
    fail_with_error "Questo script va eseguito come root."
  fi
}

load_env() {
  limristem_mail_load_env_file "$(limristem_mail_resolve_main_env_file)"
  limristem_mail_load_env_file "$(limristem_mail_resolve_backup_env_file)"
  local rclone_config
  rclone_config=$(limristem_mail_resolve_rclone_config_file)
  if [[ -f "$rclone_config" ]]; then
    export RCLONE_CONFIG="$rclone_config"
  fi
}

load_schedule_overrides() {
  local schedule_id=$1
  if [[ ! -f "$SCHEDULES_FILE" ]]; then
    return 0
  fi
  python3 - "$SCHEDULES_FILE" "$STORAGES_FILE" "$schedule_id" <<'PY'
import json
import pathlib
import shlex
import sys

def normalize_choice(value, default="", mode="raw"):
    candidate = str(value if value is not None else default).strip()
    if len(candidate) >= 2 and candidate[0] == candidate[-1] and candidate[0] in {'"', "'"}:
        candidate = candidate[1:-1].strip()
    if not candidate:
        candidate = default
    if mode == "lower":
        return candidate.lower()
    if mode == "title":
        return candidate.title()
    return candidate

schedules = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8") or "[]")
storages = json.loads(pathlib.Path(sys.argv[2]).read_text(encoding="utf-8") or "[]") if pathlib.Path(sys.argv[2]).exists() else []
schedule_id = sys.argv[3]
schedule = next((item for item in schedules if item.get("id") == schedule_id), None)
if schedule is None and schedule_id:
    raise SystemExit(f"Unknown backup schedule: {schedule_id}")
if schedule is None:
    schedule = next((item for item in schedules if item.get("enabled", "yes") == "yes"), schedules[0] if schedules else None)
if schedule is None:
    raise SystemExit(0)
storage = next((item for item in storages if item.get("id") == schedule.get("storage_id")), {})
backup_type = normalize_choice(schedule.get("backup_type", "auto"), "auto", mode="lower")
full_weekday = normalize_choice(schedule.get("full_weekday", "Sun"), "Sun", mode="title")
compression = normalize_choice(schedule.get("compression", "gzip"), "gzip", mode="lower")
include_redis = normalize_choice(schedule.get("include_redis", "yes"), "yes", mode="lower")
include_database = normalize_choice(schedule.get("include_database", "yes"), "yes", mode="lower")
exports = {
    "LIMRISTEM_MAIL_BACKUP_SCHEDULE_ID": schedule.get("id", ""),
    "LIMRISTEM_MAIL_BACKUP_TYPE": backup_type,
    "LIMRISTEM_MAIL_BACKUP_FULL_WEEKDAY": full_weekday,
    "LIMRISTEM_MAIL_BACKUP_COMPRESSION": compression,
    "LIMRISTEM_MAIL_BACKUP_INCLUDE_REDIS": include_redis,
    "LIMRISTEM_MAIL_BACKUP_DB_MODE": "logical" if include_database == "yes" else "none",
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_TYPE": storage.get("type", "local"),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_REMOTE_NAME": storage.get("remote_name", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PATH": storage.get("path", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_HOST": storage.get("host", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PORT": storage.get("port", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_USER": storage.get("user", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PASSWORD": storage.get("password", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_BUCKET": storage.get("bucket", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_REGION": storage.get("region", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_ENDPOINT": storage.get("endpoint", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_ACCESS_KEY_ID": storage.get("access_key_id", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_SECRET_ACCESS_KEY": storage.get("secret_access_key", ""),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_ENCRYPT": storage.get("encrypt", "no"),
    "LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PUBLIC_KEY_PATH": storage.get("public_key_path", ""),
}
for key, value in exports.items():
    print(f"{key}={shlex.quote(str(value))}")
PY
}

obscure_rclone_password() {
  local secret=${1-}
  if [[ -z "$secret" ]]; then
    printf '\n'
    return 0
  fi
  if command -v rclone >/dev/null 2>&1; then
    rclone obscure "$secret"
  else
    printf '%s\n' "$secret"
  fi
}

configure_selected_storage() {
  local storage_type=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_TYPE:-local}
  local remote_name=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_REMOTE_NAME:-limristem-mail-backup}
  local remote_path=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PATH:-}
  local host=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_HOST:-}
  local port=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PORT:-}
  local user=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_USER:-}
  local password=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PASSWORD:-}
  local bucket=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_BUCKET:-}
  local region=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_REGION:-}
  local endpoint=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_ENDPOINT:-}
  local access_key=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_ACCESS_KEY_ID:-}
  local secret_key=${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_SECRET_ACCESS_KEY:-}
  mkdir -p "$(dirname "$RCLONE_CONFIG")"

  case "$storage_type" in
    local|'')
      export LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS=
      rm -f "$RCLONE_CONFIG"
      return 0
      ;;
    s3)
      export LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS="${remote_name}:${bucket}"
      if [[ -n "$remote_path" ]]; then
        LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS="${LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS}/$(printf '%s' "$remote_path" | sed 's#^/*##')"
        export LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS
      fi
      local s3_provider="AWS"
      if [[ -n "$endpoint" && "$endpoint" != *"amazonaws.com"* ]]; then
        s3_provider="Other"
      fi
      cat > "$RCLONE_CONFIG" <<EOF
[$remote_name]
type = s3
provider = $s3_provider
env_auth = false
access_key_id = $access_key
secret_access_key = $secret_key
EOF
      [[ -n "$region" ]] && printf 'region = %s\n' "$region" >> "$RCLONE_CONFIG"
      [[ -n "$endpoint" ]] && printf 'endpoint = %s\n' "$endpoint" >> "$RCLONE_CONFIG"
      ;;
    sftp)
      LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS="${remote_name}:$( [[ -n "$remote_path" ]] && printf '%s' "$remote_path" || printf '/' )"
      export LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS
      cat > "$RCLONE_CONFIG" <<EOF
[$remote_name]
type = sftp
host = $host
user = $user
EOF
      [[ -n "$port" ]] && printf 'port = %s\n' "$port" >> "$RCLONE_CONFIG"
      [[ -n "$password" ]] && printf 'pass = %s\n' "$(obscure_rclone_password "$password")" >> "$RCLONE_CONFIG"
      ;;
    ftp|ftps)
      LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS="${remote_name}:$( [[ -n "$remote_path" ]] && printf '%s' "$remote_path" || printf '/' )"
      export LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS
      cat > "$RCLONE_CONFIG" <<EOF
[$remote_name]
type = ftp
host = $host
user = $user
EOF
      [[ -n "$port" ]] && printf 'port = %s\n' "$port" >> "$RCLONE_CONFIG"
      [[ -n "$password" ]] && printf 'pass = %s\n' "$(obscure_rclone_password "$password")" >> "$RCLONE_CONFIG"
      [[ "$storage_type" == "ftps" ]] && printf 'tls = true\n' >> "$RCLONE_CONFIG"
      ;;
  esac
  chmod 600 "$RCLONE_CONFIG" 2>/dev/null || true
}

bool_is_true() {
  case "${1:-}" in
    1|true|TRUE|yes|YES|on|ON) return 0 ;;
    *) return 1 ;;
  esac
}

choose_backup_type() {
  case "${LIMRISTEM_MAIL_BACKUP_TYPE:-auto}" in
    full|incremental) CHOSEN_BACKUP_TYPE=${LIMRISTEM_MAIL_BACKUP_TYPE} ;;
    auto)
      if [[ "$(date +%a)" == "${LIMRISTEM_MAIL_BACKUP_FULL_WEEKDAY:-Sun}" ]]; then
        CHOSEN_BACKUP_TYPE=full
      else
        CHOSEN_BACKUP_TYPE=incremental
      fi
      ;;
    *)
      fail_with_error "LIMRISTEM_MAIL_BACKUP_TYPE non valido: ${LIMRISTEM_MAIL_BACKUP_TYPE:-}"
      ;;
  esac
}

compression_extension() {
  case "${LIMRISTEM_MAIL_BACKUP_COMPRESSION:-gzip}" in
    gzip) COMPRESSION_EXTENSION=.gz ;;
    none) COMPRESSION_EXTENSION= ;;
    *)
      fail_with_error "LIMRISTEM_MAIL_BACKUP_COMPRESSION non valido: ${LIMRISTEM_MAIL_BACKUP_COMPRESSION:-}"
      ;;
  esac
}

tar_create() {
  local archive=$1
  shift
  case "${LIMRISTEM_MAIL_BACKUP_COMPRESSION:-gzip}" in
    gzip) tar --create --gzip --file="$archive" "$@" ;;
    none) tar --create --file="$archive" "$@" ;;
  esac
}

mysql_dump_cmd() {
  if command -v mariadb-dump >/dev/null 2>&1; then
    printf 'mariadb-dump\n'
  else
    printf 'mysqldump\n'
  fi
}

mysql_client_cmd() {
  if command -v mariadb >/dev/null 2>&1; then
    printf 'mariadb\n'
  else
    printf 'mysql\n'
  fi
}

perform_logical_backup() {
  local target_file=$1
  local dump_cmd
  dump_cmd=$(mysql_dump_cmd)
  log "Eseguo dump logico MariaDB"
  MYSQL_PWD="${LIMRISTEM_MAIL_DB_PASS}" "$dump_cmd" \
    --single-transaction \
    --routines \
    --triggers \
    --skip-events \
    -h "${LIMRISTEM_MAIL_DB_HOST}" \
    -P "${LIMRISTEM_MAIL_DB_PORT}" \
    -u"${LIMRISTEM_MAIL_DB_USER}" \
    "${LIMRISTEM_MAIL_DB_NAME}" > "$target_file"
}

perform_physical_backup() {
  local mode=$1
  local target_dir=$2
  local state_dir=$3
  local basedir=''
  local args=(--backup "--target-dir=${target_dir}" --user=root)

  if [[ "$mode" == 'physical-incremental' ]]; then
    if [[ -f "${state_dir}/last_physical_dir" ]]; then
      basedir=$(<"${state_dir}/last_physical_dir")
    fi
    if [[ -z "$basedir" || ! -d "$basedir" ]]; then
      log "Nessun physical backup precedente disponibile: eseguo full."
      mode='physical-full'
    else
      args+=("--incremental-basedir=${basedir}")
    fi
  fi

  log "Eseguo backup MariaDB ${mode}"
  MYSQL_PWD="${LIMRISTEM_MAIL_BACKUP_DB_ROOT_PASSWORD:-}" mariabackup "${args[@]}"
  printf '%s\n' "$target_dir" > "${state_dir}/last_physical_dir"
}

perform_redis_backup() {
  local target_file=$1
  if ! bool_is_true "${LIMRISTEM_MAIL_BACKUP_INCLUDE_REDIS:-yes}"; then
    return 0
  fi
  if ! command -v redis-cli >/dev/null 2>&1; then
    log "redis-cli non disponibile: salto backup Redis."
    return 0
  fi
  log "Eseguo snapshot Redis"
  REDISCLI_AUTH="${LIMRISTEM_MAIL_REDIS_PASSWORD:-}" \
    redis-cli -h "${LIMRISTEM_MAIL_REDIS_HOST}" -p "${LIMRISTEM_MAIL_REDIS_PORT}" --rdb "$target_file" >/dev/null
}

write_manifest() {
  local backup_dir=$1
  (
    cd "$backup_dir"
    find . -type f -print0 | sort -z | xargs -0 sha256sum > SHA256SUMS
  )
}

copy_to_remote_targets() {
  local backup_dir=$1
  local payload_dir=$backup_dir
  local backup_name
  backup_name=$(basename "$backup_dir")

  if [[ -z "${LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS:-}" ]]; then
    return 0
  fi
  if ! command -v rclone >/dev/null 2>&1; then
    fail_with_error "rclone non disponibile ma LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS è valorizzato."
  fi

  if [[ "${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_ENCRYPT:-no}" == "yes" && -f "${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PUBLIC_KEY_PATH:-}" ]]; then
    local bundle_file export_dir key_material
    export_dir="${backup_dir}/.encrypted-export"
    bundle_file="${export_dir}/${backup_name}.tar.gz"
    mkdir -p "$export_dir"
    tar --create --gzip --preserve-permissions --file="$bundle_file" -C "$(dirname "$backup_dir")" "$backup_name"
    key_material=$(openssl rand -hex 32)
    openssl enc -aes-256-cbc -pbkdf2 -salt -in "$bundle_file" -out "${bundle_file}.enc" -pass file:<(printf '%s' "$key_material")
    openssl pkeyutl -encrypt -pubin -inkey "${LIMRISTEM_MAIL_BACKUP_SELECTED_STORAGE_PUBLIC_KEY_PATH}" -in <(printf '%s' "$key_material") -out "${bundle_file}.key.enc"
    rm -f "$bundle_file"
    payload_dir=$export_dir
  fi

  for target in ${LIMRISTEM_MAIL_BACKUP_REMOTE_TARGETS}; do
    log "Copio backup verso ${target}"
    rclone copy "$payload_dir" "${target%/}/${backup_name}"
  done
}

apply_retention() {
  local local_dir=$1
  local retention_days=${LIMRISTEM_MAIL_BACKUP_RETENTION_DAYS:-14}
  log "Applico retention locale > ${retention_days} giorni"
  find "$local_dir" -mindepth 1 -maxdepth 1 -type d -mtime +"${retention_days}" -print0 | xargs -0r rm -rf
}

main() {
  require_root
  load_env
  local requested_schedule_id=
  if [[ ${1:-} == "--schedule-id" ]]; then
    requested_schedule_id=${2:-}
    shift 2
  fi
  local rclone_config
  rclone_config=$(limristem_mail_resolve_rclone_config_file)
  export RCLONE_CONFIG="$rclone_config"
  if [[ -n "$requested_schedule_id" || -f "$SCHEDULES_FILE" ]]; then
    eval "$(load_schedule_overrides "$requested_schedule_id")"
    configure_selected_storage
  fi

  local backup_type db_mode timestamp backup_root state_dir extension fs_archive db_dump redis_file meta_file
  choose_backup_type
  backup_type=$CHOSEN_BACKUP_TYPE
  db_mode=${LIMRISTEM_MAIL_BACKUP_DB_MODE:-logical}
  timestamp=$(date -u +%Y%m%dT%H%M%SZ)
  local backup_local_dir=${LIMRISTEM_MAIL_BACKUP_LOCAL_DIR:-/var/backups/limristem-mail}
  backup_root="${backup_local_dir}/${timestamp}-${backup_type}"
  state_dir=/var/lib/limristem-mail/backup-state
  compression_extension
  extension=$COMPRESSION_EXTENSION

  require_writable_directory "$backup_local_dir" "Backup destination"
  require_writable_directory "$state_dir" "Backup state directory"

  mkdir -p "$backup_root" "$state_dir" "$backup_local_dir"

  meta_file="${backup_root}/metadata.env"
  {
    printf 'LIMRISTEM_MAIL_BACKUP_TIMESTAMP=%s\n' "$timestamp"
    printf 'LIMRISTEM_MAIL_BACKUP_TYPE=%s\n' "$backup_type"
    printf 'LIMRISTEM_MAIL_BACKUP_DB_MODE=%s\n' "$db_mode"
    printf 'LIMRISTEM_MAIL_BACKUP_HOSTNAME=%s\n' "${LIMRISTEM_MAIL_HOSTNAME:-unknown}"
  } > "$meta_file"

  fs_archive="${backup_root}/filesystem.${backup_type}.tar${extension}"
  db_dump="${backup_root}/mariadb.logical.sql"
  redis_file="${backup_root}/redis.rdb"

  local -a backup_paths=()
  local -a candidate_paths=(
    "${LIMRISTEM_MAIL_MAIL_HOME:-/var/mail/vhosts}"
    "${LIMRISTEM_MAIL_DKIM_KEYS_DIR:-/var/lib/limristem-mail/dkim}"
    "${LIMRISTEM_MAIL_BASE_DIR:-}"
    /etc/postfix
    /etc/dovecot
    /etc/rspamd
    /etc/nginx
    /etc/default/postsrsd
    /etc/limristem-mail.env
    /etc/limristem-mail-backup.env
    /etc/limristem-mail-rclone.conf
    /etc/limristem-mail.d
  )
  local candidate
  for candidate in "${candidate_paths[@]}"; do
    if [[ -n "$candidate" && -e "$candidate" ]]; then
      backup_paths+=("${candidate#/}")
    fi
  done

  if [[ "$backup_type" == 'full' ]]; then
    rm -f "${state_dir}/filesystem.snar"
  fi

  log "Archivio filesystem (${backup_type})"
  tar_create "$fs_archive" \
    --directory=/ \
    --listed-incremental="${state_dir}/filesystem.snar" \
    --numeric-owner \
    "${backup_paths[@]}"

  case "$db_mode" in
    logical)
      perform_logical_backup "$db_dump"
      if [[ "${LIMRISTEM_MAIL_BACKUP_COMPRESSION:-gzip}" == 'gzip' ]]; then
        gzip -f "$db_dump"
      fi
      ;;
    physical-full|physical-incremental)
      if ! command -v mariabackup >/dev/null 2>&1; then
        fail_with_error "mariabackup non disponibile per ${db_mode}"
      fi
      perform_physical_backup "$db_mode" "${backup_root}/mariadb.physical" "$state_dir"
      ;;
    none)
      log "MariaDB backup disabled for this schedule."
      ;;
    *)
      fail_with_error "LIMRISTEM_MAIL_BACKUP_DB_MODE non valido: ${db_mode}"
      ;;
  esac

  perform_redis_backup "$redis_file"
  write_manifest "$backup_root"
  copy_to_remote_targets "$backup_root"
  apply_retention "${LIMRISTEM_MAIL_BACKUP_LOCAL_DIR:-/var/backups/limristem-mail}"

  log "Backup completato: ${backup_root}"
}

main "$@"
