#!/bin/sh
set -e

# 1) Normalize command:
# - No arguments: default to execute rustfs with DATA_VOLUMES
# - First argument starts with '-': treat as rustfs arguments, auto-prefix rustfs
# - First argument is 'rustfs': replace with absolute path to avoid PATH interference
# - Otherwise: treat as full rustfs arguments (e.g., /data paths)
if [ $# -eq 0 ]; then
  set -- /usr/bin/rustfs
elif [ "${1#-}" != "$1" ]; then
  set -- /usr/bin/rustfs "$@"
elif [ "$1" = "rustfs" ]; then
  shift
  set -- /usr/bin/rustfs "$@"
elif [ "$1" = "/usr/bin/rustfs" ]; then
  : # already normalized
elif [ "$1" = "cargo" ]; then
  : # Pass through cargo command as-is
else
  set -- /usr/bin/rustfs "$@"
fi

DEFAULT_ROOT_CREDENTIAL="rustfsadmin"

resolve_credential_source() {
  CREDENTIAL_ENV_NAME="$1"
  CREDENTIAL_FILE_ENV_NAME="$2"
  CREDENTIAL_VALUE_OPTION="$3"
  CREDENTIAL_FILE_OPTION="$4"
  shift 4

  CREDENTIAL_DIRECT_SET=false
  CREDENTIAL_DIRECT_VALUE=""
  CREDENTIAL_FILE_SET=false
  CREDENTIAL_FILE_VALUE=""

  # ${VAR+x} distinguishes unset from present-but-empty: an env var explicitly
  # set to "" (e.g. an unexpanded compose interpolation) is a malformed value
  # that must hit the empty-value hard failure, not the missing-credential
  # warning — the binary would otherwise run with an empty root credential.
  eval "CREDENTIAL_ENV_PRESENT=\${$CREDENTIAL_ENV_NAME+x}"
  eval "CREDENTIAL_ENV_VALUE=\${$CREDENTIAL_ENV_NAME:-}"
  eval "CREDENTIAL_ENV_FILE_PRESENT=\${$CREDENTIAL_FILE_ENV_NAME+x}"
  eval "CREDENTIAL_ENV_FILE_VALUE=\${$CREDENTIAL_FILE_ENV_NAME:-}"

  if [ -n "$CREDENTIAL_ENV_PRESENT" ]; then
    CREDENTIAL_DIRECT_SET=true
    CREDENTIAL_DIRECT_VALUE="$CREDENTIAL_ENV_VALUE"
  fi

  if [ -n "$CREDENTIAL_ENV_FILE_PRESENT" ]; then
    CREDENTIAL_FILE_SET=true
    CREDENTIAL_FILE_VALUE="$CREDENTIAL_ENV_FILE_VALUE"
  fi

  while [ "$#" -gt 0 ]; do
    arg="$1"
    case "$arg" in
      --)
        break
        ;;
      --"$CREDENTIAL_VALUE_OPTION"=*)
        CREDENTIAL_DIRECT_SET=true
        CREDENTIAL_DIRECT_VALUE="${arg#*=}"
        ;;
      --"$CREDENTIAL_VALUE_OPTION")
        shift
        if [ "$#" -eq 0 ]; then
          echo "error:--$CREDENTIAL_VALUE_OPTION requires a value."
          return
        fi
        CREDENTIAL_DIRECT_SET=true
        CREDENTIAL_DIRECT_VALUE="$1"
        ;;
      --"$CREDENTIAL_FILE_OPTION"=*)
        CREDENTIAL_FILE_SET=true
        CREDENTIAL_FILE_VALUE="${arg#*=}"
        ;;
      --"$CREDENTIAL_FILE_OPTION")
        shift
        if [ "$#" -eq 0 ]; then
          echo "error:--$CREDENTIAL_FILE_OPTION requires a value."
          return
        fi
        CREDENTIAL_FILE_SET=true
        CREDENTIAL_FILE_VALUE="$1"
        ;;
    esac

    shift
  done

  if [ "$CREDENTIAL_DIRECT_SET" = "true" ] && [ "$CREDENTIAL_FILE_SET" = "true" ]; then
    echo "conflict"
    return
  fi

  if [ "$CREDENTIAL_DIRECT_SET" = "true" ]; then
    echo "value:$CREDENTIAL_DIRECT_VALUE"
    return
  fi

  if [ "$CREDENTIAL_FILE_SET" = "true" ]; then
    echo "file:$CREDENTIAL_FILE_VALUE"
    return
  fi

  echo "missing"
}

# Mirrors the binary's .trim() so the empty/default checks below agree with
# what the server will actually use: strips CR (CRLF-edited files) and
# surrounding blanks. Comparison only — the value passed to the binary is
# untouched.
trim_credential() {
  printf '%s' "$1" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
}

