#!/usr/bin/env bash
#
# upload-mobile-update.sh
# -----------------------
# Uploads the LogShip Mobile Android update artifacts to the web server so the
# in-app updater picks up the new release:
#
#     android/update-files/logship-mobile.apk   ->  <remote>/logship-mobile.apk
#     android/update-files/version.json         ->  <remote>/version.json
#
# The Nuxt app proxies https://www.logyou.de/logship/version.json (via
# /api/mobile/app-version) and downloads https://www.logyou.de/logship/logship-mobile.apk.
# Both files must live in the SAME web directory served at that URL
# (e.g. web root /var/www/logyou.de/web  ->  remote dir /var/www/logyou.de/web/logship).
#
# Auth (SFTP):
#   * key  (default) — passwordless SSH key / agent auth. No password is asked.
#                      Optionally point SFTP_IDENTITY at a specific private key.
#   * password       — used automatically if SFTP_PASS is set, or force with
#                      SFTP_AUTH=password. Uses lftp > sshpass > expect (whichever
#                      is installed; sftp+expect are preinstalled on macOS).
#
# Settings come from (in order): environment vars, the gitignored config file
# android/update-files/.deploy.env, then interactive prompts. Recognised vars:
#   SFTP_HOST SFTP_PORT SFTP_USER SFTP_REMOTE_DIR SFTP_AUTH SFTP_IDENTITY SFTP_PASS
#
# Usage:
#     scripts/upload-mobile-update.sh
#
set -euo pipefail

# --------------------------------------------------------------------------
# Locate repo + artifacts
# --------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
UPDATE_DIR="$REPO_ROOT/android/update-files"
APK_LOCAL="$UPDATE_DIR/logship-mobile.apk"
VERSION_LOCAL="$UPDATE_DIR/version.json"

# Public base URL used only for the optional post-upload verification.
PUBLIC_BASE_URL="${PUBLIC_BASE_URL:-https://www.logyou.de/logship}"

bold() { printf '\033[1m%s\033[0m\n' "$*"; }
err()  { printf '\033[31mERROR:\033[0m %s\n' "$*" >&2; }
ok()   { printf '\033[32m✔\033[0m %s\n' "$*"; }

# --------------------------------------------------------------------------
# Load optional gitignored config file
# --------------------------------------------------------------------------
CONFIG_FILE="${MOBILE_DEPLOY_ENV:-$UPDATE_DIR/.deploy.env}"
if [ -f "$CONFIG_FILE" ]; then
  bold "Loading config from $CONFIG_FILE"
  set -a; # shellcheck disable=SC1090
  . "$CONFIG_FILE"; set +a
fi

# --------------------------------------------------------------------------
# Resolve settings (env / config -> interactive prompt)
# --------------------------------------------------------------------------
SFTP_HOST="${SFTP_HOST:-}"
SFTP_PORT="${SFTP_PORT:-22}"
SFTP_USER="${SFTP_USER:-}"
SFTP_PASS="${SFTP_PASS:-}"
SFTP_REMOTE_DIR="${SFTP_REMOTE_DIR:-}"
SFTP_IDENTITY="${SFTP_IDENTITY:-}"
SFTP_AUTH="${SFTP_AUTH:-key}"   # key | password

[ -n "$SFTP_HOST" ]       || read -rp "Server IP / host: " SFTP_HOST
[ -n "$SFTP_USER" ]       || read -rp "Username: " SFTP_USER
if [ -z "$SFTP_REMOTE_DIR" ]; then
  read -rp "Remote directory (folder served at ${PUBLIC_BASE_URL}/) e.g. /var/www/logyou.de/web/logship: " SFTP_REMOTE_DIR
fi
[ -n "${SFTP_PORT:-}" ]   || SFTP_PORT=22

# A non-empty SFTP_PASS implies password mode.
if [ -n "$SFTP_PASS" ]; then SFTP_AUTH="password"; fi
if [ "$SFTP_AUTH" = "password" ] && [ -z "$SFTP_PASS" ]; then
  read -rsp "Password for ${SFTP_USER}@${SFTP_HOST}: " SFTP_PASS; echo
