#!/bin/bash
# Export one or more ACore databases to ExportBackup_<timestamp>/
set -euo pipefail

INVOCATION_DIR="$PWD"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"

SUPPORTED_DBS=(auth characters world)
declare -A SUPPORTED_SET=()
for db in "${SUPPORTED_DBS[@]}"; do
  SUPPORTED_SET["$db"]=1
done

declare -A DB_NAMES=([auth]="" [characters]="" [world]="")
declare -a INCLUDE_DBS=()
declare -a SKIP_DBS=()

MYSQL_PW=""
DEST_PARENT=""
DEST_PROVIDED=false
EXPLICIT_SELECTION=false

usage(){
  cat <<'EOF'
Usage: ./backup-export.sh [options]

Creates a timestamped backup of one or more ACore databases.

Options:
  -o, --output DIR          Destination directory (default: storage/backups)
  -p, --password PASS       MySQL root password
      --auth-db NAME        Auth database schema name
      --characters-db NAME  Characters database schema name
      --world-db NAME       World database schema name
      --db LIST             Comma-separated list of databases to export
      --skip LIST           Comma-separated list of databases to skip
  -h, --help                Show this help and exit

Supported database identifiers: auth, characters, world.
By default exports auth and characters if database names are provided.

Examples:
  # Export all databases to default location
  ./backup-export.sh --password azerothcore123 --auth-db acore_auth --characters-db acore_characters --world-db acore_world --all

  # Export specific databases to custom directory
  ./backup-export.sh --output /path/to/backups --password azerothcore123 --db auth,characters --auth-db acore_auth --characters-db acore_characters

  # Export only world database
  ./backup-export.sh --password azerothcore123 --db world --world-db acore_world
EOF
}

err(){ printf 'Error: %s\n' "$*" >&2; }
die(){ err "$1"; exit 1; }

normalize_token(){
  printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]'
}

add_unique(){
  local -n arr="$1"
  local value="$2"
  for existing in "${arr[@]:-}"; do
    [[ "$existing" == "$value" ]] && return
  done
  arr+=("$value")
}

parse_db_list(){
  local -n target="$1"
  local value="$2"
  IFS=',' read -ra parts <<<"$value"
  for part in "${parts[@]}"; do
    local token
    token="$(normalize_token "$part")"
    [[ -z "$token" ]] && continue
    if [[ -z "${SUPPORTED_SET[$token]:-}" ]]; then
      die "Unknown database identifier: $token (supported: ${SUPPORTED_DBS[*]})"
    fi
    add_unique target "$token"
  done
}

remove_from_list(){
  local -n arr="$1"
  local value="$2"
  local -a filtered=()
  for item in "${arr[@]}"; do
    [[ "$item" == "$value" ]] || filtered+=("$item")
  done
  arr=("${filtered[@]}")
}

resolve_relative(){
  local base="$1" path="$2"
  if command -v python3 >/dev/null 2>&1; then
    python3 - "$base" "$path" <<'PY'
import os, sys
base, path = sys.argv[1:3]
if not path:
    print(os.path.abspath(base))
elif os.path.isabs(path):
    print(os.path.normpath(path))
else:
    print(os.path.normpath(os.path.join(base, path)))
PY
  else
    die "python3 is required but was not found on PATH"
  fi
}

json_string(){
  if ! command -v python3 >/dev/null 2>&1; then
    die "python3 is required but was not found on PATH"
  fi
  python3 - "$1" <<'PY'
import json, sys
print(json.dumps(sys.argv[1]))
PY
}

POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "$1" in
    -o|--output)
      [[ $# -ge 2 ]] || die "--output requires a directory argument"
      DEST_PARENT="$2"
      DEST_PROVIDED=true
      shift 2
      ;;
    -p|--password)
      [[ $# -ge 2 ]] || die "--password requires a value"
      MYSQL_PW="$2"
      shift 2
      ;;
    --auth-db)
      [[ $# -ge 2 ]] || die "--auth-db requires a value"
      DB_NAMES[auth]="$2"
      shift 2
      ;;
    --characters-db)
      [[ $# -ge 2 ]] || die "--characters-db requires a value"
      DB_NAMES[characters]="$2"
      shift 2
      ;;
    --world-db)
      [[ $# -ge 2 ]] || die "--world-db requires a value"
      DB_NAMES[world]="$2"
      shift 2
      ;;
    --db|--only)
      [[ $# -ge 2 ]] || die "--db requires a value"
      EXPLICIT_SELECTION=true
      parse_db_list INCLUDE_DBS "$2"
      shift 2
      ;;
    --skip)
      [[ $# -ge 2 ]] || die "--skip requires a value"
      parse_db_list SKIP_DBS "$2"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    --)
      shift
      while [[ $# -gt 0 ]]; do
        POSITIONAL+=("$1")
        shift
      done
      break
      ;;
    -*)
      die "Unknown option: $1"
      ;;
    *)
      POSITIONAL+=("$1")
      shift
      ;;
  esac
done

if ((${#POSITIONAL[@]} > 0)); then
  die "Positional arguments are not supported. Use named options instead."
fi

declare -a ACTIVE_DBS=()
if $EXPLICIT_SELECTION; then
  ACTIVE_DBS=("${INCLUDE_DBS[@]}")
else
  for db in "${SUPPORTED_DBS[@]}"; do
    if [[ -n "${DB_NAMES[$db]}" ]]; then
      add_unique ACTIVE_DBS "$db"
    fi
  done
  if ((${#ACTIVE_DBS[@]} == 0)); then
    ACTIVE_DBS=(auth characters)
  fi
fi

for skip in "${SKIP_DBS[@]:-}"; do
  remove_from_list ACTIVE_DBS "$skip"
done

if ((${#ACTIVE_DBS[@]} == 0)); then
  die "No databases selected for export."
fi

[[ -n "$MYSQL_PW" ]] || die "MySQL password is required (use --password)."

for db in "${ACTIVE_DBS[@]}"; do
  case "$db" in
    auth|characters|world) ;;
    *) die "Unsupported database identifier requested: $db" ;;
  esac
  if [[ -z "${DB_NAMES[$db]}" ]]; then
    die "Missing schema name for '$db'. Provide --${db}-db."
  fi
done

if $DEST_PROVIDED; then
  DEST_PARENT="$(resolve_relative "$INVOCATION_DIR" "$DEST_PARENT")"
else
  # Use storage/backups as default to align with existing backup structure
  if [ -d "$SCRIPT_DIR/storage" ]; then
    DEST_PARENT="$SCRIPT_DIR/storage/backups"
    mkdir -p "$DEST_PARENT"
  else
    DEST_PARENT="$SCRIPT_DIR"
  fi
fi

TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
DEST_DIR="$(printf '%s/ExportBackup_%s' "$DEST_PARENT" "$TIMESTAMP")"
mkdir -p "$DEST_DIR"
generated_at="$(date --iso-8601=seconds)"

dump_db(){
  local schema="$1" outfile="$2"
  echo "Dumping ${schema} -> ${outfile}"
  docker exec ac-mysql mysqldump -uroot -p"$MYSQL_PW" "$schema" | gzip > "$outfile"
}

for db in "${ACTIVE_DBS[@]}"; do
  outfile="$DEST_DIR/acore_${db}.sql.gz"
  dump_db "${DB_NAMES[$db]}" "$outfile"
done

first=1
{
  printf '{\n'
  printf '  "generated_at": %s,\n' "$(json_string "$generated_at")"
  printf '  "databases": {\n'
  for db in "${ACTIVE_DBS[@]}"; do
    key_json="$(json_string "$db")"
    value_json="$(json_string "${DB_NAMES[$db]}")"
    if (( first )); then
      first=0
    else
      printf ',\n'
    fi
    printf '    %s: %s' "$key_json" "$value_json"
  done
  printf '\n  }\n'
  printf '}\n'
} > "$DEST_DIR/manifest.json"

echo "Exported databases: ${ACTIVE_DBS[*]}"
echo "Backups saved under $DEST_DIR"
