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

ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE="$ROOT/.env.load"
LABEL=${1:-"resilience-$(date -u +%Y%m%dT%H%M%SZ)"}

if [[ ! -f "$ENV_FILE" ]]; then
  echo "Missing $ENV_FILE. Run scripts/ensure-local-load-env.sh first." >&2
  exit 1
fi

set -a
# shellcheck source=/dev/null
. "$ENV_FILE"
set +a

for name in LOAD_PROJECT LOAD_HOST LOAD_WEB_REPLICAS LOAD_WORKER_REPLICAS \
  LOAD_RESILIENCE_RECOVERY_TIMEOUT_SECONDS \
  LOAD_RESILIENCE_PROBE_INTERVAL_SECONDS \
  LOAD_RESILIENCE_REQUEST_TIMEOUT_SECONDS TRAEFIK_RETRY_ATTEMPTS \
  HTTP_PORT POSTGRES_DB METRICS_TOKEN; do
  if [[ -z "${!name:-}" ]]; then
    echo "$name is missing from .env.load" >&2
    exit 1
  fi
done

if [[ "$LOAD_PROJECT" == "who_need_help" ]]; then
  echo "The resilience profile must not use the staging Compose project." >&2
  exit 1
fi

if [[ ! "$LABEL" =~ ^[A-Za-z0-9._-]+$ ]]; then
  echo "Run label may contain only letters, numbers, dot, underscore, and dash." >&2
  exit 1
fi

for name in LOAD_WEB_REPLICAS LOAD_WORKER_REPLICAS \
  LOAD_RESILIENCE_RECOVERY_TIMEOUT_SECONDS \
  LOAD_RESILIENCE_REQUEST_TIMEOUT_SECONDS TRAEFIK_RETRY_ATTEMPTS; do
  if [[ ! "${!name}" =~ ^[1-9][0-9]*$ ]]; then
    echo "$name must be a positive integer." >&2
    exit 1
  fi
done

if ! awk -v value="$LOAD_RESILIENCE_PROBE_INTERVAL_SECONDS" \
  'BEGIN {exit !(value ~ /^[0-9]+([.][0-9]+)?$/ && value > 0)}'; then
  echo "LOAD_RESILIENCE_PROBE_INTERVAL_SECONDS must be greater than zero." >&2
  exit 1
fi

compose=(
  docker compose
  --env-file "$ENV_FILE"
  -p "$LOAD_PROJECT"
  -f compose.yaml
  -f compose.load.yaml
)

output_dir="$ROOT/output/resilience/$LABEL"
mkdir -p "$output_dir"
chmod 700 "$ROOT/output" "$ROOT/output/resilience" "$output_dir"
run_started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
probe_marker="$output_dir/.probe-running"
probe_log="$output_dir/readiness.jsonl"
touch "$probe_marker"

assert_scope() {
  local container_id=$1
  local expected_service=$2
  local observed_project observed_service

  observed_project=$(
    docker inspect --format '{{index .Config.Labels "com.docker.compose.project"}}' \
      "$container_id"
  )
  observed_service=$(
    docker inspect --format '{{index .Config.Labels "com.docker.compose.service"}}' \
      "$container_id"
  )

  if [[ "$observed_project" != "$LOAD_PROJECT" ||
    "$observed_service" != "$expected_service" ]]; then
    echo "Container scope mismatch for $container_id." >&2
    exit 1
  fi
}

service_ids() {
  "${compose[@]}" ps -q "$1"
}

wait_for_service() {
  local service=$1
  local expected=$2
  local require_health=$3
  local deadline=$((SECONDS + LOAD_RESILIENCE_RECOVERY_TIMEOUT_SECONDS))

  while ((SECONDS < deadline)); do
    mapfile -t ids < <(service_ids "$service")

    if [[ "${#ids[@]}" -eq "$expected" ]]; then
      local ready=true
      local id state health

      for id in "${ids[@]}"; do
        state=$(docker inspect --format '{{.State.Status}}' "$id")
        health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$id")

        if [[ "$state" != "running" ||
          ("$require_health" == "true" && "$health" != "healthy") ]]; then
          ready=false
          break
        fi
      done

      if [[ "$ready" == "true" ]]; then
        return 0
      fi
    fi

    sleep 1
  done

  echo "Timed out waiting for $expected ready $service replicas." >&2
  return 1
}