fi

# Validate required values
for v in SFTP_HOST SFTP_USER SFTP_REMOTE_DIR; do
  if [ -z "${!v}" ]; then err "$v is required"; exit 1; fi
done
if [ "$SFTP_AUTH" = "password" ] && [ -z "$SFTP_PASS" ]; then
  err "password auth selected but no password provided"; exit 1
fi

# --------------------------------------------------------------------------
# Validate local artifacts exist
# --------------------------------------------------------------------------
for f in "$APK_LOCAL" "$VERSION_LOCAL"; do
  if [ ! -f "$f" ]; then
    err "Missing $f"
    err "Build & stage the APK first (cd android && ./gradlew assembleDebug, then copy to update-files/)."
    exit 1
  fi
done

# --------------------------------------------------------------------------
# Sanity check: version.json must match the APK (the updater keys off version.json)
# --------------------------------------------------------------------------
find_aapt() {
  local base cand
  for base in "${ANDROID_HOME:-}" "${ANDROID_SDK_ROOT:-}" "$HOME/Library/Android/sdk" "$HOME/Android/Sdk"; do
    [ -n "$base" ] || continue
    cand="$(ls -1 "$base"/build-tools/*/aapt 2>/dev/null | sort -V | tail -1 || true)"
    [ -n "$cand" ] && { printf '%s' "$cand"; return 0; }
  done
  command -v aapt >/dev/null 2>&1 && { command -v aapt; return 0; }
  return 1
}

json_vn=""; json_vc=""
AAPT="$(find_aapt || true)"
if [ -n "${AAPT:-}" ]; then
  badging="$("$AAPT" dump badging "$APK_LOCAL" 2>/dev/null | grep '^package:' || true)"
  apk_vn="$(printf '%s' "$badging" | sed -n "s/.*versionName='\([^']*\)'.*/\1/p")"
  apk_vc="$(printf '%s' "$badging" | sed -n "s/.*versionCode='\([^']*\)'.*/\1/p")"
  json_vn="$(grep -o '"versionName"[^,]*' "$VERSION_LOCAL" | head -1 | sed 's/.*"versionName"[^"]*"\([^"]*\)".*/\1/')"
  json_vc="$(grep -o '"versionCode"[^,]*' "$VERSION_LOCAL" | head -1 | sed 's/[^0-9]//g')"
  bold "Version check"
  printf '  APK         : versionName=%s versionCode=%s\n' "$apk_vn" "$apk_vc"
  printf '  version.json: versionName=%s versionCode=%s\n' "$json_vn" "$json_vc"
  if [ "$apk_vn" != "$json_vn" ] || [ "$apk_vc" != "$json_vc" ]; then
    err "version.json does NOT match the APK. Devices compare version.json — a mismatch breaks updates."
    read -rp "Upload anyway? [y/N] " ans
    case "$ans" in y|Y) ;; *) exit 1;; esac
  else
    ok "version.json matches the APK"
  fi
else
  printf 'aapt not found — skipping APK/version.json match check.\n'
fi

# --------------------------------------------------------------------------
# Archive prep: discover the version we're about to REPLACE so the current
# remote APK can be moved (server-side rename) into history/ instead of being
# deleted, and recorded in a root-level history.json manifest that the in-app
# (admin-only) version browser reads. First release / no node → archiving is
# simply skipped and the upload behaves exactly as before.
# --------------------------------------------------------------------------
ARCHIVE_SUBDIR="history"
HISTORY_LOCAL="$UPDATE_DIR/history.json"
ARCHIVE_NAME=""
rm -f "$HISTORY_LOCAL"

# New version (the version.json we're about to upload); fall back to grep if aapt was missing.
new_vn="${json_vn:-}"; new_vc="${json_vc:-}"
[ -n "$new_vn" ] || new_vn="$(grep -o '"versionName"[^,]*' "$VERSION_LOCAL" | head -1 | sed 's/.*"versionName"[^"]*"\([^"]*\)".*/\1/')"
[ -n "$new_vc" ] || new_vc="$(grep -o '"versionCode"[^,]*' "$VERSION_LOCAL" | head -1 | sed 's/[^0-9]//g')"

