#!/bin/sh
set -eu
umask 077

BASE="${ROUTER_OPS_BASE:-/opt/router-ops}"
PUBLIC="$BASE/public"
PUBLIC_BASE="${ROUTER_PUBLIC_BASE_URL:-https://reports.secret-studio.ru}"
PUBLIC_BASE="${PUBLIC_BASE%/}"
TOKEN_FILE="${ROUTER_PUBLIC_TOKEN_FILE:-$PUBLIC/.router-public-token}"
TOKEN="${ROUTER_PUBLIC_TOKEN:-}"
HTTP_CHECK="${ROUTER_PUBLIC_HTTP_CHECK:-1}"
RESOLVE_IP="${ROUTER_PUBLIC_RESOLVE_IP:-10.71.100.84}"

if [ -z "$TOKEN" ]; then
  TOKEN="$(cat "$TOKEN_FILE")"
fi

ROOT="${ROUTER_PUBLIC_ROOT:-$PUBLIC/r/$TOKEN}"
LATEST="$ROOT/latest"
OLD="$ROOT/old"
REINDEX="${ROUTER_PUBLIC_REINDEX:-$BASE/bin/router-public-reindex}"
LOCK_DIR="$BASE/state/locks"
LOCK_FILE="$LOCK_DIR/public-publish.lock"

if [ "$#" -lt 2 ]; then
  echo "Usage: router-publish-report <label> <file1> [file2 ...]" >&2
  exit 1
fi

for CMD in awk basename chmod cmp cp date find flock grep mktemp mv rm sed sort tac tr wc; do
  command -v "$CMD" >/dev/null 2>&1 || {
    echo "ERROR: missing command: $CMD" >&2
    exit 1
  }
done

case "$PUBLIC_BASE" in
  https://*) ;;
  *)
    echo "ERROR: ROUTER_PUBLIC_BASE_URL must use https://" >&2
    exit 1
    ;;
esac

case "$HTTP_CHECK" in
  0|1) ;;
  *)
    echo "ERROR: ROUTER_PUBLIC_HTTP_CHECK must be 0 or 1" >&2
    exit 1
    ;;
esac

if [ "$HTTP_CHECK" -eq 1 ]; then
  command -v curl >/dev/null 2>&1 || {
    echo "ERROR: curl is required for HTTP post-check" >&2
    exit 1
  }
fi

LABEL_RAW="$1"
shift

LABEL="$(printf '%s' "$LABEL_RAW" | tr -c 'A-Za-z0-9_.-' '_' | sed 's/__*/_/g; s/^_//; s/_$//')"
[ -n "$LABEL" ] || LABEL="report"
LABEL_LOWER="$(printf '%s' "$LABEL" | tr '[:upper:]' '[:lower:]')"

case "$LABEL_LOWER" in
  vm100_git_source_*) KIND="vm100"; MATCH='*_vm100_git_source_*' ;;
  vm101_git_source_*) KIND="vm101"; MATCH='*_vm101_git_source_*' ;;
  vm121_git_source_*) KIND="vm121"; MATCH='*_vm121_git_source_*' ;;
  local_architecture_plan_vm101_autonomous_hmn_recovery*) KIND="local-m07-plan"; MATCH='*_local_architecture_plan_vm101_autonomous_hmn_recovery*' ;;
  global_project_plan_wg_paid*) KIND="global-project-plan"; MATCH='*_global_project_plan_wg_paid*' ;;
  xs_map_*|access_map_*) KIND="access-map"; MATCH='access-map-special' ;;
  vm101_model_*) KIND="vm101-model"; MATCH='*_vm101_model_*' ;;
  vm101_methods_*) KIND="vm101-methods"; MATCH='*_vm101_methods_*' ;;
  vm130_router_ops_source_snapshot*) KIND="vm130-source"; MATCH='*_vm130_router_ops_source_snapshot*' ;;
  step*|*_step*) KIND="step-report"; MATCH='*_step*' ;;
  *) KIND="label:$LABEL"; MATCH="*_${LABEL}" ;;
esac

TS="${ROUTER_PUBLIC_TIMESTAMP:-$(date -u +%Y%m%d-%H%M%S)}"
case "$TS" in
  [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) ;;
  *)
    echo "ERROR: invalid ROUTER_PUBLIC_TIMESTAMP: $TS" >&2
    exit 1
    ;;
esac

FOLDER="${TS}_${LABEL}"
STAGE_ROOT="$(mktemp -d "$BASE/state/public-publish-stage.${TS}.XXXXXX")"
STAGE_DIR="$STAGE_ROOT/$FOLDER"
MOVED_LIST="$STAGE_ROOT/moved-from-latest.txt"
BASENAME_LIST="$STAGE_ROOT/input-basenames.txt"
NEW_MOVED=0
COMMITTED=0

