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

mode="owned"
test_command="owned-fast-test"
clippy_command="owned-clippy-rbe"
build_command="owned-build"
dry_run=0
split_mode="${BUILDBUDDY_CHANGED_GATE_SPLIT:-auto}"
clippy_only=0
tests_only=0
paths=()

usage() {
  cat <<'EOF'
usage: buildbuddy-changed-gate [--owned|--affected] [--local-test] [--clippy-only|--tests-only] [--parallel-split|--serial-split] [--dry-run] <changed-path>...

Runs the selected BuildBuddy test/build and clippy lanes for the supplied
changed paths. Small exact lanes run in parallel; broad package-suite lanes run
serially by default to avoid duplicating first-touch remote work. Use --affected
for reverse-dependency confidence and --local-test when the selected test lane is
already warm locally. Use --dry-run to print the selected Bazel targets without
running Bazel.
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --owned)
      mode="owned"
      shift
      ;;
    --affected)
      mode="affected"
      shift
      ;;
    --local-test)
      if [[ "${mode}" == "owned" ]]; then
        test_command="owned-fast-test-local"
      else
        test_command="affected-fast-test-local"
      fi
      shift
      ;;
    --dry-run)
      dry_run=1
      shift
      ;;
    --clippy-only)
      clippy_only=1
      shift
      ;;
    --tests-only)
      tests_only=1
      shift
      ;;
    --parallel-split)
      split_mode="parallel"
      shift
      ;;
    --serial-split)
      split_mode="serial"
      shift
      ;;
    -h | --help)
      usage
      exit 0
      ;;
    --)
      shift
      while [[ $# -gt 0 ]]; do
        paths+=("$1")
        shift
      done
      ;;
    -*)
      echo "unknown argument: $1" >&2
      usage >&2
      exit 2
      ;;
    *)
      paths+=("$1")
      shift
      ;;
  esac
done

if [[ "${mode}" == "affected" ]]; then
  clippy_command="affected-clippy-rbe"
  build_command="affected-build"
  if [[ "${test_command}" == "owned-fast-test" ]]; then
    test_command="affected-fast-test"
  elif [[ "${test_command}" == "owned-fast-test-local" ]]; then
    test_command="affected-fast-test-local"
  fi
fi

if [[ "${#paths[@]}" -eq 0 ]]; then
  echo "error: at least one changed path is required" >&2
  usage >&2
  exit 2
fi

case "${split_mode}" in
  auto|parallel|serial) ;;
  *)
    echo "error: BUILDBUDDY_CHANGED_GATE_SPLIT must be auto, parallel, or serial" >&2
    exit 2
    ;;
esac

workspace_root="$(git rev-parse --show-toplevel)"
cd "${workspace_root}"

if [[ "${clippy_only}" -eq 1 && "${tests_only}" -eq 1 ]]; then
  echo "error: --clippy-only and --tests-only are mutually exclusive" >&2
  usage >&2
  exit 2
fi

temp_root="$(mktemp -d "${TMPDIR:-/tmp}/meerkat-bb-changed-gate.XXXXXX")"
cleanup() {
  chmod -R u+w "${temp_root}" 2>/dev/null || true
  rm -rf "${temp_root}"
}
trap cleanup EXIT

summarize_log() {
  local name="$1"
  local status="$2"
  local log="$3"
  if [[ "${status}" == "0" ]]; then
    printf 'PASS %s\n' "${name}"
  else
    printf 'FAIL(%s) %s\n' "${status}" "${name}"
  fi
  grep -E 'INFO: Elapsed time:|INFO: Build completed|Executed |ERROR:|FAILED' "${log}" | tail -8 || true
}

run_lane() {
  local lane="$1"
  local command="$2"
  local log="$3"
  shift 3
  (
    lane_prefix="${RUST_LANE_ID:-${BUILDBUDDY_AGENT_LANE:-${CODEX_AGENT_ID:-changed-gate-$$}}}"
    export RUST_LANE_ID="${lane_prefix}-${lane}"
    export BUILDBUDDY_BAZEL_COMMAND="${command}"
    ./scripts/buildbuddy-bazel-poc "${paths[@]}" "$@"
  ) >"${log}" 2>&1
}

test_log="${temp_root}/test.log"
clippy_log="${temp_root}/clippy.log"
combined_log="${temp_root}/combined.log"
test_status_file="${temp_root}/test.status"
clippy_status_file="${temp_root}/clippy.status"
combined_status_file="${temp_root}/combined.status"

