#!/usr/bin/env bash

set -Eeuo pipefail
umask 077
export LANG=C

EXPECTED_HOSTNAME="sqjcnginx01"
EXPECTED_IPV4="10.2.203.102"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
EXPECTED_SOURCE_COMMIT="47584918681b8aad36fee3c9eade5cd919c702ab"
EXPECTED_FRAMEWORK_SHA256="f74941f6328c752eeefb5fe082c4366dfed5c91ecad36d31ba7e11b7d2cac2d5"
EXPECTED_RPM_MANIFEST_SHA256="0ef1b62af801d782e8331b608b4b0580e0721a7553e913ab17686d4dc32dee3d"
Q041_RUN_ID="q041-20260723T114537+0800"
BUNDLE_ROOT="/opt/zhct-deploy"
PACKAGE_ROOT="${BUNDLE_ROOT}/packages"
Q041_EVIDENCE="/var/backups/zhct-q041/evidence/${Q041_RUN_ID}"
RUN_ID=${1:-}
EVIDENCE_ROOT="/var/backups/zhct-q042/evidence"
EVIDENCE_DIR="${EVIDENCE_ROOT}/${RUN_ID}"
VIP_RE='10\.2\.203\.(115|116|117)'
MIN_FREE_BYTES=$((9 * 1024 * 1024 * 1024))
TARGET_UNITS=(
  nginx.service
  keepalived.service
  haproxy.service
  orchestrator.service
  sentinel5.service
  sentinel7.service
)
FORMAL_BINARIES=(
  /usr/local/nginx/sbin/nginx
  /usr/local/sbin/keepalived
  /usr/local/haproxy/sbin/haproxy
  /usr/local/orchestrator/orchestrator
  /opt/redis/5.0.14/bin/redis-sentinel
  /opt/redis/5.0.14/bin/redis-cli
  /opt/redis/7.2.14/bin/redis-sentinel
  /opt/redis/7.2.14/bin/redis-cli
  /usr/bin/orchestrator
  /usr/bin/orchestrator-client
)
FORMAL_RUNTIME_PATHS=(
  /etc/keepalived/keepalived.conf
  /etc/haproxy/haproxy.cfg
  /etc/orchestrator.conf.json
  /var/lib/redis-sentinel5/sentinel.conf
  /var/lib/redis-sentinel7/sentinel.conf
  /var/lib/orchestrator
  /var/log/orchestrator
  /etc/zhct/node.env
  /etc/zhct/secrets
)
FORMAL_UNIT_FILES=(
  /etc/systemd/system/nginx.service
  /etc/systemd/system/keepalived.service
  /etc/systemd/system/haproxy.service
  /usr/lib/systemd/system/orchestrator.service
  /etc/systemd/system/sentinel5.service
  /etc/systemd/system/sentinel7.service
)

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

capture() {
  local name=$1
  shift
  {
    printf '# captured_at=%s\n' "$(date --iso-8601=seconds)"
    printf '# command='
    printf '%q ' "$@"
    printf '\n'
    "$@" || true
  } >"${EVIDENCE_DIR}/${name}.txt" 2>&1
}

assert_units_quiescent() {
  local output=$1
  : >"${output}"
  local unit enabled active substate started
  for unit in "${TARGET_UNITS[@]}"; do
    enabled=$(systemctl is-enabled "${unit}" 2>/dev/null || true)
    active=$(systemctl show "${unit}" -p ActiveState --value)
    substate=$(systemctl show "${unit}" -p SubState --value)
    started=$(systemctl show "${unit}" -p ExecMainStartTimestampMonotonic --value)
    printf '%s\t%s\t%s\t%s\t%s\n' \
      "${unit}" "${enabled}" "${active}" "${substate}" "${started}" >>"${output}"
    [[ "${enabled}" == "disabled" ]] || die "unit is not disabled: ${unit}"
    [[ "${active}" == "inactive" ]] || die "unit is not inactive: ${unit}"
    [[ "${substate}" == "dead" ]] || die "unit is not dead: ${unit}"
    [[ "${started}" == "0" ]] || die "unit has a start timestamp: ${unit}"
  done
}

