#!/bin/sh
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eu

ACCESS_KEY="${RUSTFS_SITE_REPL_ACCESS_KEY:-rustfsadmin}"
SECRET_KEY="${RUSTFS_SITE_REPL_SECRET_KEY:-rustfsadmin}"
BUCKET="${RUSTFS_SITE_REPL_FLOW_BUCKET:-site-repl-flow-check}"
DELETE_BUCKET="${RUSTFS_SITE_REPL_DELETE_BUCKET:-site-repl-delete-$(date +%Y%m%d-%H%M%S)-$$}"
PREFIX="${RUSTFS_SITE_REPL_FLOW_PREFIX:-flow-$(date +%Y%m%d-%H%M%S)}"
WAIT_ATTEMPTS="${RUSTFS_SITE_REPL_WAIT_ATTEMPTS:-90}"
WAIT_SLEEP_SECONDS="${RUSTFS_SITE_REPL_WAIT_SLEEP_SECONDS:-2}"

SITE1_ENDPOINT="${RUSTFS_SITE1_ENDPOINT:-http://127.0.0.1:9000}"
SITE2_ENDPOINT="${RUSTFS_SITE2_ENDPOINT:-http://127.0.0.1:9010}"
SITE3_ENDPOINT="${RUSTFS_SITE3_ENDPOINT:-http://127.0.0.1:9020}"

command_exists() {
  command -v "$1" >/dev/null 2>&1
}

require_command() {
  if ! command_exists "$1"; then
    echo "missing required command: $1" >&2
    exit 1
  fi
}

checksum_file() {
  if command_exists sha256sum; then
    sha256sum "$1" | awk '{print $1}'
  elif command_exists shasum; then
    shasum -a 256 "$1" | awk '{print $1}'
  else
    echo "missing required command: sha256sum or shasum" >&2
    exit 1
  fi
}

site_endpoint() {
  case "$1" in
    site1) printf '%s\n' "$SITE1_ENDPOINT" ;;
    site2) printf '%s\n' "$SITE2_ENDPOINT" ;;
    site3) printf '%s\n' "$SITE3_ENDPOINT" ;;
    *) echo "unknown site alias: $1" >&2; exit 1 ;;
  esac
}

other_sites() {
  case "$1" in
    site1) printf '%s\n' "site2 site3" ;;
    site2) printf '%s\n' "site1 site3" ;;
    site3) printf '%s\n' "site1 site2" ;;
    *) echo "unknown source site: $1" >&2; exit 1 ;;
  esac
}

wait_for_object() {
  site="$1"
  object="$2"
  attempt=1

  while [ "$attempt" -le "$WAIT_ATTEMPTS" ]; do
    if mc stat "$site/$BUCKET/$object" >/dev/null 2>&1; then
      return 0
    fi
    sleep "$WAIT_SLEEP_SECONDS"
    attempt=$((attempt + 1))
  done

  echo "object was not replicated in time: $site/$BUCKET/$object" >&2
  return 1
}

wait_for_bucket() {
  site="$1"
  bucket="${2:-$BUCKET}"
  attempt=1

  while [ "$attempt" -le "$WAIT_ATTEMPTS" ]; do
    if mc stat "$site/$bucket" >/dev/null 2>&1; then
      return 0
    fi
    sleep "$WAIT_SLEEP_SECONDS"
    attempt=$((attempt + 1))
  done

  echo "bucket was not replicated in time: $site/$bucket" >&2
  return 1
}

wait_for_bucket_delete() {
  site="$1"
  bucket="$2"
  attempt=1

  while [ "$attempt" -le "$WAIT_ATTEMPTS" ]; do
    if result="$(mc stat --json "$site/$bucket" 2>&1)"; then
      :
    else
      case "$result" in
        *NoSuchBucket*) return 0 ;;
      esac
    fi
    sleep "$WAIT_SLEEP_SECONDS"
    attempt=$((attempt + 1))
  done

  echo "bucket deletion was not replicated in time: $site/$bucket" >&2
  return 1
}

require_command mc
require_command dd
require_command awk
require_command date
require_command mktemp
require_command tr
require_command wc

WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/rustfs-site-repl-flow.XXXXXX")"
MC_CONFIG_DIR="$WORK_DIR/mc"
export MC_CONFIG_DIR

cleanup() {
  rm -rf "$WORK_DIR"
}
trap cleanup EXIT INT TERM

mkdir -p "$MC_CONFIG_DIR" "$WORK_DIR/src" "$WORK_DIR/downloads"

for site in site1 site2 site3; do
  mc alias set "$site" "$(site_endpoint "$site")" "$ACCESS_KEY" "$SECRET_KEY" >/dev/null
done

for site in site1 site2 site3; do
  echo "checking S3 API for $site"
  mc ls "$site" >/dev/null
done

echo "ensuring bucket exists on site1: $BUCKET"
mc mb --ignore-existing "site1/$BUCKET" >/dev/null

for site in site1 site2 site3; do
  wait_for_bucket "$site"
done

cat <<'EOF' | while read -r size_mb source_site object_name; do
10 site1 object-010m.bin
25 site2 object-025m.bin
50 site3 object-050m.bin
75 site1 object-075m.bin
100 site2 object-100m.bin
EOF
  src_file="$WORK_DIR/src/$object_name"
  object="$PREFIX/$object_name"

  echo "creating ${size_mb}MiB file: $object_name"
  dd if=/dev/urandom of="$src_file" bs=1048576 count="$size_mb" >/dev/null 2>&1
  src_checksum="$(checksum_file "$src_file")"
  src_bytes="$(wc -c < "$src_file" | tr -d ' ')"

  echo "uploading $object_name to $source_site ($src_bytes bytes)"
  mc cp "$src_file" "$source_site/$BUCKET/$object" >/dev/null

  for site in $(other_sites "$source_site"); do
    wait_for_object "$site" "$object"

    dst_file="$WORK_DIR/downloads/$site-$object_name"
    verified=false
    attempt=1
    while [ "$attempt" -le "$WAIT_ATTEMPTS" ]; do
      rm -f "$dst_file"
      echo "downloading $object_name from $site"
      if mc cp "$site/$BUCKET/$object" "$dst_file" >/dev/null 2>&1; then
        dst_checksum="$(checksum_file "$dst_file")"
        dst_bytes="$(wc -c < "$dst_file" | tr -d ' ')"

        if [ "$dst_checksum" = "$src_checksum" ] && [ "$dst_bytes" = "$src_bytes" ]; then
          verified=true
          break
        fi
      fi

      sleep "$WAIT_SLEEP_SECONDS"
      attempt=$((attempt + 1))
    done

    if [ "$verified" != "true" ]; then
      echo "download verification failed for $site/$BUCKET/$object" >&2
      echo "expected checksum: $src_checksum" >&2
      echo "expected bytes:    $src_bytes" >&2
      exit 1
    fi
  done

  echo "verified replicated downloads for $object_name"
done

echo "creating empty bucket for replicated delete check: $DELETE_BUCKET"
mc mb "site1/$DELETE_BUCKET" >/dev/null

for site in site1 site2 site3; do
  wait_for_bucket "$site" "$DELETE_BUCKET"
done

echo "deleting empty bucket on site1: $DELETE_BUCKET"
mc rb "site1/$DELETE_BUCKET" >/dev/null

for site in site1 site2 site3; do
  wait_for_bucket_delete "$site" "$DELETE_BUCKET"
done

echo "site replication object flow check passed"
echo "bucket: $BUCKET"
echo "prefix: $PREFIX"
