#!/usr/bin/env bash

set -Eeuo pipefail
export LANG=C
umask 077

FRAMEWORK_ARCHIVE=/root/zhct-emqx-framework.tar.gz
FRAMEWORK_SHA256=489294eb3a5c362a5f403518d9486dc61b26330e98923b006cfd792dfda842cd
BUNDLE_ROOT=/opt/zhct-deploy
COOKIE_FILE=/etc/zhct/secrets/emqx.cluster-cookie
COOKIE_TRANSFER=/root/emqx.cluster-cookie.transfer
EVIDENCE_ROOT=/root/q141-143-evidence
TARGET_PORT_RE=':(1883|8083|18083|4370|5370|8883|8084)([[:space:]]|$)'
QID=${1:-}
EXPECTED_COOKIE_SHA=${2:-}

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

case "${QID}" in
  Q141)
    EXPECTED_HOST=sqjcmqtt01
    EXPECTED_IP=10.2.203.108
    NODE_KEY=emqx01
    EXPECTED_NODE='emqx@10.2.203.108'
    TEMPLATE_SHA=9ffed09e41e384092d469b3c1e39fe5b05aee160c32eda69c80e5af87ce01441
    FINAL_MARK=Q141_EMQX01_ACTIVATION_PASS
    ;;
  Q142)
    EXPECTED_HOST=sqjcmqtt02
    EXPECTED_IP=10.2.203.109
    NODE_KEY=emqx02
    EXPECTED_NODE='emqx@10.2.203.109'
    TEMPLATE_SHA=5b8a58d992022caa7ee01329a1514b590697fa1adc970db33db25433280e240e
    FINAL_MARK=Q142_EMQX02_ACTIVATION_PASS
    ;;
  Q143)
    EXPECTED_HOST=sqjcmqtt03
    EXPECTED_IP=10.2.203.110
    NODE_KEY=emqx03
    EXPECTED_NODE='emqx@10.2.203.110'
    TEMPLATE_SHA=b3f89486324a77a51902164e5cfaddabe97010c1c41ee2f9ccfd9b947d4dbc2c
    FINAL_MARK=Q143_EMQX03_ACTIVATION_PASS
    ;;
  *) die "usage: $0 Q141|Q142|Q143 [expected-cookie-sha256]" ;;
esac

RUN_ID="${QID,,}-$(date +%Y%m%dT%H%M%S%z)"
EVIDENCE_DIR="${EVIDENCE_ROOT}/${RUN_ID}"
ROLLBACK_ROOT="/root/.${RUN_ID}.rollback"
SUCCESS=0
COOKIE_CREATED=0
PRE_CONFIG_PRESENT=0
PRE_NODE_ENV_PRESENT=0

rollback_on_failure() {
  local rc=$?
  if [[ "${SUCCESS}" -ne 1 ]]; then
    set +e
    printf 'ROLLBACK_BEGIN rc=%s\n' "${rc}"
    systemctl disable --now emqx.service
    if [[ "${PRE_CONFIG_PRESENT}" -eq 1 && -f "${ROLLBACK_ROOT}/emqx.conf" ]]; then
      install -m 0600 -o emqx -g emqx "${ROLLBACK_ROOT}/emqx.conf" /etc/emqx/emqx.conf
    else
      rm -f /etc/emqx/emqx.conf
    fi
    if [[ "${COOKIE_CREATED}" -eq 1 ]]; then
      rm -f "${COOKIE_FILE}"
    fi
    rm -f "${COOKIE_TRANSFER}"
    find /var/lib/emqx /var/log/emqx -mindepth 1 -delete 2>/dev/null
    if [[ "${QID}" == Q141 && -f "${ROLLBACK_ROOT}/identity.tar.gz" ]]; then
      tar --acls --xattrs --numeric-owner -xzpf "${ROLLBACK_ROOT}/identity.tar.gz" -C /
    fi
    if [[ "${QID}" == Q141 && "${PRE_NODE_ENV_PRESENT}" -eq 0 ]]; then
      rm -f /etc/zhct/node.env
    fi
    printf 'ROLLBACK_STATUS=EMQX_DISABLED_CONFIG_COOKIE_RUNTIME_RESTORED\n'
    set -e
  fi
  rm -rf "${ROLLBACK_ROOT}"
  exit "${rc}"
}
trap rollback_on_failure EXIT

[[ "${EUID}" -eq 0 ]] || die "root is required"
[[ "$(hostname -s)" == "${EXPECTED_HOST}" ]] || die "hostname mismatch"
ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 |
  grep -Fxq "${EXPECTED_IP}" || die "expected IPv4 is not configured"
