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

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

usage() {
  cat <<'EOF'
Usage:
  AGENTTEAM_AUTOMATION_API_URL=http://localhost:3001 \
  MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL=http://127.0.0.1:3001 \
  AGENTTEAM_ACCESS_TOKEN=... \
  WORKSPACE_ID=... \
  RUNNER_DIR=/path/to/actions-runner \
  scripts/meshx-actions-runner-smoke.sh

Required environment:
  RUNNER_DIR           Directory containing official actions/runner config.sh and run.sh.
  AGENTTEAM_AUTOMATION_API_URL
                       AgentTeam gateway/control-plane API URL for authenticated automation REST calls,
                       except configured start-only reuse. MESHX_AUTOMATION_API_URL and MESHX_URL
                       are supported as legacy fallbacks.
  AGENTTEAM_ACCESS_TOKEN    AgentTeam/MeshX bearer token for automation APIs, except configured start-only reuse.
  WORKSPACE_ID         MeshX workspace id, except configured start-only reuse.
  MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL
                       Public MeshX base URL for official runner callbacks when configuring a runner.
                       MESHX_URL is supported as a legacy fallback.

Optional environment:
  OWNER                                      Repository owner. Default: pingcap
  REPO                                       Repository name. Default: agentteam
  AUTOMATION_PATH                            Automation definition path. Default: .agentteam/smoke.yml
  AUTOMATION_REF                               Automation ref. Default: refs/heads/main
  RUNNER_NAME                                MeshX/official runner name. Default: meshx-actions-smoke-<timestamp>
  RUNNER_LABELS                              Comma-separated MeshX runner labels. Default: ubuntu-latest
  RUNS_ON                                    Default automation runs-on when writing the smoke automation definition. Default: ubuntu-latest
  MESHX_AUTOMATION_API_URL                     Compatibility automation API URL fallback when AGENTTEAM_AUTOMATION_API_URL is unset.
  MESHX_URL                                  Legacy fallback for both automation API URL and runner base URL.
  MESHX_AUTOMATION_ACTIONS_RUNNER_WRITE_DEFINITION        Write the default shell-only automation definition before running. Default: 0
  MESHX_ACTIONS_RUNNER_CONFIGURE             Run config.sh before run.sh. Default: 1
  MESHX_ACTIONS_RUNNER_START_ONLY            Start the runner without creating a smoke run. Default: 0
  MESHX_ACTIONS_RUNNER_KEEP_RUNNING          Leave run.sh running after this script exits. Default: 0
  MESHX_ACTIONS_RUNNER_REQUIRE_METADATA      Require completejob metadata in job outputJson. Default: 1
  MESHX_ACTIONS_RUNNER_VALIDATE_CONFIG       Validate existing .runner target when possible. Default: 1
  POLL_TIMEOUT_SECONDS                       Run polling timeout. Default: 180
  POLL_INTERVAL_SECONDS                      Run polling interval. Default: 2

Examples:
  # Run against an existing shell-only automation definition.
  scripts/meshx-actions-runner-smoke.sh

  # Write the default automation definition before starting the smoke.
  MESHX_AUTOMATION_ACTIONS_RUNNER_WRITE_DEFINITION=1 scripts/meshx-actions-runner-smoke.sh

  # Reuse an already configured runner directory.
  MESHX_ACTIONS_RUNNER_CONFIGURE=0 scripts/meshx-actions-runner-smoke.sh

  # Only start a runner for already queued jobs.
  MESHX_ACTIONS_RUNNER_START_ONLY=1 OWNER=ws_... REPO=repo scripts/meshx-actions-runner-smoke.sh

  # Reuse a configured runner without any MeshX API calls.
  MESHX_ACTIONS_RUNNER_START_ONLY=1 MESHX_ACTIONS_RUNNER_CONFIGURE=0 scripts/meshx-actions-runner-smoke.sh
EOF
}

log() {
  printf '%s\n' "$*"
}

die() {
  printf 'ERROR: %s\n' "$*" >&2
  exit 1
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "$1 is required"
}

require_env() {
  [[ -n "${!1:-}" ]] || die "$1 is required"
}

require_value() {
  local name="$1"
  local value="$2"
  [[ -n "$value" ]] || die "$name is required"
}

trim_right_slash() {
  local value="$1"
  while [[ "$value" == */ ]]; do
    value="${value%/}"
  done
  printf '%s' "$value"
}

urlencode() {
  jq -rn --arg value "$1" '$value|@uri'
}

