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

die() { printf 'error: %s\n' "$*" >&2; exit 2; }
roles=(victim-read victim-mixed aggressor-scan)
case_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
repo_root="$(cd "$case_dir/../.." && pwd -P)"

usage() { printf 'usage: %s init <experiment-id> [all|role] | run <experiment-id>\n' "$0" >&2; exit 2; }
valid_id() { [[ "$1" =~ ^[a-z][a-z0-9-]{0,62}$ ]]; }
sha256() { shasum -a 256 "$1" | awk '{print $1}'; }
need_env() { [[ -n "${!1:-}" ]] || die "$1 is required"; }
manifest() { printf '%s/runs/%s-%s/attempt-001/run-profile-manifest.json' "$repo_root" "$1" "$2"; }
attempt() { dirname "$(dirname "$(manifest "$1" "$2")")"; }
positive_seconds() { [[ "$2" =~ ^[1-9][0-9]*$ ]] || die "$1 must be a positive integer"; }

load_env() {
  [[ -n "${ENV_FILE:-}" ]] || die 'ENV_FILE is required'
  [[ -f "$ENV_FILE" ]] || die "ENV_FILE does not exist: $ENV_FILE"
  # The binding file is operator-owned shell assignments, never committed.
  set -a; source "$ENV_FILE"; set +a
  need_env TIPOC_BIN; need_env TIPOC_BUILD_RECEIPT
  [[ -x "$TIPOC_BIN" ]] || die "TIPOC_BIN is not executable"
  [[ -f "$TIPOC_BUILD_RECEIPT" ]] || die "TIPOC_BUILD_RECEIPT does not exist"
  "$TIPOC_BIN" run --help >/dev/null
  "$repo_root/scripts/rerun/run-again.sh" --help >/dev/null
}

run_recent_sequence() {
  local experiment="$1" mf="$2" root control ready_timeout baseline_seconds measurement_seconds native_warmup deadline
  root="$(dirname "$mf")"; control="$repo_root/output/three-cluster-interference/$experiment/operation-sequence"
  mkdir -p "$control"
  ready_timeout="${RECENT_READY_TIMEOUT_SECONDS:-180}"
  baseline_seconds="${RECENT_BASELINE_SECONDS:-180}"
  measurement_seconds="${RECENT_MEASUREMENT_SECONDS:-180}"
  positive_seconds RECENT_READY_TIMEOUT_SECONDS "$ready_timeout"
  positive_seconds RECENT_BASELINE_SECONDS "$baseline_seconds"
  positive_seconds RECENT_MEASUREMENT_SECONDS "$measurement_seconds"
  native_warmup="$(jq -er '.runtime.warmup_seconds' "$mf")" || return 1
  [[ "$native_warmup" =~ ^[0-9]+$ ]] || die 'compiled native warmup must be a non-negative integer'

  deadline=$((SECONDS + ready_timeout))
  until [[ -f "$root/run/operation-control-endpoint.json" ]]; do
    (( SECONDS < deadline )) || die 'recent operation control endpoint did not become ready'
    sleep 1
  done
  sleep "$native_warmup"
  sleep "$baseline_seconds"

  "$TIPOC_BIN" ops securities/tpce/recent_trade_status --action start --manifest "$mf" --wait --timeout 5m --request-id "$experiment-recent-start" >"$control/start.log" 2>&1 || return 1
  sleep "$measurement_seconds"
  "$TIPOC_BIN" ops securities/tpce/recent_trade_status --action add-feature-index --manifest "$mf" --wait --timeout 5m --request-id "$experiment-recent-add" >"$control/add-feature-index.log" 2>&1 || return 1
  sleep "$measurement_seconds"
  "$TIPOC_BIN" ops securities/tpce/recent_trade_status --action drop-feature-index --manifest "$mf" --wait --timeout 5m --request-id "$experiment-recent-drop" >"$control/drop-feature-index.log" 2>&1 || return 1
  sleep "$measurement_seconds"
  "$TIPOC_BIN" ops securities/tpce/recent_trade_status --action stop --manifest "$mf" --wait --timeout 5m --request-id "$experiment-recent-stop" >"$control/stop.log" 2>&1 || return 1
}

role_env() {
  case "$1" in
    victim-read) echo VICTIM_READ ;;
    victim-mixed) echo VICTIM_MIXED ;;
    aggressor-scan) echo AGGRESSOR_SCAN ;;
    *) die "unknown role: $1" ;;
  esac
}