wait_for_restart() {
  local container_id=$1
  local previous_restarts=$2
  local require_health=$3
  local deadline=$((SECONDS + LOAD_RESILIENCE_RECOVERY_TIMEOUT_SECONDS))

  while ((SECONDS < deadline)); do
    local state restarts health
    state=$(docker inspect --format '{{.State.Status}}' "$container_id")
    restarts=$(docker inspect --format '{{.RestartCount}}' "$container_id")
    health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$container_id")

    if [[ "$state" == "running" && "$restarts" -gt "$previous_restarts" &&
      ("$require_health" == "false" || "$health" == "healthy") ]]; then
      return 0
    fi

    sleep 1
  done

  echo "Timed out waiting for container $container_id to restart." >&2
  return 1
}

wait_for_cluster() {
  local expected_peers=$((LOAD_WEB_REPLICAS + LOAD_WORKER_REPLICAS - 1))
  local deadline=$((SECONDS + LOAD_RESILIENCE_RECOVERY_TIMEOUT_SECONDS))

  while ((SECONDS < deadline)); do
    local target peer_count
    target=$(service_ids web | head -n 1)
    assert_scope "$target" web
    peer_count=$(
      docker exec "$target" /app/bin/who_need_help rpc \
        'IO.puts(length(Node.list()))' 2>/dev/null |
        tail -n 1
    )

    if [[ "$peer_count" == "$expected_peers" ]]; then
      docker exec "$target" /app/bin/who_need_help rpc \
        'IO.inspect(%{node: node(), peers: Node.list(), peer_count: length(Node.list())})'
      return 0
    fi

    sleep 1
  done

  echo "Timed out waiting for all local BEAM replicas to join the cluster." >&2
  return 1
}

sample_readiness() {
  while [[ -e "$probe_marker" ]]; do
    observed_at=$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)
    body_file="$output_dir/readiness-body.$$"
    error_file="$output_dir/readiness-error.$$"
    metrics_headers_file="$output_dir/metrics-headers.$$"
    set +e
    status=$(
      curl --silent --show-error \
        --max-time "$LOAD_RESILIENCE_REQUEST_TIMEOUT_SECONDS" \
        --header "Host: $LOAD_HOST" \
        --output "$body_file" \
        --write-out '%{http_code}' \
        "http://localhost:$HTTP_PORT/healthz/ready" 2>"$error_file"
    )
    curl_status=$?
    set -e

    if [[ "$curl_status" -ne 0 ]]; then
      status=000
    fi

    if [[ -f "$body_file" ]]; then
      body=$(tr -d '\n' <"$body_file")
    else
      body=
    fi

    set +e
    metrics_status=$(
      curl --silent --show-error \
        --max-time "$LOAD_RESILIENCE_REQUEST_TIMEOUT_SECONDS" \
        --header "Host: $LOAD_HOST" \
        --header "Authorization: Bearer $METRICS_TOKEN" \
        --dump-header "$metrics_headers_file" \
        --output /dev/null \
        --write-out '%{http_code}' \
        "http://localhost:$HTTP_PORT/metrics" 2>>"$error_file"
    )
    metrics_curl_status=$?
    set -e

    if [[ "$metrics_curl_status" -ne 0 ]]; then
      metrics_status=000
    fi

    node=$(
      awk '
        BEGIN {IGNORECASE = 1}
        /^x-wnh-node:/ {
          sub(/^[^:]+:[[:space:]]*/, "")
          sub(/\r$/, "")
          print
          exit
        }
      ' "$metrics_headers_file" 2>/dev/null || true
    )

    error=$(tr -d '\n' <"$error_file")
    unlink "$body_file" 2>/dev/null || true
    unlink "$error_file" 2>/dev/null || true
    unlink "$metrics_headers_file" 2>/dev/null || true
    jq -cn \
      --arg observed_at "$observed_at" \
      --arg status "$status" \
      --arg metrics_status "$metrics_status" \
      --arg node "$node" \
      --arg body "$body" \
      --arg error "$error" \
      '{
        observed_at: $observed_at,
        status: $status,
        metrics_status: $metrics_status,
        node: $node,
        body: $body,
        error: $error
      }' \
      >>"$probe_log"
    sleep "$LOAD_RESILIENCE_PROBE_INTERVAL_SECONDS"
  done
}

