#!/usr/bin/env bash

set -Eeuo pipefail
export LANG=C
umask 077

EXPECTED_HOSTNAME="sqjcmqtt01"
EXPECTED_IPV4="10.2.203.108"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
EXPECTED_ARCH="x86_64"
EXPECTED_EMQX_SHA256="e4cf9f507cbe32c87c4e7496b943e2d836cf1c1e4e36bc70ed67c8370fbe8484"
EMQX_URL="https://github.com/emqx/emqx/releases/download/v5.8.9/emqx-5.8.9-el8-amd64.rpm"
ASSET_ROOT="/opt/zhct-deploy/emqx01-offline"
EVIDENCE_ROOT="/root/q082-evidence"
TARGET_PORT_RE=':(1883|8083|18083|4370|5370|8883|8084)([[:space:]]|$)'
RUN_ID=${1:-}
CONFIRM=${2:-}
STAGE_ROOT=

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

cleanup() {
  local rc=$?
  if [[ -n "${STAGE_ROOT}" && -d "${STAGE_ROOT}" ]]; then
    rm -rf -- "${STAGE_ROOT}"
  fi
  exit "${rc}"
}
trap cleanup EXIT

unit_quiet() {
  local unit=$1 enabled active
  enabled=$(systemctl is-enabled "${unit}" 2>/dev/null || true)
  active=$(systemctl is-active "${unit}" 2>/dev/null || true)
  [[ "${enabled}" == "disabled" ]] && [[ "${active}" == "inactive" ]]
}

[[ "${EUID}" -eq 0 ]] || die "must run as root"
[[ "${RUN_ID}" =~ ^q082-[0-9]{8}T[0-9]{6}[+]0800$ ]] || die "invalid Q082 run id"
[[ "${CONFIRM}" == "--execute-q082-cache-only" ]] || die "explicit Q082 flag is required"
[[ "$(hostname -s)" == "${EXPECTED_HOSTNAME}" ]] || die "hostname mismatch"
[[ "$(uname -m)" == "${EXPECTED_ARCH}" ]] || die "architecture mismatch"
[[ "$(tr -d '\r' </etc/openEuler-release)" == "${EXPECTED_OS}" ]] || die "OS mismatch"
ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 |
  grep -Fxq "${EXPECTED_IPV4}" || die "expected IPv4 is not configured"
for command_name in curl dnf rpm sha256sum flock ss; do
  command -v "${command_name}" >/dev/null || die "${command_name} not found"
done
unit_quiet emqx.service || die "emqx.service is not disabled/inactive"
unit_quiet mosquitto.service || die "mosquitto.service is not disabled/inactive"
[[ -z "$(ss -lntupH | grep -E "${TARGET_PORT_RE}" || true)" ]] ||
  die "an EMQX target port is occupied"
[[ ! -e "${ASSET_ROOT}" ]] || die "offline asset root already exists"
[[ ! -e "${EVIDENCE_ROOT}/${RUN_ID}" ]] || die "evidence run already exists"

EXPECTED_INSTALLED=(
  "emqx-5.8.9-1.el8.x86_64"
  "mosquitto-2.0.20-2.oe2403sp1.x86_64"
  "libatomic-12.3.1-67.oe2403sp1.x86_64"
  "cjson-1.7.15-11.oe2403sp1.x86_64"
  "libwebsockets-4.3.3-5.oe2403sp1.x86_64"
)
for nevra in "${EXPECTED_INSTALLED[@]}"; do
  rpm -q "${nevra}" >/dev/null || die "installed package mismatch: ${nevra}"
done

mkdir -p "$(dirname "${ASSET_ROOT}")"
install -d -m 0700 "${EVIDENCE_ROOT}/${RUN_ID}"
EVIDENCE_DIR="${EVIDENCE_ROOT}/${RUN_ID}"
STAGE_ROOT=$(mktemp -d "/opt/zhct-deploy/.emqx01-q082-${RUN_ID}.XXXXXX")
RPM_STAGE="${STAGE_ROOT}/rpms"
install -d -m 0700 "${RPM_STAGE}"
exec 9>/run/lock/zhct-emqx01-q082.lock
flock -n 9 || die "another EMQX01 Q082 process is running"

dnf_history_before=$(dnf history list | awk '$1 ~ /^[0-9]+$/ {print $1; exit}')
dnf_history_before=${dnf_history_before:-0}

curl --fail --location --proto '=https' --tlsv1.2 --retry 2 \
  --output "${STAGE_ROOT}/emqx-5.8.9-el8-amd64.rpm" "${EMQX_URL}" \
  2> >(tee "${EVIDENCE_DIR}/emqx-download.log" >&2)
printf '%s  %s\n' "${EXPECTED_EMQX_SHA256}" \
  "${STAGE_ROOT}/emqx-5.8.9-el8-amd64.rpm" |
  sha256sum -c - | tee "${EVIDENCE_DIR}/emqx-sha256-check.txt"
rpm -qpi "${STAGE_ROOT}/emqx-5.8.9-el8-amd64.rpm" \
  >"${EVIDENCE_DIR}/emqx-rpm-info.txt"
emqx_signature=$(
  awk -F: '
    /^Signature[[:space:]]*:/ {
      sub(/^[^:]*:[[:space:]]*/, "")
      print
      exit
    }
  ' "${EVIDENCE_DIR}/emqx-rpm-info.txt"
)
if [[ "${emqx_signature}" == "(none)" ]]; then
  rpm -K "${STAGE_ROOT}/emqx-5.8.9-el8-amd64.rpm" |
    tee "${EVIDENCE_DIR}/emqx-signature.txt"
  grep -Eq 'digests OK$' "${EVIDENCE_DIR}/emqx-signature.txt" ||
    die "unsigned EMQX RPM digest verification failed"
  printf 'EMQX_RPM_SIGNATURE=UNSIGNED_ACCEPTED_BY_PINNED_SHA256\n' |
    tee -a "${EVIDENCE_DIR}/emqx-signature.txt"