[[ "$(uname -m)" == x86_64 ]] || die "architecture mismatch"
rpm -q emqx-5.8.9-1.el8.x86_64 >/dev/null || die "EMQX NEVRA mismatch"
for command_name in sha256sum systemctl ss openssl mosquitto_pub mosquitto_sub timeout; do
  command -v "${command_name}" >/dev/null || die "${command_name} is missing"
done
[[ ! -e "${EVIDENCE_DIR}" ]] || die "evidence run already exists"
install -d -m 0700 "${EVIDENCE_DIR}" "${ROLLBACK_ROOT}"
exec > >(tee -a "${EVIDENCE_DIR}/terminal.log") 2>&1
exec 9>/run/lock/zhct-emqx-q141-143.lock
flock -n 9 || die "another EMQX activation process is running"

printf 'RUN_ID=%s\n' "${RUN_ID}"
printf 'QID=%s\n' "${QID}"
printf 'HOSTNAME=%s\n' "$(hostname -s)"
printf 'EXPECTED_IP=%s\n' "${EXPECTED_IP}"
printf 'NODE_KEY=%s\n' "${NODE_KEY}"
printf 'EXPECTED_NODE=%s\n' "${EXPECTED_NODE}"

enabled=$(systemctl is-enabled emqx.service 2>/dev/null || true)
active=$(systemctl is-active emqx.service 2>/dev/null || true)
[[ "${enabled}" == disabled ]] || die "emqx.service is not disabled before activation"
[[ "${active}" == inactive ]] || die "emqx.service is not inactive before activation"
[[ -z "$(ss -lntupH | grep -E "${TARGET_PORT_RE}" || true)" ]] ||
  die "an EMQX target listener exists before activation"
[[ -z "$(pgrep -fa 'beam.smp|/usr/lib/emqx' || true)" ]] ||
  die "an EMQX process exists before activation"
printf 'PRE_ACTIVATION_UNITS=DISABLED_INACTIVE\n'
printf 'PRE_ACTIVATION_PROCESSES=ZERO\n'
printf 'PRE_ACTIVATION_LISTENERS=ZERO\n'