stop_probe() {
  unlink "$probe_marker" 2>/dev/null || true

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

delete_retry_job() {
  local destination=$1
  local target

  target=$(service_ids worker | head -n 1)
  assert_scope "$target" worker

  docker exec "$target" /app/bin/who_need_help rpc \
    "import Ecto.Query
     run_id = \"$LABEL\"
     {count, _} =
       WhoNeedHelp.Repo.delete_all(
         from job in Oban.Job,
           where:
             job.worker == \"WhoNeedHelp.Workers.LocalRetryProbe\" and
               fragment(\"?->>'run_id'\", job.args) == ^run_id
       )
     IO.puts(count)" >"$destination"
}

restore_topology() {
  "${compose[@]}" up -d --no-deps \
    --scale "web=$LOAD_WEB_REPLICAS" \
    --scale "worker=$LOAD_WORKER_REPLICAS" \
    web worker >/dev/null 2>&1 || true
}

cleanup() {
  local status=$?
  local cleanup_status=0

  trap - EXIT HUP INT TERM
  stop_probe

  set +e
  restore_topology

  wait_for_service worker "$LOAD_WORKER_REPLICAS" false

  if [[ "${retry_job_inserted:-false}" == "true" ]]; then
    delete_retry_job "$output_dir/retry-job-cleanup-on-exit.txt"
    cleanup_status=$?
  fi
  set -e

  if [[ "$cleanup_status" -ne 0 ]]; then
    echo "Resilience probe cleanup failed." >&2
    status=1
  fi

  exit "$status"
}

trap cleanup EXIT HUP INT TERM

wait_for_service web "$LOAD_WEB_REPLICAS" true
wait_for_service worker "$LOAD_WORKER_REPLICAS" false

{
  printf 'observed_at=%s\n' "$run_started_at"
  printf 'load_project=%s\n' "$LOAD_PROJECT"
  printf 'web_replicas=%s\n' "$LOAD_WEB_REPLICAS"
  printf 'worker_replicas=%s\n' "$LOAD_WORKER_REPLICAS"
  printf 'recovery_timeout_seconds=%s\n' "$LOAD_RESILIENCE_RECOVERY_TIMEOUT_SECONDS"
  printf 'probe_interval_seconds=%s\n' "$LOAD_RESILIENCE_PROBE_INTERVAL_SECONDS"
  printf 'request_timeout_seconds=%s\n' "$LOAD_RESILIENCE_REQUEST_TIMEOUT_SECONDS"
  printf 'traefik_retry_attempts=%s\n' "$TRAEFIK_RETRY_ATTEMPTS"
  docker compose version
  docker info --format 'docker_server={{.ServerVersion}}'
} >"$output_dir/environment.txt"

sample_readiness &
probe_pid=$!

web_target=$(service_ids web | head -n 1)
assert_scope "$web_target" web
web_restarts=$(docker inspect --format '{{.RestartCount}}' "$web_target")
docker exec "$web_target" /app/bin/who_need_help rpc ':erlang.halt(1)' \
  >"$output_dir/web-kill.txt" 2>&1 || true
wait_for_restart "$web_target" "$web_restarts" true
docker inspect "$web_target" |
  jq '.[0] | {
    id: .Id,
    project: .Config.Labels["com.docker.compose.project"],
    service: .Config.Labels["com.docker.compose.service"],
    restart_count: .RestartCount,
    state: .State.Status,
    health: .State.Health.Status
  }' >"$output_dir/web-restart.json"

worker_target=$(service_ids worker | head -n 1)
assert_scope "$worker_target" worker
worker_restarts=$(docker inspect --format '{{.RestartCount}}' "$worker_target")
docker exec "$worker_target" /app/bin/who_need_help rpc ':erlang.halt(1)' \
  >"$output_dir/worker-kill.txt" 2>&1 || true
wait_for_restart "$worker_target" "$worker_restarts" false
docker inspect "$worker_target" |
  jq '.[0] | {
    id: .Id,
    project: .Config.Labels["com.docker.compose.project"],
    service: .Config.Labels["com.docker.compose.service"],
    restart_count: .RestartCount,
    state: .State.Status
  }' >"$output_dir/worker-restart.json"

mapfile -t original_web_ids < <(service_ids web)

for container_id in "${original_web_ids[@]}"; do
  assert_scope "$container_id" web
  {
    docker stop --timeout 30 "$container_id"
    docker rm "$container_id"
    "${compose[@]}" up -d --no-deps --scale "web=$LOAD_WEB_REPLICAS" web
  } >>"$output_dir/web-replacements.txt"
  wait_for_service web "$LOAD_WEB_REPLICAS" true