render_spec() {
  local experiment="$1" role="$2" prefix sql_var pd_var sql pd target template output
  prefix="$(role_env "$role")"; sql_var="${prefix}_SQL_ENDPOINT"; pd_var="${prefix}_PD_ENDPOINT"
  sql="${!sql_var}"; pd="${!pd_var}"
  target="$(attempt "$experiment" "$role")"; template="$case_dir/configs/$role.yaml.tpl"
  if [[ "$role" == aggressor-scan ]]; then
    case "${AGGRESSOR_OPERATION:-big-query}" in
      big-query) ;;
      recent-trade-status) template="$case_dir/configs/aggressor-recent-trade-status.yaml.tpl" ;;
      *) die 'AGGRESSOR_OPERATION must be big-query or recent-trade-status' ;;
    esac
  fi
  output="$3"
  [[ "$sql" != *$'\n'* && "$pd" != *$'\n'* ]] || die 'endpoint must not contain a newline'
  sed -e "s|{{RUN_ID}}|$experiment-$role|g" \
      -e "s|{{ATTEMPT_ROOT}}|$target|g" \
      -e "s|{{SQL_ENDPOINT}}|${sql//|/\\|}|g" \
      -e "s|{{PD_ENDPOINT}}|${pd//|/\\|}|g" "$template" >"$output"
}

check_targets() {
  local seen='' role prefix value_var value
  for role in "${roles[@]}"; do
    prefix="$(role_env "$role")"; need_env "${prefix}_SQL_ENDPOINT"; need_env "${prefix}_PD_ENDPOINT"
    value_var="${prefix}_SQL_ENDPOINT"; value="${!value_var}"
    [[ ",$seen," != *",$value,"* ]] || die 'the three SQL targets must be distinct'
    seen+="${seen:+,}$value"
  done
}

init_role() {
  local experiment="$1" role="$2" control spec root
  root="$(attempt "$experiment" "$role")"; [[ ! -e "$root" ]] || die "attempt already exists: $root"
  control="$repo_root/output/three-cluster-interference/$experiment/resolved-specs"
  mkdir -p "$control"
  spec="$control/$role.yaml"
  render_spec "$experiment" "$role" "$spec"
  "$TIPOC_BIN" init --spec "$spec" --out "$root" --attempt-id attempt-001 --remote-bootstrap plan
  [[ -f "$(manifest "$experiment" "$role")" ]] || die "init did not create manifest for $role"
}

run_all() {
  local experiment="$1" role mf status=0 pids=() lock
  lock="$repo_root/output/three-cluster-interference/$experiment/execution.lock"
  mkdir -p "$(dirname "$lock")"
  mkdir "$lock" 2>/dev/null || die "an execution is already active for $experiment"
  trap 'rmdir "$lock" 2>/dev/null || true' EXIT
  for role in "${roles[@]}"; do
    mf="$(manifest "$experiment" "$role")"; [[ -f "$mf" ]] || die "missing manifest for $role"
    [[ -f "$(dirname "$mf")/../prepare/prepare-receipt.json" ]] || die "missing native prepare receipt for $role"
    "$TIPOC_BIN" run preflight --manifest "$mf" >"$(dirname "$mf")/run/preflight-receipt.json"
    jq -e '.status == "clean_run"' "$(dirname "$mf")/run/preflight-receipt.json" >/dev/null || die "preflight is not clean_run for $role"
  done
  for role in "${roles[@]}"; do
    mf="$(manifest "$experiment" "$role")"
    "$TIPOC_BIN" run --manifest "$mf" >"$(dirname "$mf")/run/case-run.log" 2>&1 & pids+=("$!")
  done
  if [[ "${AGGRESSOR_OPERATION:-big-query}" == recent-trade-status ]]; then
    if ! run_recent_sequence "$experiment" "$(manifest "$experiment" aggressor-scan)"; then status=1; fi
  fi
  for pid in "${pids[@]}"; do wait "$pid" || status=1; done
  return "$status"
}

[[ $# -ge 2 ]] || usage
action="$1"; experiment="$2"; valid_id "$experiment" || die 'experiment id must be lowercase letters, digits, and hyphens'
load_env; check_targets
case "$action" in
  init)
    selected="${3:-all}"; [[ $# -le 3 ]] || usage
    if [[ "$selected" == all ]]; then for role in "${roles[@]}"; do init_role "$experiment" "$role"; done
    else init_role "$experiment" "$selected"; fi
    ;;
  run) [[ $# -eq 2 ]] || usage; run_all "$experiment" ;;
  *) usage ;;
esac