if [[ "${QID}" == Q141 ]]; then
  [[ -f "${FRAMEWORK_ARCHIVE}" ]] || die "framework archive is missing"
  printf '%s  %s\n' "${FRAMEWORK_SHA256}" "${FRAMEWORK_ARCHIVE}" | sha256sum -c -
  tar -tzf "${FRAMEWORK_ARCHIVE}" >"${EVIDENCE_DIR}/framework-members.txt"
  grep -Fxq common/scripts/post-clone.sh "${EVIDENCE_DIR}/framework-members.txt" ||
    die "post-clone.sh is absent from framework archive"
  grep -Fxq common/scripts/activate-role.sh "${EVIDENCE_DIR}/framework-members.txt" ||
    die "activate-role.sh is absent from framework archive"
  grep -Fxq "roles/emqx/templates/nodes/${NODE_KEY}/emqx.conf" \
    "${EVIDENCE_DIR}/framework-members.txt" ||
    die "node template is absent from framework archive"

  identity_paths=()
  for path in \
    etc/machine-id \
    var/lib/dbus/machine-id \
    etc/ssh/ssh_host_ecdsa_key \
    etc/ssh/ssh_host_ecdsa_key.pub \
    etc/ssh/ssh_host_ed25519_key \
    etc/ssh/ssh_host_ed25519_key.pub \
    etc/ssh/ssh_host_rsa_key \
    etc/ssh/ssh_host_rsa_key.pub \
    etc/zhct/node.env; do
    [[ -e "/${path}" ]] && identity_paths+=("${path}")
  done
  [[ -e /etc/zhct/node.env ]] && PRE_NODE_ENV_PRESENT=1
  if ((${#identity_paths[@]})); then
    tar --acls --xattrs --numeric-owner -czpf \
      "${ROLLBACK_ROOT}/identity.tar.gz" -C / "${identity_paths[@]}"
  fi

  mkdir -p "${BUNDLE_ROOT}"
  tar -xzf "${FRAMEWORK_ARCHIVE}" -C "${BUNDLE_ROOT}"
  printf '%s  %s\n' \
    e637ca657a897c6d0b689bbd6347f04516baf5c9f32350cd1e8a2071c211a0a5 \
    "${BUNDLE_ROOT}/common/scripts/activate-role.sh" | sha256sum -c -
  printf '%s  %s\n' \
    eda667a180add2a54decd6b5bc9f47529ca8f742eaa98c6bf278ebbf6aa3c712 \
    "${BUNDLE_ROOT}/roles/emqx/templates/runtime/provision-emqx-cookie.sh" |
    sha256sum -c -
  printf '%s  %s\n' "${TEMPLATE_SHA}" \
    "${BUNDLE_ROOT}/roles/emqx/templates/nodes/${NODE_KEY}/emqx.conf" |
    sha256sum -c -
  install -d -m 0750 -o root -g root /etc/zhct/templates/emqx
  rsync -a --delete "${BUNDLE_ROOT}/roles/emqx/templates/" /etc/zhct/templates/emqx/
  install -m 0750 -o root -g root \
    "${BUNDLE_ROOT}/roles/emqx/templates/runtime/provision-emqx-cookie.sh" \
    /usr/local/sbin/provision-emqx-cookie.sh
  "${BUNDLE_ROOT}/common/scripts/post-clone.sh" \
    --role emqx --bundle-root "${BUNDLE_ROOT}" \
    --node "${NODE_KEY}" --expected-ip "${EXPECTED_IP}"
  rm -f "${FRAMEWORK_ARCHIVE}"
else
  [[ "${EXPECTED_COOKIE_SHA}" =~ ^[0-9a-f]{64}$ ]] ||
    die "expected cookie SHA256 is required for ${QID}"
  [[ -f "${BUNDLE_ROOT}/common/scripts/activate-role.sh" ]] ||
    die "activate-role.sh is missing"
  [[ -f "${COOKIE_TRANSFER}" ]] || die "cookie transfer file is missing"
  actual_transfer_sha=$(sha256sum "${COOKIE_TRANSFER}" | awk '{print $1}')
  [[ "${actual_transfer_sha}" == "${EXPECTED_COOKIE_SHA}" ]] ||
    die "cookie transfer SHA256 mismatch"
fi

[[ -f /etc/zhct/node.env ]] || die "node.env is missing"
grep -Fxq 'ROLE=emqx' /etc/zhct/node.env || die "node.env role mismatch"
grep -Fxq "NODE_KEY=${NODE_KEY}" /etc/zhct/node.env || die "node.env node mismatch"
grep -Fxq "NODE_IP=${EXPECTED_IP}" /etc/zhct/node.env || die "node.env IP mismatch"
grep -Fxq "NODE_NAME=${EXPECTED_HOST}" /etc/zhct/node.env || die "node.env hostname mismatch"
printf 'NODE_ENV_SHA256=%s\n' "$(sha256sum /etc/zhct/node.env | awk '{print $1}')"

printf '%s  %s\n' "${TEMPLATE_SHA}" \
  "${BUNDLE_ROOT}/roles/emqx/templates/nodes/${NODE_KEY}/emqx.conf" |
  sha256sum -c -
[[ -f /etc/emqx/emqx.conf ]] || die "EMQX node config is missing"
grep -Fq '__EMQX_CLUSTER_COOKIE__' /etc/emqx/emqx.conf ||
  die "EMQX config is not in pre-activation placeholder state"
grep -Fq "name = \"${EXPECTED_NODE}\"" /etc/emqx/emqx.conf ||
  die "EMQX node.name mismatch"
PRE_CONFIG_PRESENT=1
cp -p /etc/emqx/emqx.conf "${ROLLBACK_ROOT}/emqx.conf"
printf 'NODE_TEMPLATE_SHA256=%s\n' "${TEMPLATE_SHA}"
printf 'NODE_NAME_UNIQUE=%s\n' "${EXPECTED_NODE}"
printf 'PRE_ACTIVATION_CONFIG_PLACEHOLDER=PASS\n'

[[ ! -e "${COOKIE_FILE}" ]] || die "cookie file already exists before this task"
if [[ "${QID}" == Q141 ]]; then
  /usr/local/sbin/provision-emqx-cookie.sh
  COOKIE_CREATED=1
else
  install -d -m 0700 -o root -g root /etc/zhct/secrets
  install -m 0600 -o root -g root "${COOKIE_TRANSFER}" "${COOKIE_FILE}"
  rm -f "${COOKIE_TRANSFER}"
  COOKIE_CREATED=1
fi
[[ -s "${COOKIE_FILE}" ]] || die "cookie file is empty"
[[ "$(stat -c '%U:%G' "${COOKIE_FILE}")" == root:root ]] ||
  die "cookie owner mismatch"
[[ "$(stat -c '%a' "${COOKIE_FILE}")" == 600 ]] || die "cookie mode mismatch"
COOKIE_SHA=$(sha256sum "${COOKIE_FILE}" | awk '{print $1}')
if [[ "${QID}" != Q141 ]]; then
  [[ "${COOKIE_SHA}" == "${EXPECTED_COOKIE_SHA}" ]] || die "installed cookie SHA mismatch"
fi
printf 'COOKIE_PRESENT=PASS\n'
printf 'COOKIE_OWNER=root:root\n'
printf 'COOKIE_MODE=0600\n'
printf 'COOKIE_SHA256=%s\n' "${COOKIE_SHA}"
if [[ "${QID}" == Q141 ]]; then
  install -m 0600 -o root -g root "${COOKIE_FILE}" "${COOKIE_TRANSFER}"
  printf 'COOKIE_TRANSFER_READY=/root/emqx.cluster-cookie.transfer\n'
fi

emqx check_config
printf 'CHECK_CONFIG_RC=0\n'
"${BUNDLE_ROOT}/common/scripts/activate-role.sh" \
  --role emqx --bundle-root "${BUNDLE_ROOT}"

for _ in $(seq 1 30); do
  systemctl is-active --quiet emqx.service &&
    ss -lntH | grep -Eq '127\.0\.0\.1:1883|0\.0\.0\.0:1883|\*:1883' &&
    break
  sleep 1
done
[[ "$(systemctl is-enabled emqx.service)" == enabled ]] ||
  die "emqx.service is not enabled"
systemctl is-active --quiet emqx.service || die "emqx.service is not active"
[[ "$(systemctl is-enabled mosquitto.service 2>/dev/null || true)" == disabled ]] ||
  die "mosquitto.service is not disabled"
[[ "$(systemctl is-active mosquitto.service 2>/dev/null || true)" == inactive ]] ||
  die "mosquitto.service is not inactive"

topic="sanquan/${QID,,}/${NODE_KEY}/$(date +%s)"
payload="${QID}_${NODE_KEY}_$(openssl rand -hex 12)"
sub_output=$(mktemp)
timeout 15 mosquitto_sub -h 127.0.0.1 -p 1883 \
  -t "${topic}" -C 1 -W 12 >"${sub_output}" &
sub_pid=$!
sleep 1
mosquitto_pub -h 127.0.0.1 -p 1883 -t "${topic}" -m "${payload}"
wait "${sub_pid}"
received=$(cat "${sub_output}")
rm -f "${sub_output}"
[[ "${received}" == "${payload}" ]] || die "local pub/sub payload mismatch"

printf 'PUBSUB_TOPIC=%s\n' "${topic}"
printf 'PUBSUB_SENT=%s\n' "${payload}"
printf 'PUBSUB_RECEIVED=%s\n' "${received}"
printf 'PUBSUB_MATCH=PASS\n'
printf 'NODE_STATUS_BEGIN\n'
emqx ctl status
printf 'NODE_STATUS_END\n'
printf 'CLUSTER_STATUS_OBSERVATION_BEGIN\n'
emqx ctl cluster status || true
printf 'CLUSTER_STATUS_OBSERVATION_END\n'
printf 'LISTENERS_BEGIN\n'
ss -lntupH | grep -E "${TARGET_PORT_RE}" || true
printf 'LISTENERS_END\n'
printf 'EMQX_UNIT_ENABLED=%s\n' "$(systemctl is-enabled emqx.service)"
printf 'EMQX_UNIT_ACTIVE=%s\n' "$(systemctl is-active emqx.service)"
printf 'MOSQUITTO_UNIT_ENABLED=%s\n' "$(systemctl is-enabled mosquitto.service 2>/dev/null || true)"
printf 'MOSQUITTO_UNIT_ACTIVE=%s\n' "$(systemctl is-active mosquitto.service 2>/dev/null || true)"
printf 'LOG_FILES_BEGIN\n'
find /var/log/emqx -maxdepth 2 -type f \
  -printf '%p\t%U:%G\t%m\t%s\n' | sort
printf 'LOG_FILES_END\n'
printf 'JOURNAL_ERRORS_BEGIN\n'
journalctl -u emqx.service -p err -n 50 --no-pager |
  sed -E '/cookie|secret|password|token/Id' || true
printf 'JOURNAL_ERRORS_END\n'
printf 'JOURNAL_SECRET_FILTER_APPLIED=YES\n'
printf 'Q200_NETWORK_GATE=NOT_PERFORMED\n'
printf 'Q240_FORMAL_CLUSTER_ACCEPTANCE=NOT_PERFORMED\n'
printf 'KEEPALIVED_VIP=OUT_OF_SCOPE\n'
printf 'COMMAND_RC=0\n'
printf '%s\n' "${FINAL_MARK}"

SUCCESS=1
rm -rf "${ROLLBACK_ROOT}"
trap - EXIT
exit 0