selector_mode_args=()
if [[ "${mode}" == "owned" ]]; then
  selector_mode_args+=(--owned)
else
  selector_mode_args+=(--affected)
fi

test_targets="$(./scripts/bazel-affected-fast-tests.mjs "${selector_mode_args[@]}" --test --empty-if-no-labels "${paths[@]}")"
build_targets="$(./scripts/bazel-affected-fast-tests.mjs "${selector_mode_args[@]}" --build "${paths[@]}")"
clippy_targets="$(./scripts/bazel-affected-fast-tests.mjs "${selector_mode_args[@]}" --clippy "${paths[@]}")"

target_count() {
  local count=0
  local target
  for target in $1; do
    count=$((count + 1))
  done
  printf '%s\n' "${count}"
}

split_execution="parallel"
if [[ "${split_mode}" == "serial" ]]; then
  split_execution="serial"
elif [[ "${split_mode}" == "auto" ]]; then
  clippy_target_count="$(target_count "${clippy_targets}")"
  if [[ "${test_targets}" == *":fast_tests"* ||
    "${build_targets}" == *":fast_tests"* ||
    "${clippy_target_count}" -ge "${BUILDBUDDY_CHANGED_GATE_SERIAL_THRESHOLD:-8}" ]]; then
    split_execution="serial"
  fi
fi

if [[ "${dry_run}" -eq 1 ]]; then
  printf 'BuildBuddy changed gate dry-run (%s)\n' "${mode}"
  printf 'paths:\n'
  printf '  %s\n' "${paths[@]}"
  printf 'test-command: %s\n' "${test_command}"
  printf 'build-command: %s\n' "${build_command}"
  printf 'clippy-command: %s\n' "${clippy_command}"
  printf 'test-targets: %s\n' "${test_targets:-<none>}"
  printf 'build-targets: %s\n' "${build_targets:-<none>}"
  printf 'clippy-targets: %s\n' "${clippy_targets:-<none>}"
  if [[ "${clippy_only}" -eq 1 ]]; then
    printf 'plan: clippy-only\n'
  elif [[ "${tests_only}" -eq 1 && -z "${test_targets}" ]]; then
    printf 'plan: build-only\n'
  elif [[ "${tests_only}" -eq 1 ]]; then
    printf 'plan: test-only\n'
  elif [[ -n "${test_targets}" && "${test_command}" != *-local && "${test_targets}" == "${clippy_targets}" ]]; then
    printf 'plan: combined test+clippy\n'
  elif [[ -z "${test_targets}" && "${build_targets}" == "${clippy_targets}" ]]; then
    printf 'plan: combined build+clippy\n'
  elif [[ -z "${test_targets}" ]]; then
    printf 'plan: split build + clippy\n'
    printf 'split-execution: %s\n' "${split_execution}"
  else
    printf 'plan: split test + clippy\n'
    printf 'split-execution: %s\n' "${split_execution}"
  fi
  exit 0
fi

start_seconds="$(date +%s)"

if [[ "${clippy_only}" -eq 1 ]]; then
  set +e
  run_lane clippy "${clippy_command}" "${clippy_log}" --jobs=64 --color=no --curses=no
  echo "$?" >"${clippy_status_file}"
  set -e

  end_seconds="$(date +%s)"
  clippy_status="$(cat "${clippy_status_file}" 2>/dev/null || echo 1)"
  printf 'BuildBuddy changed gate (%s, clippy-only) wall: %ss\n' "${mode}" "$((end_seconds - start_seconds))"
  summarize_log "${clippy_command}" "${clippy_status}" "${clippy_log}"
  if [[ "${clippy_status}" != "0" ]]; then
    exit 1
  fi
  exit 0
fi

if [[ "${tests_only}" -eq 1 ]]; then
  if [[ -z "${test_targets}" ]]; then
    set +e
    run_lane build "${build_command}" "${test_log}" --jobs=64 --color=no --curses=no
    echo "$?" >"${test_status_file}"
    set -e
  else
    set +e
    run_lane test "${test_command}" "${test_log}" --jobs=64
    echo "$?" >"${test_status_file}"
    set -e
  fi

  end_seconds="$(date +%s)"
  test_status="$(cat "${test_status_file}" 2>/dev/null || echo 1)"
  printf 'BuildBuddy changed gate (%s, tests-only) wall: %ss\n' "${mode}" "$((end_seconds - start_seconds))"
  if [[ -z "${test_targets}" ]]; then
    summarize_log "${build_command}" "${test_status}" "${test_log}"
  else
    summarize_log "${test_command}" "${test_status}" "${test_log}"
  fi
  if [[ "${test_status}" != "0" ]]; then
    exit 1
  fi
  exit 0
