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

usage() {
  cat <<'EOF' >&2
usage: agentteam-staging-activate-release.sh <bundle.tar.gz> <checksums.txt> <release-id>

Environment overrides:
  AGENTTEAM_STAGING_ROOT                (default: /opt/staging-agentteam)
  AGENTTEAM_STAGING_ENV_FILE            (default: /etc/staging-agentteam/staging.env)
  AGENTTEAM_STAGING_SERVICE_NAME        (default: agentteam-staging)
  AGENTTEAM_STAGING_OWNER               (default: inferred from current release ownership, else agentteam)
  AGENTTEAM_STAGING_GROUP               (default: $AGENTTEAM_STAGING_OWNER)
  AGENTTEAM_STAGING_HEALTHZ_URL         (default: http://127.0.0.1:3001/healthz)
  AGENTTEAM_STAGING_READYZ_URL          (default: http://127.0.0.1:3001/readyz)
  AGENTTEAM_STAGING_APP_URL             (default: unset; skip public URL check)
  AGENTTEAM_STAGING_HEALTHCHECK_ATTEMPTS (default: 30)
  AGENTTEAM_STAGING_HEALTHCHECK_INTERVAL_SEC (default: 2)

This script accepts either release-bundle web layout:
  bin/agentteam-server
  bin/agentteam-schema
  bin/agentteam-tidb-admin
  release.env   (optional; currently supports AGENTTEAM_DAEMON_MIN_VERSION)
  web/index.html
  web/dist/index.html
EOF
}

log() {
  printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S%z')" "$*"
}

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

on_error() {
  local exit_code="$?"
  local line="$1"
  local cmd="$2"
  fail "agentteam staging activate release failed at line ${line}: ${cmd} (exit=${exit_code})"
}

trap 'on_error ${LINENO} "${BASH_COMMAND}"' ERR

require_command() {
  local cmd="$1"
  command -v "$cmd" >/dev/null 2>&1 || fail "missing required command: $cmd"
}

require_file() {
  local path="$1"
  [[ -f "$path" ]] || fail "required file not found: $path"
}

require_executable() {
  local path="$1"
  [[ -x "$path" ]] || fail "required executable not found: $path"
}

trim_left() {
  local value="$1"
  value="${value#"${value%%[![:space:]]*}"}"
  printf '%s' "$value"
}

trim_right() {
  local value="$1"
  value="${value%"${value##*[![:space:]]}"}"
  printf '%s' "$value"
}

read_env_value() {
  local env_file_path="$1"
  local key="$2"

  local line=''
  while IFS= read -r line || [[ -n "$line" ]]; do
    line="${line%$'\r'}"
    line="$(trim_left "$line")"
    if [[ -z "$line" || "$line" == \#* ]]; then
      continue
    fi

    if [[ "$line" == export[[:space:]]* ]]; then
      line="${line#export}"
      line="$(trim_left "$line")"
    fi

    if [[ "$line" != "$key="* ]]; then
      continue
    fi

    local raw="${line#"$key="}"
    raw="$(trim_left "$raw")"
    if [[ -z "$raw" ]]; then
      return 0
    fi

    local first="${raw:0:1}"
    if [[ "$first" == "'" ]]; then
      raw="${raw:1}"
      if [[ "$raw" != *"'"* ]]; then
        echo "error: failed to parse $key (unterminated single quote) in env file: $env_file_path" >&2
        exit 1
      fi
      printf '%s' "${raw%%"'"*}"
      return 0
    fi
    if [[ "$first" == '"' ]]; then
      raw="${raw:1}"
      if [[ "$raw" != *'"'* ]]; then
        echo "error: failed to parse $key (unterminated double quote) in env file: $env_file_path" >&2
        exit 1
      fi
      printf '%s' "${raw%%\"*}"
      return 0
    fi

    raw="${raw%%#*}"
    raw="$(trim_right "$raw")"
    printf '%s' "$raw"
    return 0
  done <"$env_file_path"

  return 1
}

upsert_env_value() {
  local env_file_path="$1"
  local key="$2"
  local value="$3"
  local temp_path

  temp_path="$(mktemp "${env_file_path}.tmp.XXXXXX")"
  awk -v key="$key" -v value="$value" '
    function matches(line) {
      return line ~ "^[[:space:]]*(export[[:space:]]+)?" key "="
    }

    BEGIN {
      replaced = 0
    }

    {
      original = $0
      stripped = original
      sub(/\r$/, "", stripped)
      if (matches(stripped)) {
        if (!replaced) {
          print key "=" value
          replaced = 1
        }
        next
      }
      print original
    }

    END {
      if (!replaced) {
        print key "=" value
      }
    }
  ' "$env_file_path" >"$temp_path"

  mv "$temp_path" "$env_file_path"
}

apply_release_env_metadata() {
  local release_dir="$1"
  local release_env_path="${release_dir}/release.env"
  local daemon_min_version=''
  local display_value=''

  if [[ ! -f "$release_env_path" ]]; then
    return 0
  fi

  if daemon_min_version="$(read_env_value "$release_env_path" 'AGENTTEAM_DAEMON_MIN_VERSION')"; then
    upsert_env_value "$ENV_FILE" 'AGENTTEAM_DAEMON_MIN_VERSION' "$daemon_min_version"
    display_value="$daemon_min_version"
    if [[ -z "$display_value" ]]; then
      display_value='<empty>'
    fi
    log "updated ${ENV_FILE} with AGENTTEAM_DAEMON_MIN_VERSION=${display_value} from release metadata"
  fi
}

ensure_staging_environment() {
  upsert_env_value "$ENV_FILE" 'AGENTTEAM_ENV' 'staging'
  log "updated ${ENV_FILE} with AGENTTEAM_ENV=staging for staging deployment"
}

detect_owner_group() {
  local probe

  for probe in "$CURRENT_LINK" "$RELEASES_DIR" "$ROOT"; do
    if stat -Lc '%U:%G' "$probe" >/dev/null 2>&1; then
      stat -Lc '%U:%G' "$probe"
      return 0
    fi
  done

  printf '%s\n' 'agentteam:agentteam'
}

extract_expected_sha() {
  local checksum_path="$1"
  local archive_name="$2"

  awk -v name="$archive_name" '
    {
      entry=$2
      sub(/^\*/, "", entry)
      gsub(/^.*\//, "", entry)
      if (entry == name) {
        print $1
        exit
      }
    }
  ' "$checksum_path"
}

wait_http_ready() {
  local label="$1"
  local url="$2"
  local attempts="$3"
  local interval_sec="$4"

  local i
  for i in $(seq 1 "$attempts"); do
    if curl --fail --silent --show-error --max-time 2 "$url" >/dev/null; then
      return 0
    fi
    sleep "$interval_sec"
  done

  fail "${label} check failed after ${attempts} attempts: ${url}"
}

normalize_release_layout() {
  local release_dir="$1"
  local web_dir="$release_dir/web"
  local root_index="$web_dir/index.html"
  local legacy_dist="$web_dir/dist"
  local legacy_index="$legacy_dist/index.html"

  if [[ -f "$root_index" && ! -e "$legacy_dist" ]]; then
    ln -sfn . "$legacy_dist"
  fi

  [[ -f "$root_index" || -f "$legacy_index" ]] || \
    fail "release archive missing web/index.html or web/dist/index.html"
}

dump_service_diagnostics() {
  local service_name="$1"

  log "service ${service_name} is not active after restart; dumping diagnostics"
  systemctl status --no-pager -l "$service_name" || true
  if command -v journalctl >/dev/null 2>&1; then
    journalctl -u "$service_name" -n 80 --no-pager -l || true
  fi
}

if [[ $# -ne 3 ]]; then
  usage
  exit 1
fi

readonly BUNDLE_PATH="$1"
readonly CHECKSUM_PATH="$2"
readonly RELEASE_ID="$3"
readonly ROOT="${AGENTTEAM_STAGING_ROOT:-/opt/staging-agentteam}"
readonly RELEASES_DIR="${ROOT}/releases"
readonly CURRENT_LINK="${ROOT}/current"
readonly ENV_FILE="${AGENTTEAM_STAGING_ENV_FILE:-/etc/staging-agentteam/staging.env}"
readonly SERVICE_NAME="${AGENTTEAM_STAGING_SERVICE_NAME:-agentteam-staging}"
readonly DEFAULT_OWNER_GROUP="$(detect_owner_group)"
readonly DEFAULT_OWNER="${DEFAULT_OWNER_GROUP%%:*}"
readonly DEFAULT_GROUP="${DEFAULT_OWNER_GROUP#*:}"
readonly OWNER="${AGENTTEAM_STAGING_OWNER:-$DEFAULT_OWNER}"
readonly GROUP="${AGENTTEAM_STAGING_GROUP:-${AGENTTEAM_STAGING_OWNER:-$DEFAULT_GROUP}}"
readonly HEALTHZ_URL="${AGENTTEAM_STAGING_HEALTHZ_URL:-http://127.0.0.1:3001/healthz}"
readonly READYZ_URL="${AGENTTEAM_STAGING_READYZ_URL:-http://127.0.0.1:3001/readyz}"
readonly APP_URL="${AGENTTEAM_STAGING_APP_URL:-}"
readonly HEALTHCHECK_ATTEMPTS="${AGENTTEAM_STAGING_HEALTHCHECK_ATTEMPTS:-30}"
readonly HEALTHCHECK_INTERVAL_SEC="${AGENTTEAM_STAGING_HEALTHCHECK_INTERVAL_SEC:-2}"
readonly TARGET_DIR="${RELEASES_DIR}/${RELEASE_ID}"

staging_dir="${RELEASES_DIR}/.${RELEASE_ID}.staging.$$"
cleanup_dir="$staging_dir"
cleanup() {
  if [[ -n "${cleanup_dir}" && -d "${cleanup_dir}" ]]; then
    rm -rf "${cleanup_dir}"
  fi
}
trap cleanup EXIT

for cmd in awk curl ln mkdir mktemp mv rm sha256sum stat systemctl tar chown; do
  require_command "$cmd"
done

require_file "$BUNDLE_PATH"
require_file "$CHECKSUM_PATH"
require_file "$ENV_FILE"

archive_name="$(basename "$BUNDLE_PATH")"
expected_sha="$(extract_expected_sha "$CHECKSUM_PATH" "$archive_name")"
[[ -n "$expected_sha" ]] || fail "checksum entry not found for ${archive_name} in ${CHECKSUM_PATH}"
actual_sha="$(sha256sum "$BUNDLE_PATH" | awk '{print $1}')"
[[ "$expected_sha" == "$actual_sha" ]] || fail "checksum mismatch for ${archive_name}"

mkdir -p "$RELEASES_DIR"
rm -rf "$staging_dir" "$TARGET_DIR"
mkdir -p "$staging_dir"

log "extracting ${archive_name} into ${staging_dir}"
tar -xzf "$BUNDLE_PATH" -C "$staging_dir"

require_executable "$staging_dir/bin/agentteam-server"
require_executable "$staging_dir/bin/agentteam-schema"
require_executable "$staging_dir/bin/agentteam-tidb-admin"
normalize_release_layout "$staging_dir"
apply_release_env_metadata "$staging_dir"
ensure_staging_environment

log "stopping service ${SERVICE_NAME} before schema cutover"
systemctl stop "$SERVICE_NAME"

log "running schema and workspace placement cutover gates with ${ENV_FILE}"
(
  set -a
  # shellcheck disable=SC1090
  source "$ENV_FILE"
  set +a

  [[ -n "${AGENTTEAM_DATABASE_DSN:-${AGENTTEAM_CORE_STORAGE_DSN:-}}" ]] || fail "AGENTTEAM_DATABASE_DSN is required in ${ENV_FILE}"

  "$staging_dir/bin/agentteam-schema" init
  "$staging_dir/bin/agentteam-tidb-admin" workspace-placement-backfill --workspace-group-id workspace-default
  "$staging_dir/bin/agentteam-tidb-admin" workspace-placement-verify --workspace-group-id workspace-default
)

mv "$staging_dir" "$TARGET_DIR"
cleanup_dir=""

ln -sfn "$TARGET_DIR" "$CURRENT_LINK"
chown -R "${OWNER}:${GROUP}" "$TARGET_DIR"
chown -h "${OWNER}:${GROUP}" "$CURRENT_LINK"

log "restarting service ${SERVICE_NAME}"
systemctl restart "$SERVICE_NAME"
if ! systemctl is-active --quiet "$SERVICE_NAME"; then
  dump_service_diagnostics "$SERVICE_NAME"
  fail "service not active after restart: ${SERVICE_NAME}"
fi

log "checking health endpoint ${HEALTHZ_URL}"
wait_http_ready "health" "$HEALTHZ_URL" "$HEALTHCHECK_ATTEMPTS" "$HEALTHCHECK_INTERVAL_SEC"

log "checking ready endpoint ${READYZ_URL}"
wait_http_ready "ready" "$READYZ_URL" "$HEALTHCHECK_ATTEMPTS" "$HEALTHCHECK_INTERVAL_SEC"

if [[ -n "$APP_URL" ]]; then
  log "checking app endpoint ${APP_URL}"
  wait_http_ready "app" "$APP_URL" "$HEALTHCHECK_ATTEMPTS" "$HEALTHCHECK_INTERVAL_SEC"
fi

rm -f "$BUNDLE_PATH" "$CHECKSUM_PATH"

echo "agentteam staging deploy success: ${RELEASE_ID}"
