#!/usr/bin/env bash
# Shared helpers — source this file from the other sandbox scripts.

# Print an error and stop.
fail() {
  echo "ERROR: $*" >&2
  exit 1
}

# Make `jq` resolve to the bundled binary in bin/ when the system has none —
# so non-technical users don't have to install it themselves. No-op if jq is
# already on the PATH, and harmless to call more than once (idempotent).
_ensure_jq() {
  command -v jq >/dev/null 2>&1 && return

  local root_dir asset target os arch
  root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
  os="$(uname -s)"
  arch="$(uname -m)"

  case "$os" in
    Darwin)
      case "$arch" in
        arm64) asset="jq-macos-arm64" ;;
        *) asset="jq-macos-amd64" ;;
      esac
      target="$root_dir/bin/jq"
      ;;
    Linux)
      case "$arch" in
        aarch64 | arm64) asset="jq-linux-arm64" ;;
        *) asset="jq-linux-amd64" ;;
      esac
      target="$root_dir/bin/jq"
      ;;
    MINGW* | MSYS* | CYGWIN*)
      asset="jq-windows-amd64.exe"
      target="$root_dir/bin/jq.exe"
      ;;
    *)
      fail "jq is required and no bundled binary is available for $os/$arch — install it from https://jqlang.org/download"
      ;;
  esac

  if [ ! -f "$target" ]; then
    [ -f "$root_dir/bin/$asset" ] ||
      fail "jq is required — install it from https://jqlang.org/download"
    cp "$root_dir/bin/$asset" "$target"
    chmod +x "$target" 2>/dev/null || true
  fi

  export PATH="$root_dir/bin:$PATH"
}

_ensure_jq

# Load the sandbox configuration: config.env at the root of this folder, with
# SANDBOX_* variables already in the environment taking precedence over it.
#
# Sourcing an env file overwrites whatever the caller exported, which would make
# `SANDBOX_ACTIVITY_DELAY=2 bash publish-consignment.sh` silently do nothing as
# soon as that key is also present in config.env. So the SANDBOX_* values the
# script started with are captured first and re-applied afterwards: the file is
# the default, the environment wins. Only the SANDBOX_ namespace is treated this
# way — SUPPLY_CHAIN_* is shared with the node and a stray value in the shell
# should not quietly redirect a script to a different node or account.
_load_env() {
  local env_file
  env_file="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/config.env"

  local inline_overrides=()
  local name
  for name in $(compgen -e | grep '^SANDBOX_' || true); do
    inline_overrides+=("$name=${!name}")
  done

  if [ -f "$env_file" ]; then
    set -a
    # shellcheck disable=SC1090
    source "$env_file"
    set +a
  fi

  # Guarded because expanding an empty array under `set -u` is an unbound
  # variable error in bash 3.2, which is what macOS ships.
  local override
  if [ ${#inline_overrides[@]} -gt 0 ]; then
    for override in "${inline_overrides[@]}"; do
      export "${override?}"
    done
  fi
}

_require() {
  local var
  for var in "$@"; do
    if [ -z "${!var:-}" ]; then
      fail "Missing required env var: $var (set it in config.env)"
    fi
  done
}

# Resolve the route the published consignment travels, defaulting to Calais →
# Felixstowe. The codes are what Border Agencies and Location Operators are matched
# on, so they are validated before anything is published rather than leaving a
# half-published consignment behind.
# Sets PORT_OF_{EXIT,ENTRY}_{NAME,CODE}.
_load_route() {
  PORT_OF_EXIT_NAME="${SANDBOX_PORT_OF_EXIT_NAME:-Calais}"
  PORT_OF_EXIT_CODE="${SANDBOX_PORT_OF_EXIT_CODE:-FRCQF}"
  PORT_OF_ENTRY_NAME="${SANDBOX_PORT_OF_ENTRY_NAME:-Felixstowe}"
  PORT_OF_ENTRY_CODE="${SANDBOX_PORT_OF_ENTRY_CODE:-GBFXT}"

  # Same shape the node validates with (deriveLocodeCountry in supply-chain-models):
  # two letters of country followed by three alphanumerics.
  local var
  for var in PORT_OF_EXIT_CODE PORT_OF_ENTRY_CODE; do
    if [[ ! "${!var}" =~ ^[A-Z]{2}[A-Z0-9]{3}$ ]]; then
      fail "SANDBOX_${var} must be a 5-character UN/LOCODE, e.g. GBFXT — got '${!var}'"
    fi
  done

  export PORT_OF_EXIT_NAME PORT_OF_EXIT_CODE PORT_OF_ENTRY_NAME PORT_OF_ENTRY_CODE
}

# jq builds the body so quotes/backslashes in the password aren't mangled.
credentials_body() {
  jq -nc --arg email "$1" --arg password "$2" '{email: $email, password: $password}'
}

# Log in and export TOKEN and ORG_ID.
login() {
  _require SUPPLY_CHAIN_NODE_URL SUPPLY_CHAIN_USER_NAME \
    SUPPLY_CHAIN_USER_PASSWORD SUPPLY_CHAIN_ORGANIZATION_IDENTITY

  local response
  response=$(curl -s -D - -X POST "${SUPPLY_CHAIN_NODE_URL}/authentication/login" \
    -H "Content-Type: application/json" \
    -d "$(credentials_body "$SUPPLY_CHAIN_USER_NAME" "$SUPPLY_CHAIN_USER_PASSWORD")") \
    || fail "Cannot reach ${SUPPLY_CHAIN_NODE_URL} — is the node running?"

  TOKEN=$(echo "$response" | grep -i "set-cookie" | grep -o "access_token=[^;]*" | cut -d= -f2 | tr -d '\r' || true)

  if [ -z "$TOKEN" ]; then
    fail "Login failed for ${SUPPLY_CHAIN_USER_NAME}: $(echo "$response" | tail -1)"
  fi

  ORG_ID="$SUPPLY_CHAIN_ORGANIZATION_IDENTITY"
  export TOKEN ORG_ID
}

# POST an ActivityStreams activity to the dataspace inbox.
# Usage: notify_inbox "<activity-json>" — sets INBOX_STATUS, INBOX_RESPONSE and INBOX_RESULT.
notify_inbox() {
  local activity="$1"
  local response_file="${TMPDIR:-/tmp}/twin-sandbox-inbox-response.json"

  INBOX_STATUS=$(curl -s -o "$response_file" -w "%{http_code}" \
    -X POST "${SUPPLY_CHAIN_NODE_URL}/supply-chain/data-space/inbox" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer ${TOKEN}" \
    -d "$activity" || true)
  INBOX_STATUS="${INBOX_STATUS:-000}"

  INBOX_RESPONSE=$(cat "$response_file" 2>/dev/null || true)
  # The inbox answers 200/201 even when processing rejects the activity —
  # the real outcome is the status field in the body ("completed" or "error").
  INBOX_RESULT=$(echo "$INBOX_RESPONSE" | jq -r '.status // empty' 2>/dev/null || true)
}

# Whether the last notify_inbox call actually succeeded.
inbox_ok() {
  { [ "$INBOX_STATUS" = "200" ] || [ "$INBOX_STATUS" = "201" ]; } && [ "$INBOX_RESULT" != "error" ]
}
