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

umask 077

SOURCE_DIR="${AGRARIAN_PROJECT_SOURCE:-/mnt/projects/AgrarianGameBulid}"
MOUNT_POINT="${AGRARIAN_BACKUP_MOUNT:-/mnt/backups/linastorage}"
BACKUP_ROOT="${AGRARIAN_BACKUP_ROOT:-$MOUNT_POINT/backups/agrarian-game/project}"
RESTIC_REPOSITORY="${AGRARIAN_RESTIC_REPOSITORY:-$BACKUP_ROOT/restic-repository}"
RESTIC_PASSWORD_FILE="${AGRARIAN_RESTIC_PASSWORD_FILE:-/root/.backup-secrets/agrarian-project-restic.password}"
STATE_DIR="${AGRARIAN_BACKUP_STATE_DIR:-$BACKUP_ROOT/state}"
LOCK_FILE="${AGRARIAN_BACKUP_LOCK:-/run/lock/agrarian-project-backup.lock}"
LOG_TAG="${AGRARIAN_BACKUP_LOG_TAG:-agrarian-project-backup}"

DRY_RUN=0
FORCE=0
MANUAL=0

EXCLUDES=(
  --exclude "$SOURCE_DIR/DerivedDataCache"
  --exclude "$SOURCE_DIR/Intermediate"
  --exclude "$SOURCE_DIR/Saved"
  --exclude "$SOURCE_DIR/Binaries"
  --exclude "$SOURCE_DIR/Builds"
  --exclude "$SOURCE_DIR/.git/index.lock"
  --exclude "$SOURCE_DIR/.git/lfs/tmp"
  --exclude "$SOURCE_DIR/.git/logs"
  --exclude "$SOURCE_DIR/.vs"
  --exclude "$SOURCE_DIR/AGRARIAN_BACKUP_MANIFEST.txt"
  --exclude "$SOURCE_DIR/AGRARIAN_CRITICAL_SHA256SUMS"
  --exclude "$SOURCE_DIR/AGRARIAN_RSYNC_LOG.txt"
)

log() {
  printf '[%s] %s\n' "$(date -Is)" "$*"
}

die() {
  log "ERROR: $*"
  exit 1
}

usage() {
  cat <<'USAGE'
Usage: agrarian_project_backup.sh [--force] [--manual] [--dry-run]

Creates an incremental deduplicated restic backup of the Agrarian Unreal project
on Linastorage. Generated Unreal cache/build folders are excluded; Git metadata
and local source data are included.
USAGE
}

parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --force)
        FORCE=1
        ;;
      --manual)
        MANUAL=1
        FORCE=1
        ;;
      --dry-run)
        DRY_RUN=1
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        die "Unknown argument: $1"
        ;;
    esac
    shift
  done
}

restic_cmd() {
  RESTIC_PASSWORD_FILE="$RESTIC_PASSWORD_FILE" \
    restic -r "$RESTIC_REPOSITORY" "$@"
}

require_environment() {
  [[ -d "$SOURCE_DIR" ]] || die "Project source directory not found: $SOURCE_DIR"
  [[ -f "$SOURCE_DIR/AgrarianGame.uproject" ]] || die "Missing AgrarianGame.uproject under $SOURCE_DIR"
  findmnt -rn "$MOUNT_POINT" >/dev/null || die "$MOUNT_POINT is not mounted"
  mkdir -p "$BACKUP_ROOT" "$STATE_DIR" "$(dirname "$RESTIC_PASSWORD_FILE")"
  [[ -w "$BACKUP_ROOT" ]] || die "$BACKUP_ROOT is not writable"
  command -v restic >/dev/null || die "restic is required"
}

ensure_password_file() {
  if [[ -f "$RESTIC_PASSWORD_FILE" ]]; then
    chmod 0600 "$RESTIC_PASSWORD_FILE"
    return 0
  fi

  log "Creating root-owned restic password file: $RESTIC_PASSWORD_FILE"
  openssl rand -base64 48 > "$RESTIC_PASSWORD_FILE"
  chmod 0600 "$RESTIC_PASSWORD_FILE"
}