old_remote_json=""; old_history_json=""
if command -v curl >/dev/null 2>&1; then
  old_remote_json="$(curl -fsS "${PUBLIC_BASE_URL}/version.json" 2>/dev/null || true)"
  old_history_json="$(curl -fsS "${PUBLIC_BASE_URL}/history.json" 2>/dev/null || true)"
fi

old_vn=""; old_vc=""
if [ -n "$old_remote_json" ]; then
  old_vn="$(printf '%s' "$old_remote_json" | grep -o '"versionName"[^,]*' | head -1 | sed 's/.*"versionName"[^"]*"\([^"]*\)".*/\1/')"
  old_vc="$(printf '%s' "$old_remote_json" | grep -o '"versionCode"[^,]*' | head -1 | sed 's/[^0-9]//g')"
fi

if [ -n "$old_vn" ] && [ -n "$old_vc" ] && [ "$old_vc" != "$new_vc" ]; then
  ARCHIVE_NAME="logship-mobile-${old_vn}-${old_vc}.apk"
  if command -v node >/dev/null 2>&1; then
    # Merge the replaced version into history.json (newest first, dedup by versionCode).
    OLD_VERSION_JSON="$old_remote_json" OLD_HISTORY_JSON="$old_history_json" \
    ARCHIVE_URL="${ARCHIVE_SUBDIR}/${ARCHIVE_NAME}" \
    node -e '
      const oldV = JSON.parse(process.env.OLD_VERSION_JSON || "{}");
      let hist = [];
      try { hist = JSON.parse(process.env.OLD_HISTORY_JSON || "[]"); if (!Array.isArray(hist)) hist = []; } catch { hist = []; }
      const entry = {
        versionName: oldV.versionName || "",
        versionCode: Number(oldV.versionCode) || 0,
        releaseDate: oldV.releaseDate || "",
        releaseNotes: oldV.releaseNotes || "",
        apkUrl: process.env.ARCHIVE_URL,
      };
      hist = hist.filter(h => Number(h.versionCode) !== entry.versionCode);
      hist.unshift(entry);
      hist.sort((a, b) => (Number(b.versionCode) || 0) - (Number(a.versionCode) || 0));
      process.stdout.write(JSON.stringify(hist, null, 2) + "\n");
    ' > "$HISTORY_LOCAL" || { err "Could not build history.json (node failed) — archiving APK without manifest update"; rm -f "$HISTORY_LOCAL"; }
  else
    err "node not found — archiving the old APK but NOT updating history.json (in-app history won't list it)"
  fi
fi

# --------------------------------------------------------------------------
# Confirm
# --------------------------------------------------------------------------
echo
bold "About to upload"
printf '  from : %s\n         %s\n' "$APK_LOCAL" "$VERSION_LOCAL"
printf '  to   : %s@%s:%s  (port %s, auth=%s)\n' "$SFTP_USER" "$SFTP_HOST" "$SFTP_REMOTE_DIR" "$SFTP_PORT" "$SFTP_AUTH"
if [ -n "$ARCHIVE_NAME" ]; then
  printf '  archive: current remote APK (v%s, code %s) -> %s/%s\n' "$old_vn" "$old_vc" "$ARCHIVE_SUBDIR" "$ARCHIVE_NAME"
  [ -f "$HISTORY_LOCAL" ] && printf '  history: history.json updated (%s entries)\n' "$(grep -c '"versionCode"' "$HISTORY_LOCAL" 2>/dev/null || echo '?')"
else
  printf '  archive: (none — first release or same version)\n'
fi
read -rp "Proceed? [y/N] " go
case "$go" in y|Y) ;; *) echo "Aborted."; exit 0;; esac