fi

if [[ "${test_command}" != *-local && -n "${test_targets}" && "${test_targets}" == "${clippy_targets}" ]]; then
  (
    set +e
    run_lane combined "${test_command}" "${combined_log}" \
      --jobs=64 \
      --color=no \
      --curses=no \
      --aspects="@rules_rust//rust:defs.bzl%rust_clippy_aspect" \
      --output_groups="+clippy_checks" \
      --@rules_rust//rust/settings:clippy_flag=-Dwarnings
    echo "$?" >"${combined_status_file}"
  ) &
  combined_pid="$!"

  wait "${combined_pid}" || true

  end_seconds="$(date +%s)"
  combined_status="$(cat "${combined_status_file}" 2>/dev/null || echo 1)"

  printf 'BuildBuddy changed gate (%s, combined) wall: %ss\n' "${mode}" "$((end_seconds - start_seconds))"
  summarize_log "${test_command}+${clippy_command}" "${combined_status}" "${combined_log}"

  if [[ "${combined_status}" != "0" ]]; then
    exit 1
  fi
  exit 0
fi

if [[ -z "${test_targets}" ]]; then
  if [[ "${build_targets}" == "${clippy_targets}" ]]; then
    (
      set +e
      run_lane combined-build "${build_command}" "${combined_log}" \
        --jobs=64 \
        --color=no \
        --curses=no \
        --aspects="@rules_rust//rust:defs.bzl%rust_clippy_aspect" \
        --output_groups="+clippy_checks" \
        --@rules_rust//rust/settings:clippy_flag=-Dwarnings
      echo "$?" >"${combined_status_file}"
    ) &
    combined_pid="$!"

    wait "${combined_pid}" || true

    end_seconds="$(date +%s)"
    combined_status="$(cat "${combined_status_file}" 2>/dev/null || echo 1)"

    printf 'BuildBuddy changed gate (%s, build+clippy) wall: %ss\n' "${mode}" "$((end_seconds - start_seconds))"
    summarize_log "${build_command}+${clippy_command}" "${combined_status}" "${combined_log}"

    if [[ "${combined_status}" != "0" ]]; then
      exit 1
    fi
    exit 0
  fi

  run_primary_lane() {
    run_lane build "${build_command}" "${test_log}" --jobs=64 --color=no --curses=no
  }
else
  run_primary_lane() {
    run_lane test "${test_command}" "${test_log}" --jobs=64
  }
fi

if [[ "${split_execution}" == "serial" ]]; then
  set +e
  run_primary_lane
  echo "$?" >"${test_status_file}"
  run_lane clippy "${clippy_command}" "${clippy_log}" --jobs=64 --color=no --curses=no
  echo "$?" >"${clippy_status_file}"
  set -e
else
  (
    set +e
    run_primary_lane
    echo "$?" >"${test_status_file}"
  ) &
  test_pid="$!"

  (
    set +e
    run_lane clippy "${clippy_command}" "${clippy_log}" --jobs=64 --color=no --curses=no
    echo "$?" >"${clippy_status_file}"
  ) &
  clippy_pid="$!"

  wait "${test_pid}" || true
  wait "${clippy_pid}" || true
fi

end_seconds="$(date +%s)"
test_status="$(cat "${test_status_file}" 2>/dev/null || echo 1)"
clippy_status="$(cat "${clippy_status_file}" 2>/dev/null || echo 1)"

printf 'BuildBuddy changed gate (%s, split-%s) wall: %ss\n' "${mode}" "${split_execution}" "$((end_seconds - start_seconds))"
if [[ -z "${test_targets}" ]]; then
  summarize_log "${build_command}" "${test_status}" "${test_log}"
else
  summarize_log "${test_command}" "${test_status}" "${test_log}"
fi
summarize_log "${clippy_command}" "${clippy_status}" "${clippy_log}"

if [[ "${test_status}" != "0" || "${clippy_status}" != "0" ]]; then
  exit 1
fi