kind_dirs() {
  if [ "$KIND" = "access-map" ]; then
    find "$LATEST" \
      -mindepth 1 \
      -maxdepth 1 \
      -type d \
      \( -name '*_xs_map_*' -o -name '*_access_map_*' \) \
      -printf '%f\n'
  else
    find "$LATEST" \
      -mindepth 1 \
      -maxdepth 1 \
      -type d \
      -name "$MATCH" \
      -printf '%f\n'
  fi
}

cleanup() {
  RC=$?
  trap - EXIT INT TERM HUP
  set +e

  if [ "$COMMITTED" -ne 1 ]; then
    if [ "$NEW_MOVED" -eq 1 ] && [ -d "$LATEST/$FOLDER" ]; then
      mv "$LATEST/$FOLDER" "$STAGE_DIR"
    fi

    if [ -s "$MOVED_LIST" ]; then
      tac "$MOVED_LIST" | while IFS= read -r NAME; do
        [ -n "$NAME" ] || continue
        if [ -d "$OLD/$NAME" ] && [ ! -e "$LATEST/$NAME" ]; then
          mv "$OLD/$NAME" "$LATEST/$NAME"
        fi
      done
    fi

    "$REINDEX" >/dev/null 2>&1 || true
  fi

  rm -rf "$STAGE_ROOT"
  exit "$RC"
}
trap cleanup EXIT INT TERM HUP

mkdir -p "$LATEST" "$OLD" "$LOCK_DIR" "$STAGE_DIR"
chmod 755 "$ROOT" "$LATEST" "$OLD" "$STAGE_DIR"
: > "$MOVED_LIST"
: > "$BASENAME_LIST"

exec 9>"$LOCK_FILE"
if ! flock -n 9; then
  echo "ERROR: another publication is running" >&2
  exit 1
fi

REFUSE_RE='raw|\.env|secret|secrets|private|Private|id_rsa|id_ed25519|authorized_keys|wg[0-9]?.conf|amnezia|hmn.*code|access.*code'
PUBLISHED_FILE_COUNT=0

for SRC in "$@"; do
  if [ ! -f "$SRC" ]; then
    echo "ERROR: file not found: $SRC" >&2
    exit 1
  fi

  BASESRC="$(basename "$SRC")"

  case "$BASESRC" in
    index.html|index.html.tmp.*)
      echo "REFUSE: reserved publication filename: $BASESRC" >&2
      exit 2
      ;;
  esac

  if printf '%s\n%s\n' "$SRC" "$BASESRC" | grep -Eiq "$REFUSE_RE"; then
    echo "REFUSE: suspicious path/name, not publishing: $SRC" >&2
    exit 2
  fi

  if grep -Fqx "$BASESRC" "$BASENAME_LIST"; then
    echo "ERROR: duplicate publication basename: $BASESRC" >&2
    exit 1
  fi
  printf '%s\n' "$BASESRC" >> "$BASENAME_LIST"

  cp -pL "$SRC" "$STAGE_DIR/$BASESRC"
  chmod 644 "$STAGE_DIR/$BASESRC"
  cmp -s "$SRC" "$STAGE_DIR/$BASESRC" || {
    echo "ERROR: staged copy mismatch: $SRC" >&2
    exit 1
  }
  PUBLISHED_FILE_COUNT=$((PUBLISHED_FILE_COUNT + 1))
done

{
  echo '<!doctype html><html><head><meta charset="utf-8"><title>router report</title></head><body>'
  printf '<h1>%s %s</h1>\n' "$TS" "$LABEL"
  echo '<ul>'
  find "$STAGE_DIR" -maxdepth 1 -type f | LC_ALL=C sort | while IFS= read -r FILE; do
    NAME="$(basename "$FILE")"
    printf '<li><a href="%s">%s</a></li>\n' "$NAME" "$NAME"
  done
  echo '</ul>'
  echo '<p><a href="../">Back to latest</a></p>'
  echo '</body></html>'
} > "$STAGE_DIR/index.html"
chmod 644 "$STAGE_DIR/index.html"

kind_dirs | LC_ALL=C sort | while IFS= read -r NAME; do
  [ -n "$NAME" ] || continue
  if [ -e "$OLD/$NAME" ]; then
    echo "ERROR: historical destination already exists: $OLD/$NAME" >&2
    exit 1
  fi
  mv "$LATEST/$NAME" "$OLD/$NAME"
  printf '%s\n' "$NAME" >> "$MOVED_LIST"
done

if [ -e "$LATEST/$FOLDER" ]; then
  echo "ERROR: publication destination already exists: $LATEST/$FOLDER" >&2
  exit 1
fi

mv "$STAGE_DIR" "$LATEST/$FOLDER"
NEW_MOVED=1