# Credential policy: images ship no baked-in credentials, but default or
# missing credentials only WARN — the container still starts (the binary falls
# back to its built-in default, and warns when both keys end up default).
# Hard failures (ERROR, exit 1) are reserved for malformed configuration
# (conflicting sources, unreadable files, empty values). The binary also accepts credentials via
# legacy/compat envs (RUSTFS_ROOT_*, MINIO_*) that this script does not
# inspect, so the missing-credential warning is phrased conditionally.
validate_credential_source() {
  CREDENTIAL_NAME="$1"
  FILE_NAME="$2"
  ALIAS_HINT="$3"
  CREDENTIAL_SOURCE="$4"

  case "$CREDENTIAL_SOURCE" in
    error:*)
      echo "ERROR: ${CREDENTIAL_SOURCE#error:}" >&2
      exit 1
      ;;
    conflict)
      echo "ERROR: Set either $CREDENTIAL_NAME or $FILE_NAME, not both." >&2
      exit 1
      ;;
    missing)
      echo "WARNING: $CREDENTIAL_NAME or $FILE_NAME is not set; unless credentials are provided via another supported source (e.g. $ALIAS_HINT), rustfs falls back to its built-in default credential. Set non-default credentials for production deployments." >&2
      ;;
    value:*)
      CREDENTIAL_VALUE=$(trim_credential "${CREDENTIAL_SOURCE#value:}")
      if [ -z "$CREDENTIAL_VALUE" ]; then
        echo "ERROR: $CREDENTIAL_NAME must not be empty." >&2
        exit 1
      fi
      if [ "$CREDENTIAL_VALUE" = "$DEFAULT_ROOT_CREDENTIAL" ]; then
        echo "WARNING: $CREDENTIAL_NAME uses the default $DEFAULT_ROOT_CREDENTIAL credential. Set non-default credentials for production deployments; with an all-default pair, multi-node clusters additionally need RUSTFS_RPC_SECRET to derive internode RPC auth." >&2
      fi
      ;;
    file:*)
      CREDENTIAL_FILE="${CREDENTIAL_SOURCE#file:}"
      if [ -z "$CREDENTIAL_FILE" ]; then
        echo "ERROR: $FILE_NAME must not be empty." >&2
        exit 1
      fi
      if [ ! -r "$CREDENTIAL_FILE" ]; then
        echo "ERROR: $FILE_NAME points to an unreadable file." >&2
        exit 1
      fi
      # `read` fails at EOF-without-newline but still fills the variable;
      # keep the partial line so newline-less secret files stay valid.
      IFS= read -r CREDENTIAL_FILE_CONTENT < "$CREDENTIAL_FILE" || :
      CREDENTIAL_FILE_CONTENT=$(trim_credential "$CREDENTIAL_FILE_CONTENT")
      if [ -z "$CREDENTIAL_FILE_CONTENT" ]; then
        echo "ERROR: $FILE_NAME must not be empty." >&2
        exit 1
      fi
      if [ "$CREDENTIAL_FILE_CONTENT" = "$DEFAULT_ROOT_CREDENTIAL" ]; then
        echo "WARNING: $FILE_NAME contains the default $DEFAULT_ROOT_CREDENTIAL credential. Set non-default credentials for production deployments; with an all-default pair, multi-node clusters additionally need RUSTFS_RPC_SECRET to derive internode RPC auth." >&2
      fi
      ;;
  esac
}

if [ "$1" = "/usr/bin/rustfs" ]; then
  ACCESS_SOURCE=$(resolve_credential_source "RUSTFS_ACCESS_KEY" "RUSTFS_ACCESS_KEY_FILE" "access-key" "access-key-file" "$@")
  SECRET_SOURCE=$(resolve_credential_source "RUSTFS_SECRET_KEY" "RUSTFS_SECRET_KEY_FILE" "secret-key" "secret-key-file" "$@")
  validate_credential_source "RUSTFS_ACCESS_KEY" "RUSTFS_ACCESS_KEY_FILE" "RUSTFS_ROOT_USER or MINIO_ROOT_USER" "$ACCESS_SOURCE"
  validate_credential_source "RUSTFS_SECRET_KEY" "RUSTFS_SECRET_KEY_FILE" "RUSTFS_ROOT_PASSWORD or MINIO_ROOT_PASSWORD" "$SECRET_SOURCE"
fi