assert_formal_runtime_paths_absent() {
  local output=$1
  : >"${output}"
  local path
  for path in "${FORMAL_RUNTIME_PATHS[@]}"; do
    if [[ -e "${path}" || -L "${path}" ]]; then
      printf '%s\tPRESENT\n' "${path}" >>"${output}"
      die "formal runtime path exists during Q042: ${path}"
    fi
    printf '%s\tABSENT\n' "${path}" >>"${output}"
  done
}

[[ "${EUID}" -eq 0 ]] || die "must run as root"
[[ "${RUN_ID}" =~ ^q042-[0-9]{8}T[0-9]{6}[+]0800(-R[1-9][0-9]*)?$ ]] || \
  die "invalid Q042 run id"
[[ "$(hostname -s)" == "${EXPECTED_HOSTNAME}" ]] || die "hostname mismatch"
[[ "$(tr -d '\r' </etc/openEuler-release)" == "${EXPECTED_OS}" ]] || die "OS mismatch"
[[ "$(uname -m)" == "x86_64" ]] || die "CPU architecture mismatch"
ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 | \
  grep -Fxq "${EXPECTED_IPV4}" || die "expected IPv4 is not configured"

install -d -m 0700 -o root -g root "${EVIDENCE_ROOT}"
mkdir -m 0700 "${EVIDENCE_DIR}" 2>/dev/null || \
  die "Q042 run id has already been claimed: ${RUN_ID}"
exec > >(tee "${EVIDENCE_DIR}/preflight-check.log") 2>&1

for command in awk bash cat chmod cmp curl cut date df dnf find grep hostname install ip make \
  mkdir rpm sed sha256sum sort ss systemctl tail tar tee timeout tr uname unshare wc; do
  command -v "${command}" >/dev/null 2>&1 || die "required command is missing: ${command}"
done

grep -Fq 'final_audit_check=PASS' "${Q041_EVIDENCE}/final-audit-result.env" || \
  die "Q041 final audit is not PASS"
grep -Fq 'q042_remote_runtime_state=NOT_STARTED' \
  "${Q041_EVIDENCE}/final-audit-result.env" || die "Q041 did not leave Q042 locked"

[[ "$(tr -d '\r\n' <"${BUNDLE_ROOT}/.source-commit")" == "${EXPECTED_SOURCE_COMMIT}" ]] || \
  die "framework source commit mismatch"
[[ "$(tr -d '\r\n' <"${BUNDLE_ROOT}/.framework-bundle-sha256")" == "${EXPECTED_FRAMEWORK_SHA256}" ]] || \
  die "framework bundle marker mismatch"
[[ "$(sha256sum "${PACKAGE_ROOT}/rpms.sha256" | awk '{print $1}')" == \
  "${EXPECTED_RPM_MANIFEST_SHA256}" ]] || die "RPM manifest SHA256 mismatch"
[[ $(wc -l <"${PACKAGE_ROOT}/rpms.sha256") -eq 18 ]] || die "RPM manifest must contain 18 rows"
[[ $(find "${PACKAGE_ROOT}/rpms" -maxdepth 1 -type f -name '*.rpm' | wc -l) -eq 18 ]] || \
  die "RPM directory must contain exactly 18 files"

(cd "${BUNDLE_ROOT}" && sha256sum -c roles/nginx/manifests/packages.sha256) \
  >"${EVIDENCE_DIR}/nginx-packages-sha256-check.txt"
(cd "${PACKAGE_ROOT}" && sha256sum -c rpms.sha256) \
  >"${EVIDENCE_DIR}/rpms-sha256-check.txt"