# --------------------------------------------------------------------------
# Build the sftp batch:
#   - create the remote dir if missing (ignored if it already exists)
#   - upload APK to a .part temp, then atomically rename to the final name
#   - upload version.json LAST so the updater never points at a half-uploaded APK
# --------------------------------------------------------------------------
#   - when replacing an existing version, MOVE the current APK into history/<name>
#     (server-side rename, then promote the new APK) and upload the refreshed history.json
BATCH="$(mktemp -t logship-sftp.XXXXXX)"
cleanup() { rm -f "$BATCH"; }
trap cleanup EXIT
{
  printf '%s\n' "-mkdir \"$SFTP_REMOTE_DIR\""
  printf '%s\n' "cd \"$SFTP_REMOTE_DIR\""
  printf '%s\n' "put \"$APK_LOCAL\" logship-mobile.apk.part"
  if [ -n "$ARCHIVE_NAME" ]; then
    printf '%s\n' "-mkdir $ARCHIVE_SUBDIR"
    printf '%s\n' "-rm $ARCHIVE_SUBDIR/$ARCHIVE_NAME"
    printf '%s\n' "-rename logship-mobile.apk $ARCHIVE_SUBDIR/$ARCHIVE_NAME"
  else
    printf '%s\n' "-rm logship-mobile.apk"
  fi
  printf '%s\n' "rename logship-mobile.apk.part logship-mobile.apk"
  printf '%s\n' "put \"$VERSION_LOCAL\" version.json"
  [ -f "$HISTORY_LOCAL" ] && printf '%s\n' "put \"$HISTORY_LOCAL\" history.json"
  printf '%s\n' "ls -l logship-mobile.apk version.json"
  printf '%s\n' "bye"
} >"$BATCH"

SFTP_COMMON_OPTS=(-oStrictHostKeyChecking=accept-new -oConnectTimeout=20 -P "$SFTP_PORT")
[ -n "$SFTP_IDENTITY" ] && SFTP_COMMON_OPTS+=(-i "$SFTP_IDENTITY")

# --- key / agent auth (default), with interactive password fallback -------
# IMPORTANT: commands are fed on stdin (NOT via `sftp -b`). `-b` silently forces
# the SSH option BatchMode=yes, which disables the password prompt — so if the
# key is not accepted, `sftp -b` hangs/fails with no way to type a password.
# Feeding commands on stdin keeps the connection interactive: the key is used if
# available, otherwise sftp prompts for the server password on the terminal.
# -oConnectTimeout bounds an unreachable host instead of hanging forever.
upload_key() {
  local idopt=(); [ -n "$SFTP_IDENTITY" ] && idopt=(-i "$SFTP_IDENTITY")
  # ${idopt[@]+"${idopt[@]}"} is the empty-array-safe expansion: under `set -u`
  # macOS bash 3.2 errors on "${idopt[@]}" when the array is empty.
  # Feed the shared $BATCH on stdin (NOT via `-b`, which forces BatchMode=yes and
  # kills the password prompt). ssh still reads any password from /dev/tty.
  sftp -oStrictHostKeyChecking=accept-new -oConnectTimeout=20 ${idopt[@]+"${idopt[@]}"} \
       -P "$SFTP_PORT" "$SFTP_USER@$SFTP_HOST" < "$BATCH"
}

# --- password auth back-ends ----------------------------------------------
upload_lftp() {
  local idline=""
  [ -n "$SFTP_IDENTITY" ] && idline="set sftp:connect-program \"ssh -a -x -i $SFTP_IDENTITY\""
  local archive_cmds="rm -f logship-mobile.apk"
  if [ -n "$ARCHIVE_NAME" ]; then
    archive_cmds=$'mkdir -f '"$ARCHIVE_SUBDIR"$'\nrm -f '"$ARCHIVE_SUBDIR/$ARCHIVE_NAME"$'\nmv logship-mobile.apk '"$ARCHIVE_SUBDIR/$ARCHIVE_NAME"
  fi
  local history_cmd=""
  [ -f "$HISTORY_LOCAL" ] && history_cmd="put \"$HISTORY_LOCAL\" -o history.json"
  lftp -u "$SFTP_USER,$SFTP_PASS" "sftp://$SFTP_HOST:$SFTP_PORT" <<EOF
set sftp:auto-confirm yes
set net:max-retries 2
set net:timeout 30
$idline
mkdir -f "$SFTP_REMOTE_DIR"
cd "$SFTP_REMOTE_DIR"
put "$APK_LOCAL" -o logship-mobile.apk.part
$archive_cmds
mv logship-mobile.apk.part logship-mobile.apk
put "$VERSION_LOCAL" -o version.json
$history_cmd
bye
EOF
}