api_request() {
  local method="$1"
  local path="$2"
  local body="${3:-}"
  [[ "$WORKSPACE_ID" =~ ^[A-Za-z0-9_-]{1,128}$ ]] || die "WORKSPACE_ID is not a valid URL path segment"
  [[ "$path" == /api/v1 || "$path" == /api/v1/* ]] || die "automation API path must start with /api/v1"
  path="/api/v1/workspaces/$WORKSPACE_ID${path#/api/v1}"
  local response_file
  response_file="$(mktemp)"
  local status
  local curl_args=(
    -sS
    -o "$response_file"
    -w '%{http_code}'
    -X "$method"
    "$AUTOMATION_API_URL$path"
    -H "Authorization: Bearer $AGENTTEAM_ACCESS_TOKEN"
    -H "X-Workspace-Id: $WORKSPACE_ID"
  )
  if [[ -n "$body" ]]; then
    curl_args+=(-H "Content-Type: application/json" -d "$body")
  fi
  if ! status="$(curl "${curl_args[@]}")"; then
    rm -f "$response_file"
    printf 'ERROR: curl failed for %s %s\n' "$method" "$path" >&2
    return 1
  fi
  if [[ "$status" -lt 200 || "$status" -ge 300 ]]; then
    printf 'ERROR: %s %s returned HTTP %s\n' "$method" "$path" "$status" >&2
    cat "$response_file" >&2
    printf '\n' >&2
    rm -f "$response_file"
    return 1
  fi
  cat "$response_file"
  rm -f "$response_file"
}

labels_json() {
  jq -cn --arg labels "$RUNNER_LABELS" '
    $labels
    | split(",")
    | map(gsub("^\\s+|\\s+$"; ""))
    | map(select(length > 0))
  '
}

configured_runner_url() {
  jq -er '.gitHubUrl // .GitHubUrl // empty' "$RUNNER_DIR/.runner"
}

validate_configured_runner_target() {
  if [[ "${MESHX_ACTIONS_RUNNER_VALIDATE_CONFIG:-1}" == "0" ]]; then
    return
  fi
  if [[ ! -f "$RUNNER_DIR/.runner" ]]; then
    return
  fi
  if [[ -z "${OWNER:-}" || -z "${REPO:-}" || -z "${MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL:-}" ]]; then
    return
  fi
  require_command jq
  local configured_url
  if ! configured_url="$(configured_runner_url)"; then
    die "RUNNER_DIR/.runner is not readable JSON: $RUNNER_DIR/.runner"
  fi
  configured_url="$(trim_right_slash "$configured_url")"
  local expected_url
  expected_url="$(trim_right_slash "$MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL")/$OWNER/$REPO"
  if [[ "$configured_url" != "$expected_url" ]]; then
    die "configured official actions/runner targets $configured_url, expected $expected_url; use a clean RUNNER_DIR or remove the existing runner config before reconfiguring"
  fi
}

default_automation_content() {
  cat <<EOF
name: Smoke
on:
  manual: {}
jobs:
  smoke:
    runs-on: $RUNS_ON
    steps:
      - run: echo hello
EOF
}

runner_log_tail() {
  if [[ -f "$RUNNER_LOG" ]]; then
    printf '\n== actions/runner log tail ==\n' >&2
    tail -n 80 "$RUNNER_LOG" >&2 || true
  fi
}

runner_pid=""
cleanup() {
  if [[ -n "$runner_pid" && "${MESHX_ACTIONS_RUNNER_KEEP_RUNNING:-0}" != "1" ]]; then
    if kill -0 "$runner_pid" >/dev/null 2>&1; then
      kill "$runner_pid" >/dev/null 2>&1 || true
      wait "$runner_pid" >/dev/null 2>&1 || true
    fi
  fi
}
trap cleanup EXIT

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
  usage
  exit 0
fi

MESHX_ACTIONS_RUNNER_START_ONLY="${MESHX_ACTIONS_RUNNER_START_ONLY:-0}"
MESHX_ACTIONS_RUNNER_CONFIGURE="${MESHX_ACTIONS_RUNNER_CONFIGURE:-1}"
require_env RUNNER_DIR

[[ -d "$RUNNER_DIR" ]] || die "RUNNER_DIR does not exist: $RUNNER_DIR"
[[ -x "$RUNNER_DIR/run.sh" ]] || die "RUNNER_DIR/run.sh is missing or not executable"

if [[ "$MESHX_ACTIONS_RUNNER_START_ONLY" == "1" && "$MESHX_ACTIONS_RUNNER_CONFIGURE" == "0" ]]; then
  MESHX_URL="$(trim_right_slash "${MESHX_URL:-}")"
  if [[ -z "${OWNER:-}" && -n "${WORKSPACE_ID:-}" ]]; then
    OWNER="${WORKSPACE_ID}"
  fi
  MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL="$(trim_right_slash "${MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL:-$MESHX_URL}")"
  validate_configured_runner_target
  log "Starting configured official actions/runner"
  cd "$RUNNER_DIR"
  export GITHUB_ACTIONS_RUNNER_FORCE_GHES=1
  exec ./run.sh
fi

require_command curl
require_command jq
require_env AGENTTEAM_ACCESS_TOKEN
require_env WORKSPACE_ID

AUTOMATION_API_URL="$(trim_right_slash "${AGENTTEAM_AUTOMATION_API_URL:-${MESHX_AUTOMATION_API_URL:-${MESHX_URL:-}}}")"
require_value "AGENTTEAM_AUTOMATION_API_URL, MESHX_AUTOMATION_API_URL, or legacy MESHX_URL" "$AUTOMATION_API_URL"
if [[ "$MESHX_ACTIONS_RUNNER_START_ONLY" == "1" ]]; then
  OWNER="${OWNER:-$WORKSPACE_ID}"
  REPO="${REPO:-}"
else
  OWNER="${OWNER:-pingcap}"
  REPO="${REPO:-agentteam}"
fi
AUTOMATION_PATH="${AUTOMATION_PATH:-.agentteam/smoke.yml}"
AUTOMATION_REF="${AUTOMATION_REF:-refs/heads/main}"
RUNNER_NAME="${RUNNER_NAME:-meshx-actions-smoke-$(date +%s)}"
RUNNER_LABELS="${RUNNER_LABELS:-ubuntu-latest}"
RUNS_ON="${RUNS_ON:-ubuntu-latest}"
MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL="$(trim_right_slash "${MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL:-${MESHX_URL:-}}")"
MESHX_AUTOMATION_ACTIONS_RUNNER_WRITE_DEFINITION="${MESHX_AUTOMATION_ACTIONS_RUNNER_WRITE_DEFINITION:-0}"
MESHX_ACTIONS_RUNNER_REQUIRE_METADATA="${MESHX_ACTIONS_RUNNER_REQUIRE_METADATA:-1}"
POLL_TIMEOUT_SECONDS="${POLL_TIMEOUT_SECONDS:-180}"
POLL_INTERVAL_SECONDS="${POLL_INTERVAL_SECONDS:-2}"
RUNNER_WORK_DIR="${RUNNER_WORK_DIR:-_work}"
RUNNER_LOG="${RUNNER_LOG:-$ROOT/.data/meshx/actions-runner-smoke-$RUNNER_NAME.log}"

label_count="$(labels_json | jq 'length')"
[[ "$label_count" -gt 0 ]] || die "RUNNER_LABELS must contain at least one label"

if [[ "$MESHX_ACTIONS_RUNNER_START_ONLY" == "1" ]]; then
  [[ -n "$OWNER" ]] || die "OWNER is required when MESHX_ACTIONS_RUNNER_START_ONLY=1"
  [[ -n "$REPO" ]] || die "REPO is required when MESHX_ACTIONS_RUNNER_START_ONLY=1"
else
  encoded_definition_path="$(urlencode "$AUTOMATION_PATH")"
  encoded_ref="$(urlencode "$AUTOMATION_REF")"

  if [[ "$MESHX_AUTOMATION_ACTIONS_RUNNER_WRITE_DEFINITION" == "1" ]]; then
    log "Writing shell-only automation definition to $OWNER/$REPO:$AUTOMATION_PATH"
    definition_payload="$(
      default_automation_content | jq -Rs \
        --arg message "meshx actions runner smoke automation definition" \
        --arg ref "$AUTOMATION_REF" \
        '{content: ., message: $message, ref: $ref}'
    )"
    api_request PUT "/api/v1/automations/repositories/$OWNER/$REPO/definitions/$encoded_definition_path" "$definition_payload" >/dev/null
  else
    log "Checking automation definition source $OWNER/$REPO:$AUTOMATION_PATH@$AUTOMATION_REF"
    if ! api_request GET "/api/v1/automations/repositories/$OWNER/$REPO/definitions/$encoded_definition_path?ref=$encoded_ref" >/dev/null; then
      die "automation definition source check failed; set MESHX_AUTOMATION_ACTIONS_RUNNER_WRITE_DEFINITION=1 to write the default smoke automation"
    fi
  fi
fi

registration_token=""
if [[ "$MESHX_ACTIONS_RUNNER_CONFIGURE" == "1" ]]; then
  [[ -x "$RUNNER_DIR/config.sh" ]] || die "RUNNER_DIR/config.sh is missing or not executable"
  require_value "MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL or legacy MESHX_URL" "$MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL"
  if [[ -f "$RUNNER_DIR/.runner" ]]; then
    die "RUNNER_DIR is already configured; use a clean runner dir or set MESHX_ACTIONS_RUNNER_CONFIGURE=0 to reuse it"
  fi
  registration_payload="$(
    jq -nc \
      --arg id "$RUNNER_NAME" \
      --arg name "$RUNNER_NAME" \
      --argjson labels "$(labels_json)" \
      '{runnerId: $id, name: $name, labels: $labels}'
  )"
  log "Registering MeshX runner $RUNNER_NAME with labels $RUNNER_LABELS"
  registration_token="$(
    api_request POST "/api/v1/automations/runners/register" "$registration_payload" |
      jq -er '.registrationToken'
  )"

  log "Configuring official actions/runner in $RUNNER_DIR"
  (
    cd "$RUNNER_DIR"
    GITHUB_ACTIONS_RUNNER_FORCE_GHES=1 ./config.sh \
      --unattended \
      --url "$MESHX_AUTOMATION_ACTIONS_RUNNER_BASE_URL/$OWNER/$REPO" \
      --token "$registration_token" \
      --name "$RUNNER_NAME" \
      --runnergroup Default \
      --labels "$RUNNER_LABELS" \
      --work "$RUNNER_WORK_DIR" \
      --replace
  )
else
  log "Skipping config.sh; expecting RUNNER_DIR to already be configured for MeshX"
fi

if [[ "$MESHX_ACTIONS_RUNNER_START_ONLY" == "1" ]]; then
  log "Starting official actions/runner for $OWNER/$REPO with labels $RUNNER_LABELS"
  cd "$RUNNER_DIR"
  export GITHUB_ACTIONS_RUNNER_FORCE_GHES=1
  exec ./run.sh
fi

mkdir -p "$(dirname "$RUNNER_LOG")"
log "Starting official actions/runner; log: $RUNNER_LOG"
(
  cd "$RUNNER_DIR"
  GITHUB_ACTIONS_RUNNER_FORCE_GHES=1 ./run.sh
) >"$RUNNER_LOG" 2>&1 &
runner_pid="$!"

sleep 3
if ! kill -0 "$runner_pid" >/dev/null 2>&1; then
  runner_log_tail
  die "actions/runner exited before the automation run was started"
fi

start_payload="$(jq -nc --arg ref "$AUTOMATION_REF" --arg path "$AUTOMATION_PATH" '{ref: $ref, definitionPath: $path}')"
run_id="$(
  api_request POST "/api/v1/automations/repositories/$OWNER/$REPO/runs" "$start_payload" |
    jq -er '.id'
)"
log "Started automation run $run_id"

deadline=$((SECONDS + POLL_TIMEOUT_SECONDS))
last_status=""
last_jobs=""
while (( SECONDS < deadline )); do
  run_json="$(api_request GET "/api/v1/automations/runs/$run_id")"
  jobs_json="$(api_request GET "/api/v1/automations/runs/$run_id/jobs")"
  status="$(jq -r '.status' <<<"$run_json")"
  jobs_summary="$(jq -r '[.[] | "\(.jobId):\(.status)"] | join(", ")' <<<"$jobs_json")"
  if [[ "$status" != "$last_status" || "$jobs_summary" != "$last_jobs" ]]; then
    log "Run $run_id status=$status jobs=[$jobs_summary]"
    last_status="$status"
    last_jobs="$jobs_summary"
  fi

  case "$status" in
    succeeded)
      if ! jq -e 'length > 0 and all(.[]; .status == "succeeded" or .status == "skipped")' <<<"$jobs_json" >/dev/null; then
        jq . <<<"$jobs_json" >&2
        die "run succeeded but at least one job is not succeeded or skipped"
      fi
      if [[ "$MESHX_ACTIONS_RUNNER_REQUIRE_METADATA" == "1" ]]; then
        metadata_count="$(jq '[.[] | .outputJson // "" | select(test("\"(step_results|annotations|telemetry)\""))] | length' <<<"$jobs_json")"
        if [[ "$metadata_count" -eq 0 ]]; then
          jq . <<<"$jobs_json" >&2
          die "job outputJson does not contain completejob metadata"
        fi
      fi
      log "Smoke passed for run $run_id"
      exit 0
      ;;
    failed | canceled | blocked)
      jq . <<<"$run_json" >&2
      jq . <<<"$jobs_json" >&2
      runner_log_tail
      die "automation run reached terminal status $status"
      ;;
  esac

  if ! kill -0 "$runner_pid" >/dev/null 2>&1; then
    runner_log_tail
    die "actions/runner exited before automation run $run_id reached a terminal status"
  fi
  sleep "$POLL_INTERVAL_SECONDS"
done

jq . <<<"$(api_request GET "/api/v1/automations/runs/$run_id")" >&2
jq . <<<"$(api_request GET "/api/v1/automations/runs/$run_id/jobs")" >&2
runner_log_tail
die "timed out waiting for automation run $run_id after ${POLL_TIMEOUT_SECONDS}s"