assert_units_quiescent "${EVIDENCE_DIR}/unit-state-before.tsv"
ss -lntupH >"${EVIDENCE_DIR}/listeners-before.txt"
ip -4 -br addr >"${EVIDENCE_DIR}/ipv4-before.txt"
ip route show table all >"${EVIDENCE_DIR}/routes-before.txt"
sha256sum "${FORMAL_BINARIES[@]}" >"${EVIDENCE_DIR}/formal-binaries-before.sha256"
sha256sum "${FORMAL_UNIT_FILES[@]}" >"${EVIDENCE_DIR}/formal-unit-files-before.sha256"
assert_formal_runtime_paths_absent "${EVIDENCE_DIR}/formal-runtime-paths-before.tsv"

target_ports=$(awk -F'\t' 'NR > 1 && $1 ~ /^[0-9]+$/ {
  ports[++count]=$1
} END {
  for (i=1; i<=count; i++) printf "%s%s", (i > 1 ? "|" : ""), ports[i]
}' "${BUNDLE_ROOT}/roles/nginx/manifests/ports.tsv")
target_listeners=$(ss -lntupH | grep -E ":(${target_ports})([[:space:]]|$)" || true)
vip_addresses=$(ip -o -4 addr show | grep -E "${VIP_RE}" || true)
[[ -z "${target_listeners}" ]] || die "target port is listening before Q042"
[[ -z "${vip_addresses}" ]] || die "project VIP is announced before Q042"

unshare -n true >"${EVIDENCE_DIR}/network-namespace-capability.txt" 2>&1 || \
  die "network namespace isolation is unavailable"
set +e
timeout 6 unshare -n bash -c \
  'ip link set lo up; curl --noproxy "*" --connect-timeout 2 --max-time 4 -fsS https://repo.openeuler.org/ >/dev/null' \
  >"${EVIDENCE_DIR}/isolated-public-network-negative-test.txt" 2>&1
isolated_curl_rc=$?
set -e
[[ "${isolated_curl_rc}" -ne 0 ]] || die "public network unexpectedly worked inside isolated namespace"

mapfile -d '' rpms < <(find "${PACKAGE_ROOT}/rpms" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z)
dnf_history_id_before=$(dnf history list | awk '$1 ~ /^[0-9]+$/ {print $1; exit}')
dnf_history_id_before=${dnf_history_id_before:-0}
set +e
unshare -n dnf --disablerepo='*' --cacheonly --setopt=install_weak_deps=False \
  reinstall --assumeno "${rpms[@]}" >"${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log" 2>&1
dnf_assumeno_rc=$?
set -e
[[ "${dnf_assumeno_rc}" -eq 1 ]] || \
  die "cache-only DNF assumeno returned unexpected code ${dnf_assumeno_rc}"
grep -Eiq 'Transaction Summary|事务摘要' \
  "${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log" || \
  die "cache-only DNF assumeno has no transaction summary"
[[ $(grep -Ec '^[[:space:]]*Reinstalling:[[:space:]]*$' \
  "${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log") -eq 1 ]] || \
  die "cache-only DNF output does not contain exactly one Reinstalling section"
if grep -Eq '^[[:space:]]*(Installing|Upgrading|Removing|Downgrading|Skipping|Obsoleting|Erasing|Replacing)([^:]*)?:[[:space:]]*$' \
  "${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log"; then
  die "cache-only DNF output contains a non-reinstall transaction section"
fi
if grep -Eq '^[[:space:]]*Reinstalling[[:space:]]+[^:]+:[[:space:]]*$' \
  "${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log"; then
  die "cache-only DNF output contains an additional reinstall transaction section"
fi
if grep -Eq '^[[:space:]]*(Install|Upgrade|Remove|Downgrade|Skip|Obsolete|Erase|Replace)[[:space:]]+[0-9]+[[:space:]]+Packages?[[:space:]]*$' \
  "${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log"; then
  die "cache-only DNF summary contains a non-reinstall transaction action"
fi
[[ $(grep -Ec '^Operation aborted[.]$' \
  "${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log") -eq 1 ]] || \
  die "cache-only DNF assumeno did not terminate with Operation aborted"
if grep -Eiq 'Failed to download|Cannot download|Curl error|Could not resolve host|No match for argument|conflicting requests|nothing provides' \
  "${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log"; then
  die "cache-only DNF transaction has an unresolved package or network dependency"