"$REINDEX" >/dev/null

if ! grep -Fq "$FOLDER" "$LATEST/index.html"; then
  echo "ERROR: latest index missing new folder: $FOLDER" >&2
  exit 1
fi

CURRENT_KIND_COUNT="$(kind_dirs | awk 'END {print NR + 0}')"
if [ "$CURRENT_KIND_COUNT" -ne 1 ]; then
  echo "ERROR: current kind count is $CURRENT_KIND_COUNT, expected 1" >&2
  exit 1
fi

while IFS= read -r BASESRC; do
  [ -n "$BASESRC" ] || continue
  [ -f "$LATEST/$FOLDER/$BASESRC" ] || {
    echo "ERROR: published file missing: $BASESRC" >&2
    exit 1
  }
done < "$BASENAME_LIST"

HTTP_POSTCHECK="SKIPPED"
if [ "$HTTP_CHECK" -eq 1 ]; then
  HOST="$(printf '%s' "$PUBLIC_BASE" | sed -n 's#^https://\([^/:]*\).*#\1#p')"
  [ -n "$HOST" ] || {
    echo "ERROR: cannot parse public host from $PUBLIC_BASE" >&2
    exit 1
  }

  HTTP_LATEST="$STAGE_ROOT/http-latest.html"
  HTTP_FOLDER="$STAGE_ROOT/http-folder.html"

  curl -fsS --connect-timeout 5 --max-time 30 \
    --resolve "$HOST:443:$RESOLVE_IP" \
    "$PUBLIC_BASE/latest/" > "$HTTP_LATEST"

  grep -Fq "$FOLDER" "$HTTP_LATEST" || {
    echo "ERROR: served latest index missing new folder: $FOLDER" >&2
    exit 1
  }

  curl -fsS --connect-timeout 5 --max-time 30 \
    --resolve "$HOST:443:$RESOLVE_IP" \
    "$PUBLIC_BASE/latest/$FOLDER/" > "$HTTP_FOLDER"

  while IFS= read -r BASESRC; do
    [ -n "$BASESRC" ] || continue
    grep -Fq "$BASESRC" "$HTTP_FOLDER" || {
      echo "ERROR: served folder index missing file: $BASESRC" >&2
      exit 1
    }
  done < "$BASENAME_LIST"

  HTTP_POSTCHECK="PASS"
fi

PREVIOUS_MOVED_COUNT="$(awk 'END {print NR + 0}' "$MOVED_LIST")"
COMMITTED=1
trap - EXIT INT TERM HUP
rm -rf "$STAGE_ROOT"

PUBLISHED_DIR="$LATEST/$FOLDER"
PUBLISHED_RELATIVE_PATH="latest/$FOLDER"

printf 'PUBLISHED_DIR=%s\n' "$PUBLISHED_DIR"
printf 'PUBLISHED_RELATIVE_PATH=%s\n' "$PUBLISHED_RELATIVE_PATH"
printf 'PUBLIC_OBJECT_KIND=%s\n' "$KIND"
printf 'PREVIOUS_MOVED_COUNT=%s\n' "$PREVIOUS_MOVED_COUNT"
printf 'CURRENT_KIND_COUNT=%s\n' "$CURRENT_KIND_COUNT"
printf 'PUBLISHED_FILE_COUNT=%s\n' "$PUBLISHED_FILE_COUNT"
printf 'LOCAL_INDEX=http://192.168.30.84:8099/r/%s/latest/%s/\n' "$TOKEN" "$FOLDER"
printf 'LATEST_ROOT_URL=%s/latest/\n' "$PUBLIC_BASE"
printf 'OLD_ROOT_URL=%s/old/\n' "$PUBLIC_BASE"
printf 'PUBLIC_INDEX=%s/latest/\n' "$PUBLIC_BASE"
printf 'PUBLIC_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
printf 'LATEST_INDEX_CONTAINS_FOLDER=true\n'
printf 'HTTP_POSTCHECK=%s\n' "$HTTP_POSTCHECK"

case "$KIND" in
  step-report)
    printf 'STEP_REPORT_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  local-m07-plan)
    printf 'LOCAL_M07_PLAN_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  global-project-plan)
    printf 'GLOBAL_PROJECT_PLAN_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  access-map)
    printf 'ACCESS_MAP_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  vm100)
    printf 'VM100_PUBLIC_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  vm101)
    printf 'VM101_PUBLIC_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  vm121)
    printf 'VM121_PUBLIC_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  vm101-model)
    printf 'VM101_MODEL_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  vm101-methods)
    printf 'VM101_METHODS_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
  vm130-source)
    printf 'VM130_SOURCE_URL=%s/latest/%s/\n' "$PUBLIC_BASE" "$FOLDER"
    ;;
esac
