#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"

RUN_ID="${PARRHESIA_NODE_SYNC_E2E_RUN_ID:-local-$(date +%s)}"
RESOURCE="${PARRHESIA_NODE_SYNC_E2E_RESOURCE:-tribes.accounts.user}"
RUNNER_MIX_ENV="${PARRHESIA_NODE_SYNC_E2E_RUNNER_MIX_ENV:-test}"
TMP_DIR="${PARRHESIA_NODE_SYNC_E2E_TMP_DIR:-$(mktemp -d "${TMPDIR:-/tmp}/parrhesia-node-sync-e2e.XXXXXX")}"
STATE_FILE="$TMP_DIR/state.json"
LOG_DIR="$TMP_DIR/logs"
mkdir -p "$LOG_DIR"

SUFFIX="$(basename "$TMP_DIR" | tr -c 'a-zA-Z0-9' '_')"
DB_NAME_A="${PARRHESIA_NODE_SYNC_E2E_DB_A:-parrhesia_node_sync_a_${SUFFIX}}"
DB_NAME_B="${PARRHESIA_NODE_SYNC_E2E_DB_B:-parrhesia_node_sync_b_${SUFFIX}}"

port_in_use() {
  local port="$1"

  if command -v ss >/dev/null 2>&1; then
    ss -ltn "( sport = :${port} )" | tail -n +2 | grep -q .
    return
  fi

  if command -v lsof >/dev/null 2>&1; then
    lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
    return
  fi

  echo "Neither ss nor lsof is available for checking port usage." >&2
  exit 1
}

pick_port() {
  local port

  while true; do
    port="$(( (RANDOM % 10000) + 40000 ))"

    if ! port_in_use "$port"; then
      printf '%s\n' "$port"
      return
    fi
  done
}

NODE_A_PORT="${PARRHESIA_NODE_A_PORT:-$(pick_port)}"
NODE_B_PORT="${PARRHESIA_NODE_B_PORT:-$(pick_port)}"

if [[ "$NODE_A_PORT" == "$NODE_B_PORT" ]]; then
  echo "Node A and Node B ports must differ." >&2
  exit 1
fi