upload_sshpass() {
  sshpass -p "$SFTP_PASS" sftp -oBatchMode=no "${SFTP_COMMON_OPTS[@]}" -b "$BATCH" "$SFTP_USER@$SFTP_HOST"
}

upload_expect() {
  # Password is passed via the environment (not interpolated) so special
  # characters can't break the expect script.
  SFTP_PASS="$SFTP_PASS" SFTP_USER="$SFTP_USER" SFTP_HOST="$SFTP_HOST" \
  SFTP_PORT="$SFTP_PORT" BATCH="$BATCH" SFTP_IDENTITY="$SFTP_IDENTITY" expect <<'EXPECT'
set timeout 1200
set user  $env(SFTP_USER)
set host  $env(SFTP_HOST)
set port  $env(SFTP_PORT)
set batch $env(BATCH)
set pass  $env(SFTP_PASS)
set ident $env(SFTP_IDENTITY)
set idopt ""
if {$ident ne ""} { set idopt "-i $ident" }
spawn sftp -oStrictHostKeyChecking=accept-new -oBatchMode=no {*}$idopt -P $port -b $batch $user@$host
expect {
  -re "(P|p)assword:"  { send -- "$pass\r"; exp_continue }
  -re "passphrase"     { send -- "$pass\r"; exp_continue }
  -re "yes/no"         { send -- "yes\r";  exp_continue }
  eof
}
catch wait result
exit [lindex $result 3]
EXPECT
}

echo
bold "Uploading…"
if [ "$SFTP_AUTH" = "key" ]; then
  echo "(SSH key / agent auth — enter the server password if prompted; Ctrl-C to cancel)"
  upload_key
else
  if   command -v lftp    >/dev/null 2>&1; then echo "(password auth via lftp)";    upload_lftp
  elif command -v sshpass >/dev/null 2>&1; then echo "(password auth via sshpass)"; upload_sshpass
  elif command -v expect  >/dev/null 2>&1; then echo "(password auth via expect)";  upload_expect
  else
    err "Password auth needs lftp / sshpass / expect. Install one, e.g.: brew install lftp"
    exit 1
  fi
fi
ok "Upload finished"

# --------------------------------------------------------------------------
# Optional: verify the public endpoint now serves the new version
# --------------------------------------------------------------------------
if command -v curl >/dev/null 2>&1; then
  echo
  bold "Verifying ${PUBLIC_BASE_URL}/version.json"
  remote_json="$(curl -fsS "${PUBLIC_BASE_URL}/version.json" 2>/dev/null || true)"
  if [ -n "$remote_json" ]; then
    remote_vn="$(printf '%s' "$remote_json" | grep -o '"versionName"[^,]*' | head -1 | sed 's/.*"versionName"[^"]*"\([^"]*\)".*/\1/')"
    printf '  served versionName = %s\n' "$remote_vn"
    if [ -n "${json_vn:-}" ] && [ "$remote_vn" = "$json_vn" ]; then
      ok "Public endpoint serves the uploaded version"
    else
      printf '  (note: served value may lag behind due to CDN/proxy caching)\n'
    fi
  else
    printf '  (could not fetch — check the URL / server config manually)\n'
  fi
fi

echo
bold "Done. Devices will see the update on the next check."
printf '  %s/version.json\n  %s/logship-mobile.apk\n' "$PUBLIC_BASE_URL" "$PUBLIC_BASE_URL"