done

mapfile -t original_worker_ids < <(service_ids worker)

for container_id in "${original_worker_ids[@]}"; do
  assert_scope "$container_id" worker
  {
    docker stop --timeout 30 "$container_id"
    docker rm "$container_id"
    "${compose[@]}" up -d --no-deps --scale "worker=$LOAD_WORKER_REPLICAS" worker
  } >>"$output_dir/worker-replacements.txt"
  wait_for_service worker "$LOAD_WORKER_REPLICAS" false
done

wait_for_cluster >"$output_dir/cluster-after-replacements.txt"

COMPOSE_PROJECT_NAME=$LOAD_PROJECT "$ROOT/scripts/verify-realtime-cluster.sh" compose \
  >"$output_dir/pubsub-after-replacements.txt"

worker_target=$(service_ids worker | head -n 1)
assert_scope "$worker_target" worker

docker exec "$worker_target" /app/bin/who_need_help rpc \
  "run_id = \"$LABEL\"
   {:ok, job} =
     %{
       \"confirmation\" => \"isolated-local-resilience-probe\",
       \"run_id\" => run_id
     }
     |> WhoNeedHelp.Workers.LocalRetryProbe.new()
   |> Oban.insert()
   IO.puts(job.id)" >"$output_dir/retry-job-insert.txt"
retry_job_inserted=true

retry_deadline=$((SECONDS + LOAD_RESILIENCE_RECOVERY_TIMEOUT_SECONDS))

while ((SECONDS < retry_deadline)); do
  # The run label is restricted above and passed as a psql value.
  # shellcheck disable=SC2016
  "${compose[@]}" exec -T db sh -c \
    'psql --no-psqlrc --tuples-only --no-align --set ON_ERROR_STOP=1 \
      --set run_id="$1" --username "$POSTGRES_USER" --dbname "$POSTGRES_DB"' \
    sh "$LABEL" >"$output_dir/retry-job.json" <<'SQL'
SELECT json_build_object(
  'id', id,
  'state', state,
  'attempt', attempt,
  'max_attempts', max_attempts,
  'error_count', cardinality(errors),
  'worker', worker
)
FROM oban_jobs
WHERE worker = 'WhoNeedHelp.Workers.LocalRetryProbe'
  AND args->>'run_id' = :'run_id'
ORDER BY id DESC
LIMIT 1;
SQL

  if jq -e '
    .state == "completed" and
    .attempt == 2 and
    .max_attempts == 2 and
    .error_count == 1 and
    .worker == "WhoNeedHelp.Workers.LocalRetryProbe"
  ' "$output_dir/retry-job.json" >/dev/null 2>&1; then
    break
  fi

  sleep 1
done

if ! jq -e '
  .state == "completed" and
  .attempt == 2 and
  .max_attempts == 2 and
  .error_count == 1 and
  .worker == "WhoNeedHelp.Workers.LocalRetryProbe"
' "$output_dir/retry-job.json" >/dev/null; then
  echo "The isolated Oban retry probe did not complete its second attempt." >&2
  exit 1
fi

delete_retry_job "$output_dir/retry-job-cleanup.txt"

if [[ "$(tail -n 1 "$output_dir/retry-job-cleanup.txt")" != "1" ]]; then
  echo "The exact resilience probe job was not removed." >&2
  exit 1
fi
retry_job_inserted=false

stop_probe
probe_pid=

jq -s '{
  samples: length,
  failures: (map(select(.status != "200" or .metrics_status != "200")) | length),
  nodes: (map(.node) | map(select(. != null and . != "")) | unique)
}' "$probe_log" >"$output_dir/readiness-summary.json"

if ! jq -e '.samples > 0 and .failures == 0 and (.nodes | length) >= 2' \
  "$output_dir/readiness-summary.json" >/dev/null; then
  echo "Readiness was unavailable or did not reach multiple web replicas." >&2
  exit 1
fi

"${compose[@]}" ps -a >"$output_dir/compose-after.txt"
"${compose[@]}" logs --since "$run_started_at" proxy web worker \
  >"$output_dir/application.log" 2>&1

trap - EXIT HUP INT TERM
printf 'Resilience evidence: %s\n' "$output_dir"