database_url_for() {
  local database_name="$1"
  local pg_user="${PGUSER:-${USER:-agent}}"
  local pg_host="${PGHOST:-localhost}"
  local pg_port="${PGPORT:-5432}"

  if [[ "$pg_host" == /* ]]; then
    if [[ -n "${PGPASSWORD:-}" ]]; then
      printf 'ecto://%s:%s@localhost/%s?socket_dir=%s&port=%s\n' \
        "$pg_user" "$PGPASSWORD" "$database_name" "$pg_host" "$pg_port"
    else
      printf 'ecto://%s@localhost/%s?socket_dir=%s&port=%s\n' \
        "$pg_user" "$database_name" "$pg_host" "$pg_port"
    fi
  else
    if [[ -n "${PGPASSWORD:-}" ]]; then
      printf 'ecto://%s:%s@%s:%s/%s\n' \
        "$pg_user" "$PGPASSWORD" "$pg_host" "$pg_port" "$database_name"
    else
      printf 'ecto://%s@%s:%s/%s\n' \
        "$pg_user" "$pg_host" "$pg_port" "$database_name"
    fi
  fi
}

DATABASE_URL_A="$(database_url_for "$DB_NAME_A")"
DATABASE_URL_B="$(database_url_for "$DB_NAME_B")"
printf -v PROTECTED_FILTERS_JSON '[{"kinds":[5000],"#r":["%s"]}]' "$RESOURCE"

cleanup() {
  if [[ -n "${NODE_A_PID:-}" ]] && kill -0 "$NODE_A_PID" 2>/dev/null; then
    kill "$NODE_A_PID" 2>/dev/null || true
    wait "$NODE_A_PID" 2>/dev/null || true
  fi

  if [[ -n "${NODE_B_PID:-}" ]] && kill -0 "$NODE_B_PID" 2>/dev/null; then
    kill "$NODE_B_PID" 2>/dev/null || true
    wait "$NODE_B_PID" 2>/dev/null || true
  fi

  if [[ "${PARRHESIA_NODE_SYNC_E2E_DROP_DB_ON_EXIT:-1}" == "1" ]]; then
    DATABASE_URL="$DATABASE_URL_A" MIX_ENV=prod mix ecto.drop --quiet --force || true
    DATABASE_URL="$DATABASE_URL_B" MIX_ENV=prod mix ecto.drop --quiet --force || true
  fi

  if [[ "${PARRHESIA_NODE_SYNC_E2E_KEEP_TMP:-0}" != "1" ]]; then
    rm -rf "$TMP_DIR"
  fi
}

trap cleanup EXIT INT TERM

wait_for_health() {
  local port="$1"
  local label="$2"

  for _ in {1..150}; do
    if curl -fsS "http://127.0.0.1:${port}/health" >/dev/null 2>&1; then
      return
    fi

    sleep 0.2
  done

  echo "${label} did not become healthy on port ${port}" >&2
  exit 1
}

setup_database() {
  local database_url="$1"

  DATABASE_URL="$database_url" MIX_ENV=prod mix ecto.drop --quiet --force || true
  DATABASE_URL="$database_url" MIX_ENV=prod mix ecto.create --quiet
  DATABASE_URL="$database_url" MIX_ENV=prod mix ecto.migrate --quiet
}

start_node() {
  local node_name="$1"
  local port="$2"
  local database_url="$3"
  local relay_url="$4"
  local identity_path="$5"
  local sync_path="$6"
  local log_path="$7"

  DATABASE_URL="$database_url" \
    PORT="$port" \
    PARRHESIA_RELAY_URL="$relay_url" \
    PARRHESIA_ACL_PROTECTED_FILTERS="$PROTECTED_FILTERS_JSON" \
    PARRHESIA_IDENTITY_PATH="$identity_path" \
    PARRHESIA_SYNC_PATH="$sync_path" \
    MIX_ENV=prod \
    mix run --no-compile --no-halt >"$log_path" 2>&1 &

  if [[ "$node_name" == "a" ]]; then
    NODE_A_PID=$!
  else
    NODE_B_PID=$!
  fi
}

run_runner() {
  ERL_LIBS="_build/${RUNNER_MIX_ENV}/lib" \
    elixir scripts/node_sync_e2e.exs "$@" --state-file "$STATE_FILE"
}

export DATABASE_URL="$DATABASE_URL_A"
MIX_ENV=prod mix compile
MIX_ENV="$RUNNER_MIX_ENV" mix compile >/dev/null

setup_database "$DATABASE_URL_A"
setup_database "$DATABASE_URL_B"

NODE_A_HTTP_URL="http://127.0.0.1:${NODE_A_PORT}"
NODE_B_HTTP_URL="http://127.0.0.1:${NODE_B_PORT}"
NODE_A_WS_URL="ws://127.0.0.1:${NODE_A_PORT}/relay"
NODE_B_WS_URL="ws://127.0.0.1:${NODE_B_PORT}/relay"

start_node \
  a \
  "$NODE_A_PORT" \
  "$DATABASE_URL_A" \
  "$NODE_A_WS_URL" \
  "$TMP_DIR/node-a-identity.json" \
  "$TMP_DIR/node-a-sync.json" \
  "$LOG_DIR/node-a.log"

start_node \
  b \
  "$NODE_B_PORT" \
  "$DATABASE_URL_B" \
  "$NODE_B_WS_URL" \
  "$TMP_DIR/node-b-identity.json" \
  "$TMP_DIR/node-b-sync.json" \
  "$LOG_DIR/node-b.log"

wait_for_health "$NODE_A_PORT" "Node A"
wait_for_health "$NODE_B_PORT" "Node B"

export PARRHESIA_NODE_SYNC_E2E_RUN_ID="$RUN_ID"
export PARRHESIA_NODE_SYNC_E2E_RESOURCE="$RESOURCE"
export PARRHESIA_NODE_A_HTTP_URL="$NODE_A_HTTP_URL"
export PARRHESIA_NODE_B_HTTP_URL="$NODE_B_HTTP_URL"
export PARRHESIA_NODE_A_WS_URL="$NODE_A_WS_URL"
export PARRHESIA_NODE_B_WS_URL="$NODE_B_WS_URL"
export PARRHESIA_NODE_A_RELAY_AUTH_URL="$NODE_A_WS_URL"
export PARRHESIA_NODE_B_RELAY_AUTH_URL="$NODE_B_WS_URL"
export PARRHESIA_NODE_A_SYNC_URL="$NODE_A_WS_URL"
export PARRHESIA_NODE_B_SYNC_URL="$NODE_B_WS_URL"

run_runner bootstrap

kill "$NODE_B_PID"
wait "$NODE_B_PID" 2>/dev/null || true
unset NODE_B_PID

run_runner publish-resume

start_node \
  b \
  "$NODE_B_PORT" \
  "$DATABASE_URL_B" \
  "$NODE_B_WS_URL" \
  "$TMP_DIR/node-b-identity.json" \
  "$TMP_DIR/node-b-sync.json" \
  "$LOG_DIR/node-b.log"

wait_for_health "$NODE_B_PORT" "Node B"
run_runner verify-resume

run_runner filter-selectivity
run_runner sync-stop-restart
run_runner bidirectional-sync

printf 'node-sync-e2e local run completed\nlogs: %s\n' "$LOG_DIR"
