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

APP_DIR=${TT_PHASE1_APP_DIR:?TT_PHASE1_APP_DIR is required}
ENV_FILE=${TT_PHASE1_ENV_FILE:-"$APP_DIR/.env"}
PUBLISHER_NAME=${TT_PHASE1_PUBLISHER_NAME:-timetabler-rb-phase1-publisher}
CONSUMER_NAME=${TT_PHASE1_ENGINE_RESPONSE_CONSUMER_NAME:-tt_response}
PYTHON_BIN=${TT_PHASE1_PYTHON_BIN:-"$APP_DIR/venv/bin/python"}
MAIN_APP=""
PM2_BIN=""
PM2_AVAILABLE="false"

if [[ ! -x "$PYTHON_BIN" ]]; then
  echo "Phase 1 gate: configured virtualenv Python is not executable" >&2
  exit 1
fi
if [[ ! -f "$APP_DIR/manage.py" ]]; then
  echo "Phase 1 gate: Timetabler checkout is invalid" >&2
  exit 1
fi

# shellcheck source=deploy/resolve_phase1_pm2.sh
source "$APP_DIR/deploy/resolve_phase1_pm2.sh"

is_false() {
  case "${1:-}" in
  ""|[Ff][Aa][Ll][Ss][Ee]|0|[Nn][Oo]|[Oo][Ff][Ff]) return 0 ;;
  *) return 1 ;;
  esac
}

default_off_requested() {
  is_false "${RB_INTEGRATION_PUBLISH_ENABLED:-False}" &&
    is_false "${RB_INTEGRATION_ACTIVATION_APPROVED:-False}" &&
    is_false "${RB_INTEGRATION_REVERSE_DELIVERY_ENABLED:-False}"
}

if phase1_resolve_pm2; then
  PM2_BIN=$PHASE1_PM2_BIN
  PM2_AVAILABLE="true"
elif default_off_requested && \
  [[ "${TT_PHASE1_DEFAULT_OFF_PRELOADED:-False}" == "True" ]] && \
  phase1_attest_no_publisher_processes /proc; then
  echo "Phase 1 gate: PM2 unavailable; using attested snapshot-only default-off mode"
else
  echo "Phase 1 gate: PM2 is required unless snapshot-only default-off mode is attested" >&2
  exit 1
fi

resolve_main_app() {
  if [[ -n "${TT_PHASE1_MAIN_PM2_APP:-}" ]]; then
    printf '%s\n' "$TT_PHASE1_MAIN_PM2_APP"
    return
  fi
  PM2_SILENT=true "$PM2_BIN" jlist --silent | \
    "$PYTHON_BIN" "$APP_DIR/deploy/parse_pm2_jlist.py" \
    find-main --app-dir "$APP_DIR" \
      --exclude-name "$PUBLISHER_NAME" \
      --exclude-name "$CONSUMER_NAME"
}

disable_publisher() {
  if [[ "$PM2_AVAILABLE" == "true" ]]; then
    "$PM2_BIN" delete "$PUBLISHER_NAME" >/dev/null 2>&1 || true
  else
    phase1_attest_no_publisher_processes /proc
  fi
  "$PYTHON_BIN" "$APP_DIR/deploy/configure_phase1_publisher.py" \
    --env-file "$ENV_FILE" --disable-only >/dev/null
}

rollback_gate() {
  status=$?
  trap - ERR
  set +e
  echo "Phase 1 gate failed; forcing publication and approval off" >&2
  disable_publisher
  export RB_INTEGRATION_PUBLISH_ENABLED=False
  export RB_INTEGRATION_ACTIVATION_APPROVED=False
  export RB_INTEGRATION_REVERSE_DELIVERY_ENABLED=False
  if [[ "$PM2_AVAILABLE" == "true" ]] && [[ -n "$MAIN_APP" ]]; then
    RB_INTEGRATION_PUBLISH_ENABLED=False \
      RB_INTEGRATION_ACTIVATION_APPROVED=False \
      RB_INTEGRATION_REVERSE_DELIVERY_ENABLED=False \
      "$PM2_BIN" restart "$MAIN_APP" --update-env >/dev/null 2>&1
  fi
  if [[ "$PM2_AVAILABLE" == "true" ]]; then
    "$PM2_BIN" save --force >/dev/null 2>&1
  fi
  exit "$status"
}
trap rollback_gate ERR

if [[ "$PM2_AVAILABLE" == "true" ]]; then
  MAIN_APP=$(resolve_main_app)
fi

# No deploy may leave an earlier publisher running while configuration or code
# changes. The workflow also performs this stop before the base deploy begins.
disable_publisher

