#!/bin/bash

## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

set -o errexit
set -o nounset
set -o errtrace
set -o pipefail
shopt -s inherit_errexit
shopt -s shift_verbose

# shellcheck source=../share/mediawiki-shell/common
source /usr/share/mediawiki-shell/common

log info "START"

usage() {
  printf '%s\n' "Usage: ${0##*/} [OPTIONS] WIKI BACKUP_DIR
Pushes git-modified .mw files from BACKUP_DIR to the wiki using mw-edit.
Uses 'git diff' to detect which files have changed.

Options:
  --edit-msg=MSG             Edit summary for wiki edits (default: ${default_edit_msg})
  --git-diff-arg=ARG         Argument passed to 'git diff' to select commits
                             (default: HEAD~1, i.e. changes in the last commit)
  --continue-from=N|TITLE    Continue from file index (N) or page title (case-insensitive)
  --keep-going               Continue past per-item failures (record them, exit non-zero)
  --retry                    Skip items already recorded done; retry the rest
  --reset                    Clear any prior state before starting
  --state-file=PATH          Override the state-file location (default: under TMPFOLDER)
  --dry-run                  Preview only; skip the actual write on the server.
Example:
  ${0##*/} 'https://www.kicksecure.com/w' ~/derivative-backup/kicksecure-wiki-backup
  ${0##*/} --git-diff-arg=HEAD~1 'https://www.kicksecure.com/w' ~/derivative-backup/kicksecure-wiki-backup
  ${0##*/} --git-diff-arg=HEAD~1 'https://www.whonix.org/w' ~/derivative-backup/whonix-wiki-backup" >&2
  exit 1
}

default_edit_msg="mediawiki-shell-bot-default-edit-message"
git_diff_arg="HEAD~1"
edit_msg="${default_edit_msg}"
continue_from=""

# shellcheck disable=SC2034  # KEEP_GOING/RETRY/STATE_RESET/STATE_FILE set here, read by common's state_* helpers
while true; do
  case "${1-}" in
    --edit-msg=*)
      edit_msg="${1#--edit-msg=}"
      shift
      ;;
    --git-diff-arg=*)
      git_diff_arg="${1#--git-diff-arg=}"
      shift
      ;;
    --continue-from=*)
      continue_from="${1#--continue-from=}"
      shift
      ;;
    --keep-going)
      KEEP_GOING="true"
      shift
      ;;
    --retry)
      RETRY="true"
      shift
      ;;
    --reset)
      STATE_RESET="true"
      shift
      ;;
    --state-file=*)
      STATE_FILE="${1#--state-file=}"
      shift
      ;;
    --dry-run)
      export DRY_RUN="true"
      shift
      ;;
    -h|--help)
      usage
      ;;
    --)
      shift
      break
      ;;
    -*)
      die 2 "Invalid option: '$1'"
      ;;
    *)
      break
      ;;
  esac
done

if [ -z "${2-}" ]; then
  usage
fi

wiki_url="$1"
backup_dir="$2"

check_vars_exist wiki_url backup_dir

if [ ! -d "$backup_dir" ]; then
  die 1 "backup_dir '$backup_dir' does not exist!"
fi

## backup_dir must be inside a git repository.
if ! git -C "$backup_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  die 1 "backup_dir '$backup_dir' is not inside a git repository!"
fi

log info "wiki_url      : $wiki_url"
log info "backup_dir    : $backup_dir"
log info "git_diff_arg  : $git_diff_arg"
log info "edit_msg      : $edit_msg"
log info "continue_from : ${continue_from-}"

state_setup "$wiki_url"

mw-login-test "$wiki_url"

## Get list of modified/added .mw files from git diff.
## --diff-filter=AM: only Added or Modified files (not Deleted).
## --name-only: output file names only.
## --relative: paths relative to backup_dir.
modified_files="$(git -C "$backup_dir" diff --diff-filter=AM --name-only --relative "$git_diff_arg" -- '*.mw')" || true

if [ -z "$modified_files" ]; then
  log info "No modified .mw files found. Nothing to push."
  exit 0
fi

## Defense-in-depth: check filenames from git for malicious unicode
## (bidi overrides, homoglyphs, etc.) before decoding them to page titles.
printf '%s\n' "$modified_files" | unicode-show

counter_total="$(printf '%s\n' "$modified_files" | wc -l)"
counter_currently=0

log info "Found $counter_total modified .mw file(s) to push."

# shellcheck disable=SC2034  # mutated by should_start_processing via nameref
continue_state="no"
while IFS= read -r mw_file; do
  (( counter_currently++ )) || true

  ## Convert filename back to page title.
  ## Uses decode_backup_filename_item() which strips .mw and runs full
  ## percent-decoding (urllib.parse.unquote), reversing set_backup_page_item().
  page_title="$(decode_backup_filename_item "$mw_file")"

  if ! should_start_processing "$counter_currently" "$page_title" "$continue_from" continue_state; then
    log info "skip $counter_currently / $counter_total | $page_title"
    continue
  fi

  if state_should_skip "$page_title"; then
    log info "retry-skip $counter_currently / $counter_total | already done | $page_title"
    continue
  fi

  file_path="${backup_dir}/${mw_file}"
  assert_path_within_dir "$backup_dir" "$file_path"

  if [ ! -r "$file_path" ]; then
    log warn "SKIP $counter_currently / $counter_total | file not readable: $file_path"
    continue
  fi

  log info "push $counter_currently / $counter_total | $page_title | $file_path"

  push_rc=0
  mw-edit "$wiki_url" "$file_path" "$page_title" "$edit_msg" || push_rc="$?"
  if [ "$push_rc" = "0" ]; then
    state_record_done "$page_title"
  else
    state_handle_failure "$page_title" "mw-edit rc=$push_rc"
  fi

done <<< "$modified_files"

state_finish
if [ "$state_fail_count" -gt 0 ]; then
  exit 2
fi

## TODO: Should we print some sort of progress message in the failure
## path above too?
log info "Done. Pushed $counter_currently / $counter_total page(s)."