fi

rpm -qp --qf '%{NAME}\n' "${rpms[@]}" | sort -u \
  >"${EVIDENCE_DIR}/dnf-expected-reinstall-packages.txt"
rpm -qp --qf '%{ARCH}\n' "${rpms[@]}" | sort -u \
  >"${EVIDENCE_DIR}/dnf-expected-reinstall-arches.txt"
awk -v arch_file="${EVIDENCE_DIR}/dnf-expected-reinstall-arches.txt" '
  BEGIN {
    while ((getline arch < arch_file) > 0) {
      expected_arch[arch] = 1
    }
    close(arch_file)
  }
  /^[[:space:]]*Reinstalling:[[:space:]]*$/ {
    in_reinstall = 1
    next
  }
  /^[[:space:]]*Transaction Summary[[:space:]]*$/ {
    in_reinstall = 0
  }
  in_reinstall {
    line = $0
    sub(/^[[:space:]]+/, "", line)
    if (line == "" || line ~ /^=+$/) {
      next
    }
    split(line, fields, /[[:space:]]+/)
    if (expected_arch[fields[1]]) {
      next
    }
    print fields[1]
  }
' "${EVIDENCE_DIR}/dnf-cacheonly-reinstall-assumeno.log" \
  >"${EVIDENCE_DIR}/dnf-actual-reinstall-packages.raw.txt"
sort -u "${EVIDENCE_DIR}/dnf-actual-reinstall-packages.raw.txt" \
  >"${EVIDENCE_DIR}/dnf-actual-reinstall-packages.txt"
[[ $(wc -l <"${EVIDENCE_DIR}/dnf-expected-reinstall-packages.txt") -eq 18 ]] || \
  die "expected DNF reinstall package set does not contain 18 unique names"
[[ $(wc -l <"${EVIDENCE_DIR}/dnf-actual-reinstall-packages.raw.txt") -eq 18 ]] || \
  die "actual DNF reinstall section does not contain exactly 18 package rows"
[[ $(wc -l <"${EVIDENCE_DIR}/dnf-actual-reinstall-packages.txt") -eq 18 ]] || \
  die "actual DNF reinstall package set does not contain 18 unique names"
cmp "${EVIDENCE_DIR}/dnf-expected-reinstall-packages.txt" \
  "${EVIDENCE_DIR}/dnf-actual-reinstall-packages.txt" || \
  die "actual DNF reinstall package set differs from the 18 locked local RPMs"

dnf_history_id_after=$(dnf history list | awk '$1 ~ /^[0-9]+$/ {print $1; exit}')
dnf_history_id_after=${dnf_history_id_after:-0}
[[ "${dnf_history_id_after}" == "${dnf_history_id_before}" ]] || \
  die "DNF history changed during Q042 preflight"

root_available_bytes=$(df -B1 --output=avail / | tail -n 1 | tr -d ' ')
root_total_bytes=$(df -B1 --output=size / | tail -n 1 | tr -d ' ')
minimum_required_bytes=${MIN_FREE_BYTES}
root_twenty_percent_bytes=$((root_total_bytes / 5))
if ((root_twenty_percent_bytes > minimum_required_bytes)); then
  minimum_required_bytes=${root_twenty_percent_bytes}
fi
((root_available_bytes >= minimum_required_bytes)) || die "root free space is below Q042 gate"

assert_units_quiescent "${EVIDENCE_DIR}/unit-state-after-preflight.tsv"
ss -lntupH >"${EVIDENCE_DIR}/listeners-after-preflight.txt"
ip -4 -br addr >"${EVIDENCE_DIR}/ipv4-after-preflight.txt"
ip route show table all >"${EVIDENCE_DIR}/routes-after-preflight.txt"
sha256sum "${FORMAL_BINARIES[@]}" >"${EVIDENCE_DIR}/formal-binaries-after-preflight.sha256"
sha256sum "${FORMAL_UNIT_FILES[@]}" >"${EVIDENCE_DIR}/formal-unit-files-after-preflight.sha256"
assert_formal_runtime_paths_absent "${EVIDENCE_DIR}/formal-runtime-paths-after-preflight.tsv"