"$PYTHON_BIN" "$APP_DIR/deploy/configure_phase1_publisher.py" --env-file "$ENV_FILE"

# Reload the main API after the atomically written environment, then attest the
# exact migrated code/config before deciding whether a publisher may start.
if [[ "$PM2_AVAILABLE" == "true" ]]; then
  "$PM2_BIN" restart "$MAIN_APP" --update-env
else
  default_off_requested
  [[ "${TT_PHASE1_DEFAULT_OFF_PRELOADED:-False}" == "True" ]]
  phase1_attest_no_publisher_processes /proc
fi
cd "$APP_DIR"
"$PYTHON_BIN" manage.py check
"$PYTHON_BIN" manage.py showmigrations api | grep -Fq '[X] 0102_alter_integrationoutbox_dead_letter_at'

resource_bootstrap_requested="false"
projection_revision_requested="false"
case "${TT_PHASE1_RESOURCE_BOOTSTRAP_ENABLED:-False}" in
[Tt][Rr][Uu][Ee]|1|[Yy][Ee][Ss]|[Oo][Nn]) resource_bootstrap_requested="true" ;;
esac
case "${TT_PHASE1_RESOURCE_PROJECTION_REVISION_ENABLED:-False}" in
[Tt][Rr][Uu][Ee]|1|[Yy][Ee][Ss]|[Oo][Nn]) projection_revision_requested="true" ;;
esac

if [[ "$resource_bootstrap_requested" == "true" ]] && \
  [[ "$projection_revision_requested" == "true" ]]; then
  echo "Resource bootstrap and projection revision cannot run together" >&2
  false
fi

if [[ "$resource_bootstrap_requested" == "true" ]]; then
  if ! default_off_requested; then
    echo "Resource bootstrap is allowed only while publication, approval, and reverse are off" >&2
    false
  fi
  "$PYTHON_BIN" manage.py bootstrap_resource_booking_resources \
    --confirm-resource-bootstrap \
    --batch-size "${TT_PHASE1_RESOURCE_BOOTSTRAP_BATCH_SIZE:-100}" \
    --run-id "${TT_PHASE1_RESOURCE_BOOTSTRAP_RUN_ID:?bootstrap run ID is required}"
fi

if [[ "$projection_revision_requested" == "true" ]]; then
  if ! default_off_requested; then
    echo "Resource projection revision is allowed only while publication, approval, and reverse are off" >&2
    false
  fi
  "$PYTHON_BIN" manage.py revise_resource_booking_resource_projection \
    --confirm-resource-projection-revision \
    --operation-key "${TT_PHASE1_RESOURCE_PROJECTION_OPERATION_KEY:?projection revision operation key is required}" \
    --batch-size "${TT_PHASE1_RESOURCE_BOOTSTRAP_BATCH_SIZE:-100}"
fi

case "${RB_INTEGRATION_PUBLISH_ENABLED:-False}" in
[Tt][Rr][Uu][Ee]|1|[Yy][Ee][Ss]|[Oo][Nn])
  "$PYTHON_BIN" manage.py preflight_resource_booking_phase1 --require-enabled
  "$PM2_BIN" start "$PYTHON_BIN" \
    --name "$PUBLISHER_NAME" \
    --cwd "$APP_DIR" \
    --interpreter none \
    --instances 1 \
    --update-env \
    -- manage.py publish_resource_booking_outbox --interval 1

  live=false
  for _attempt in {1..15}; do
    if "$PYTHON_BIN" manage.py probe_resource_booking_publisher --liveness; then
      live=true
      break
    fi
    sleep 2
  done
  if [[ "$live" != "true" ]]; then
    echo "Phase 1 publisher did not become live" >&2
    false
  fi
  publisher_count=$(PM2_SILENT=true "$PM2_BIN" jlist --silent | \
    "$PYTHON_BIN" "$APP_DIR/deploy/parse_pm2_jlist.py" count --name "$PUBLISHER_NAME")
  if [[ "$publisher_count" != "1" ]]; then
    echo "Phase 1 gate requires exactly one supervised publisher" >&2
    false
  fi
  echo "Phase 1 publisher activated after preflight and liveness attestation"
  ;;
*)
  "$PYTHON_BIN" manage.py preflight_resource_booking_phase1
  if [[ "$PM2_AVAILABLE" != "true" ]]; then
    phase1_attest_no_publisher_processes /proc
  fi
  echo "Phase 1 publisher is installed/configured but stopped (default-off gate)"
  ;;
esac

if [[ "$PM2_AVAILABLE" == "true" ]]; then
  "$PM2_BIN" save --force
fi
trap - ERR