# 2) Process data volumes (separate from log directory)
DATA_VOLUMES=""
process_data_volumes() {
  VOLUME_RAW="${RUSTFS_VOLUMES:-/data}"
  # Convert comma/tab to space
  VOLUME_LIST_RAW=$(echo "$VOLUME_RAW" | tr ',\t' ' ')
  
  # Manually expand {N...M} ranges since sh doesn't support brace expansion on
  # variables. A single token can carry MORE than one range — the distributed
  # form "http://rustfs{1...4}:9000/data/rustfs{0...3}" has two — so expand the
  # FIRST range in each token and re-scan until no ranges remain. Operating on
  # only the first brace (via #*{ / %%}* rather than ##*} / %}) is what keeps
  # multi-range tokens intact; the previous single-pass expander collapsed them
  # to "http://rustfs1" and silently dropped the disk path.
  VOLUME_LIST="$VOLUME_LIST_RAW"
  while echo "$VOLUME_LIST" | grep -E -q "\{[0-9]+\.\.\.?[0-9]+\}"; do
      NEXT_LIST=""
      for vol in $VOLUME_LIST; do
          if echo "$vol" | grep -E -q "\{[0-9]+\.\.\.?[0-9]+\}"; then
               PREFIX=${vol%%\{*}
               REST=${vol#*\}}
               RANGE=${vol#*\{}
               RANGE=${RANGE%%\}*}
               RANGE=$(echo "$RANGE" | sed 's/\.\.\./../')
               START=${RANGE%%..*}
               END=${RANGE##*..}

               # Check if START and END are numbers
               if [ "$START" -eq "$START" ] 2>/dev/null && [ "$END" -eq "$END" 2>/dev/null ]; then
                   i=$START
                   while [ "$i" -le "$END" ]; do
                     NEXT_LIST="$NEXT_LIST ${PREFIX}${i}${REST}"
                     i=$((i+1))
                   done
               else
                   # Fallback if not numbers
                   NEXT_LIST="$NEXT_LIST $vol"
               fi
          else
               NEXT_LIST="$NEXT_LIST $vol"
          fi
      done
      VOLUME_LIST="$NEXT_LIST"
  done

  # CREATE_DIRS holds every local filesystem path that must exist before
  # startup. DATA_VOLUMES holds only the paths that are also appended as CLI
  # args (bare absolute paths). Distributed URL-form endpoints are read from
  # RUSTFS_VOLUMES by rustfs directly, so they must NOT be appended as args,
  # but their local path component still has to exist on disk — otherwise
  # LocalDisk init aborts with VolumeNotFound (rustfs no longer auto-creates
  # the disk root).
  CREATE_DIRS=""
  for vol in $VOLUME_LIST; do
    case "$vol" in
      /*)
        DATA_VOLUMES="$DATA_VOLUMES $vol"
        CREATE_DIRS="$CREATE_DIRS $vol"
        ;;
      *://*)
        # e.g. http://node0:9000/data/rustfs0 -> /data/rustfs0
        vol_rest=${vol#*://}
        case "$vol_rest" in
          */*) CREATE_DIRS="$CREATE_DIRS /${vol_rest#*/}" ;;
          *)   : ;; # URL without a path component; nothing local to create
        esac
        ;;
      *)  : ;; # skip anything else we don't recognize
    esac
  done

  echo "Initializing data directories:$CREATE_DIRS"
  for vol in $CREATE_DIRS; do
    if [ ! -d "$vol" ]; then
      echo "  mkdir -p $vol"
      mkdir -p "$vol"
      # If target user is specified, try to set directory owner to that user (non-recursive to avoid large disk overhead)
      if [ -n "$RUSTFS_UID" ] && [ -n "$RUSTFS_GID" ]; then
        chown "$RUSTFS_UID:$RUSTFS_GID" "$vol" 2>/dev/null || true
      elif [ -n "$RUSTFS_USERNAME" ] && [ -n "$RUSTFS_GROUPNAME" ]; then
        chown "$RUSTFS_USERNAME:$RUSTFS_GROUPNAME" "$vol" 2>/dev/null || true
      fi
    fi
  done
}

# 3) Process log directory (separate from data volumes)
process_log_directory() {
  # Output logs to stdout
  if [ -z "$RUSTFS_OBS_LOG_DIRECTORY" ]; then
    echo "OBS log directory not configured and logs outputs to stdout"
    return
  fi

  # Output logs to remote endpoint
  if [ "${RUSTFS_OBS_LOG_DIRECTORY}" != "${RUSTFS_OBS_LOG_DIRECTORY#*://}" ]; then
    echo "Output logs to remote endpoint"
    return
  fi

  # Outputs logs to local directory
  LOG_DIR="${RUSTFS_OBS_LOG_DIRECTORY}"
  
  echo "Initializing log directory: $LOG_DIR"
  if [ ! -d "$LOG_DIR" ]; then
    echo "  mkdir -p $LOG_DIR"
    mkdir -p "$LOG_DIR"
    # If target user is specified, try to set directory owner to that user (non-recursive to avoid large disk overhead)
    if [ -n "$RUSTFS_UID" ] && [ -n "$RUSTFS_GID" ]; then
      chown "$RUSTFS_UID:$RUSTFS_GID" "$LOG_DIR" 2>/dev/null || true
    elif [ -n "$RUSTFS_USERNAME" ] && [ -n "$RUSTFS_GROUPNAME" ]; then
      chown "$RUSTFS_USERNAME:$RUSTFS_GROUPNAME" "$LOG_DIR" 2>/dev/null || true
    fi
  fi
}

# Execute the separate processes
process_data_volumes
process_log_directory

# 5) Append DATA_VOLUMES only if no data paths in arguments
# Check if any argument looks like a data path (starts with / and not an option)
HAS_DATA_PATH=false
for arg in "$@"; do
  case "$arg" in
    /usr/bin/rustfs) continue ;;
    -*) continue ;;
    /*) HAS_DATA_PATH=true; break ;;
  esac
done

if [ "$HAS_DATA_PATH" = "false" ] && [ -n "$DATA_VOLUMES" ]; then
  echo "Starting: $* $DATA_VOLUMES"
  set -- "$@" $DATA_VOLUMES
else
  echo "Starting: $*"
fi

exec "$@"