cmp "${EVIDENCE_DIR}/formal-binaries-before.sha256" \
  "${EVIDENCE_DIR}/formal-binaries-after-preflight.sha256" || \
  die "formal binary changed during Q042 preflight"
cmp "${EVIDENCE_DIR}/formal-unit-files-before.sha256" \
  "${EVIDENCE_DIR}/formal-unit-files-after-preflight.sha256" || \
  die "formal systemd unit file changed during Q042 preflight"
cmp "${EVIDENCE_DIR}/listeners-before.txt" \
  "${EVIDENCE_DIR}/listeners-after-preflight.txt" || \
  die "host listeners changed during Q042 preflight"
cmp "${EVIDENCE_DIR}/ipv4-before.txt" \
  "${EVIDENCE_DIR}/ipv4-after-preflight.txt" || \
  die "host IPv4 configuration changed during Q042 preflight"
cmp "${EVIDENCE_DIR}/routes-before.txt" \
  "${EVIDENCE_DIR}/routes-after-preflight.txt" || \
  die "host routes changed during Q042 preflight"
cmp "${EVIDENCE_DIR}/formal-runtime-paths-before.tsv" \
  "${EVIDENCE_DIR}/formal-runtime-paths-after-preflight.tsv" || \
  die "formal runtime path state changed during Q042 preflight"

target_listeners_after=$(ss -lntupH | grep -E ":(${target_ports})([[:space:]]|$)" || true)
vip_addresses_after=$(ip -o -4 addr show | grep -E "${VIP_RE}" || true)
[[ -z "${target_listeners_after}" ]] || die "target port is listening after Q042 preflight"
[[ -z "${vip_addresses_after}" ]] || die "project VIP is announced after Q042 preflight"

{
  printf 'captured_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'run_id=%s\n' "${RUN_ID}"
  printf 'q041_user_confirmation=RECORDED_LOCAL\n'
  printf 'host_identity_check=PASS\n'
  printf 'q041_final_audit_check=PASS\n'
  printf 'locked_asset_check=PASS\n'
  printf 'rpm_count=18\n'
  printf 'network_namespace_capability=PASS\n'
  printf 'isolated_public_network=BLOCKED\n'
  printf 'isolated_public_network_test_rc=%s\n' "${isolated_curl_rc}"
  printf 'dnf_cacheonly_assumeno_rc=%s\n' "${dnf_assumeno_rc}"
  printf 'dnf_cacheonly_resolution=PASS\n'
  printf 'dnf_cacheonly_reinstall_package_count=18\n'
  printf 'dnf_cacheonly_reinstall_package_identity=PASS\n'
  printf 'dnf_history_id_before=%s\n' "${dnf_history_id_before}"
  printf 'dnf_history_id_after=%s\n' "${dnf_history_id_after}"
  printf 'formal_binary_immutability=PASS\n'
  printf 'formal_unit_file_immutability=PASS\n'
  printf 'formal_runtime_paths_immutability=PASS\n'
  printf 'host_listener_immutability=PASS\n'
  printf 'host_ipv4_immutability=PASS\n'
  printf 'host_route_immutability=PASS\n'
  printf 'target_units_quiescent=PASS\n'
  printf 'target_listener_count=0\n'
  printf 'vip_announcement_count=0\n'
  printf 'root_available_bytes=%s\n' "${root_available_bytes}"
  printf 'minimum_required_bytes=%s\n' "${minimum_required_bytes}"
  printf 'preflight_check=PASS\n'
  printf 'offline_rebuild_execution=NOT_PERFORMED\n'
} >"${EVIDENCE_DIR}/preflight-result.env"

chmod 0600 "${EVIDENCE_DIR}"/*
printf 'Q042_PREFLIGHT_CHECK_OK\n'
cat "${EVIDENCE_DIR}/preflight-result.env"