else
  rpm --define '_pkgverify_level signature' -K \
    "${STAGE_ROOT}/emqx-5.8.9-el8-amd64.rpm" |
    tee "${EVIDENCE_DIR}/emqx-signature.txt"
  grep -Eq 'signatures OK$|digests signatures OK$' \
    "${EVIDENCE_DIR}/emqx-signature.txt" ||
    die "signed EMQX RPM signature verification failed"
fi

dnf -y reinstall --downloadonly --downloaddir="${RPM_STAGE}" \
  --setopt=install_weak_deps=False \
  libatomic-12.3.1-67.oe2403sp1.x86_64 \
  mosquitto-2.0.20-2.oe2403sp1.x86_64 \
  cjson-1.7.15-11.oe2403sp1.x86_64 \
  libwebsockets-4.3.3-5.oe2403sp1.x86_64 \
  2>&1 | tee "${EVIDENCE_DIR}/dnf-download.log"

mapfile -d '' closure_rpms < <(
  find "${RPM_STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z
)
[[ "${#closure_rpms[@]}" -eq 4 ]] ||
  die "expected four openEuler RPMs, got ${#closure_rpms[@]}"

for rpm_file in "${closure_rpms[@]}"; do
  rpm --define '_pkgverify_level signature' -K "${rpm_file}"
done | tee "${EVIDENCE_DIR}/closure-signatures.txt"

{
  rpm -qp --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' "${closure_rpms[@]}"
  rpm -qp --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' \
    "${STAGE_ROOT}/emqx-5.8.9-el8-amd64.rpm"
} | sort >"${EVIDENCE_DIR}/packages.nevra"
printf '%s\n' "${EXPECTED_INSTALLED[@]}" | sort >"${EVIDENCE_DIR}/packages.expected-nevra"
diff -u "${EVIDENCE_DIR}/packages.expected-nevra" \
  "${EVIDENCE_DIR}/packages.nevra" |
  tee "${EVIDENCE_DIR}/packages-nevra.diff"

offline_log="${EVIDENCE_DIR}/dnf-cache-only-reinstall-assumeno.log"
set +e
dnf --disablerepo='*' --setopt=strict=True \
  --setopt=install_weak_deps=False --setopt=localpkg_gpgcheck=False \
  --assumeno reinstall \
  "${STAGE_ROOT}/emqx-5.8.9-el8-amd64.rpm" "${closure_rpms[@]}" \
  >"${offline_log}" 2>&1
dnf_rc=$?
set -e
cat "${offline_log}"
[[ "${dnf_rc}" -eq 1 ]] || die "unexpected cache-only DNF assumeno RC: ${dnf_rc}"
grep -Fq 'Transaction Summary' "${offline_log}" ||
  die "cache-only DNF did not reach Transaction Summary"
grep -Eiq 'nothing provides|conflicting requests|transaction test error|protected packages|cannot download|failed to download' \
  "${offline_log}" && die "cache-only reinstall has an unresolved package"
grep -Eiq '(^|[[:space:]])(Installing|Upgrading|Downgrading|Removing|Obsoleting)(:|[[:space:]])|^[[:space:]]*(Install|Upgrade|Downgrade|Remove|Obsolete)[[:space:]]+[0-9]+' \
  "${offline_log}" && die "cache-only transaction contains an unexpected action"
grep -Eq 'Reinstall[[:space:]]+5 Packages|Reinstalling:' "${offline_log}" ||
  die "cache-only transaction did not resolve five reinstalls"

cp -p "${STAGE_ROOT}/emqx-5.8.9-el8-amd64.rpm" "${RPM_STAGE}/"
(
  cd "${RPM_STAGE}"
  find . -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z |
    xargs -0 sha256sum
) >"${STAGE_ROOT}/rpms.sha256"
(
  cd "${RPM_STAGE}"
  sha256sum -c ../rpms.sha256
) | tee "${EVIDENCE_DIR}/rpms-sha256-check.txt"
mv "${STAGE_ROOT}" "${ASSET_ROOT}"
STAGE_ROOT=
chmod 0700 "${ASSET_ROOT}" "${ASSET_ROOT}/rpms"
chmod 0600 "${ASSET_ROOT}/rpms.sha256" "${ASSET_ROOT}"/rpms/*.rpm

dnf_history_after=$(dnf history list | awk '$1 ~ /^[0-9]+$/ {print $1; exit}')
dnf_history_after=${dnf_history_after:-0}
[[ "${dnf_history_after}" == "${dnf_history_before}" ]] ||
  die "DNF history changed during Q082"
unit_quiet emqx.service || die "emqx.service final state changed"
unit_quiet mosquitto.service || die "mosquitto.service final state changed"
[[ -z "$(ss -lntupH | grep -E "${TARGET_PORT_RE}" || true)" ]] ||
  die "a target listener exists after Q082"

printf 'RUN_ID=%s\n' "${RUN_ID}"
printf 'OFFLINE_ASSET_ROOT=%s\n' "${ASSET_ROOT}"
printf 'OFFLINE_RPM_COUNT=5\n'
printf 'DNF_HISTORY_UNCHANGED=%s\n' "${dnf_history_after}"
printf 'TARGET_UNITS_DISABLED_INACTIVE\n'
printf 'TARGET_LISTENERS_ZERO\n'
printf 'Q082_CACHE_ONLY_REINSTALL_ASSUMENO_PASS\n'