ensure_repository() {
  if [[ -f "$RESTIC_REPOSITORY/config" ]]; then
    return 0
  fi

  log "Initializing restic repository: $RESTIC_REPOSITORY"
  mkdir -p "$RESTIC_REPOSITORY"
  restic_cmd init
}

source_signature() {
  local tmp
  tmp="$(mktemp)"

  {
    git -C "$SOURCE_DIR" rev-parse HEAD 2>/dev/null || true
    git -C "$SOURCE_DIR" status --porcelain=v1 2>/dev/null || true
    find "$SOURCE_DIR" \
      \( -path "$SOURCE_DIR/DerivedDataCache" \
      -o -path "$SOURCE_DIR/Intermediate" \
      -o -path "$SOURCE_DIR/Saved" \
      -o -path "$SOURCE_DIR/Binaries" \
      -o -path "$SOURCE_DIR/Builds" \
      -o -path "$SOURCE_DIR/.git/lfs/tmp" \
      -o -path "$SOURCE_DIR/.git/logs" \
      -o -path "$SOURCE_DIR/.vs" \) -prune \
      -o -type f -printf '%P\t%s\t%T@\n' 2>/dev/null \
      | sort
  } > "$tmp"

  sha256sum "$tmp" | awk '{print $1}'
  rm -f -- "$tmp"
}

write_manifest() {
  local signature="$1"
  local manifest="$STATE_DIR/LATEST_MANIFEST.txt"

  {
    echo "backup_timestamp=$(date -Is)"
    echo "host=$(hostname -f 2>/dev/null || hostname)"
    echo "source=$SOURCE_DIR"
    echo "mount=$MOUNT_POINT"
    echo "repository=$RESTIC_REPOSITORY"
    echo "signature=$signature"
    echo "backup_mode=$([[ "$MANUAL" == "1" ]] && echo manual || echo scheduled)"
    echo "git_head=$(git -C "$SOURCE_DIR" rev-parse HEAD 2>/dev/null || echo unavailable)"
    echo
    echo "[excluded]"
    printf '%s\n' "${EXCLUDES[@]}"
  } > "$manifest"
}

backup_project() {
  local signature="$1"
  local mode_tag="scheduled"
  [[ "$MANUAL" == "1" ]] && mode_tag="manual"

  log "Starting restic backup for Agrarian project"
  restic_cmd backup \
    --one-file-system \
    --tag agrarian-game \
    --tag project \
    --tag "$mode_tag" \
    "${EXCLUDES[@]}" \
    "$SOURCE_DIR"

  write_manifest "$signature"
  printf '%s\n' "$signature" > "$STATE_DIR/LAST_SOURCE_SIGNATURE"

  log "Applying restic retention policy"
  restic_cmd forget \
    --tag agrarian-game \
    --tag project \
    --keep-within 7d \
    --keep-daily 30 \
    --keep-weekly 12 \
    --keep-monthly 12 \
    --prune

  restic_cmd snapshots --tag agrarian-game --tag project > "$STATE_DIR/LATEST_SNAPSHOTS.txt"
  log "Project backup completed"
  logger -t "$LOG_TAG" "restic project backup completed successfully"
}

main() {
  parse_args "$@"

  exec 9>"$LOCK_FILE"
  if ! flock -n 9; then
    log "Another Agrarian project backup is already running; skipping this trigger"
    logger -t "$LOG_TAG" "another project backup is already running; skipped"
    exit 0
  fi

  require_environment
  ensure_password_file
  ensure_repository

  local signature previous_signature
  signature="$(source_signature)"
  previous_signature="$(cat "$STATE_DIR/LAST_SOURCE_SIGNATURE" 2>/dev/null || true)"

  if [[ "$FORCE" != "1" && "$signature" == "$previous_signature" ]]; then
    log "No project changes detected since latest restic backup; skipping"
    logger -t "$LOG_TAG" "no project changes detected; skipped"
    exit 0
  fi

  if [[ "$DRY_RUN" == "1" ]]; then
    log "Dry run: project changes detected; restic snapshot would be created"
    exit 0
  fi

  backup_project "$signature"
}

main "$@"
