#!/usr/bin/env bash

set -Eeuo pipefail
export LC_ALL=C

EXPECTED_HOSTNAME="sqjcnginx01"
EXPECTED_IPV4="10.2.203.102"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
EXPECTED_OLD_COUNT=29
EXPECTED_OLD_MANIFEST_SHA256="1afa990482067def7817332403121428d42209a5e6e30e0b58a83be5e36f53df"
EXPECTED_FINAL_COUNT=18
EXPECTED_OPENEULER_COUNT=15
EXPECTED_PERCONA_COUNT=3
PACKAGE_ROOT="/opt/zhct-deploy/packages"
RPM_ROOT="${PACKAGE_ROOT}/rpms"
RPM_MANIFEST="${PACKAGE_ROOT}/rpms.sha256"
FAILED_STAGE="${PACKAGE_ROOT}/rpms.q040-install-only-stage"
STAGE="${PACKAGE_ROOT}/rpms.q040-compatible-stage"
EVIDENCE_DIR="/var/backups/zhct-q040/20260722T203557+0800"
REJECTED_ROOT="${EVIDENCE_DIR}/rejected-assets/rpms-29-upgrades"
FAILED_STAGE_ARCHIVE="${EVIDENCE_DIR}/rejected-assets/rpms-18-unresolved-exact-deps"
FAILED_EVIDENCE_ARCHIVE="${EVIDENCE_DIR}/rejected-assets/rpms-18-unresolved-exact-deps-evidence"
CLASSIFICATION="${EVIDENCE_DIR}/compatible-rpm-classification.tsv"

KEEP_FROM_OLD_NAMES=(
  autoconf
  automake
  jq
  libmnl-devel
  libnfnetlink-devel
  libnftnl-devel
  libnl3-devel
  libtool
  oniguruma
  pcre2-devel
  percona-orchestrator
  percona-orchestrator-cli
  percona-orchestrator-client
)

EXACT_COMPATIBLE_PACKAGES=(
  gcc-c++-12.3.1-62.oe2403sp1.x86_64
  libstdc++-devel-12.3.1-62.oe2403sp1.x86_64
  systemd-devel-255-34.oe2403sp1.x86_64
  systemd-pam-255-34.oe2403sp1.x86_64
  zlib-devel-1.2.13-4.oe2403sp1.x86_64
)

EXPECTED_FINAL_NAMES=(
  autoconf
  automake
  gcc-c++
  jq
  libmnl-devel
  libnfnetlink-devel
  libnftnl-devel
  libnl3-devel
  libstdc++-devel
  libtool
  oniguruma
  pcre2-devel
  percona-orchestrator
  percona-orchestrator-cli
  percona-orchestrator-client
  systemd-devel
  systemd-pam
  zlib-devel
)

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

contains() {
  local wanted=$1 item
  shift
  for item in "$@"; do
    [[ "${item}" == "${wanted}" ]] && return 0
  done
  return 1
}

[[ "${EUID}" -eq 0 ]] || die "must run as root"
[[ "$(hostname -s)" == "${EXPECTED_HOSTNAME}" ]] || die "hostname 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"
[[ -d "${RPM_ROOT}" ]] || die "29-RPM closure is missing"
[[ -s "${RPM_MANIFEST}" ]] || die "29-RPM manifest is missing"
[[ "$(sha256sum "${RPM_MANIFEST}" | awk '{print $1}')" == "${EXPECTED_OLD_MANIFEST_SHA256}" ]] || \
  die "29-RPM manifest SHA256 mismatch"
[[ $(find "${RPM_ROOT}" -maxdepth 1 -type f -name '*.rpm' | wc -l) -eq "${EXPECTED_OLD_COUNT}" ]] || \
  die "old closure does not contain 29 RPMs"
[[ -d "${FAILED_STAGE}" ]] || die "failed 18-RPM stage is missing"
[[ $(find "${FAILED_STAGE}" -maxdepth 1 -type f -name '*.rpm' | wc -l) -eq 18 ]] || \
  die "failed stage does not contain 18 RPMs"
[[ ! -e "${STAGE}" ]] || die "compatible stage already exists"
[[ ! -e "${REJECTED_ROOT}" ]] || die "rejected 29-RPM archive already exists"
[[ ! -e "${FAILED_STAGE_ARCHIVE}" ]] || die "failed stage archive already exists"
[[ ! -e "${FAILED_EVIDENCE_ARCHIVE}" ]] || die "failed evidence archive already exists"

umask 077
install -d -m 0700 "${STAGE}" "${FAILED_EVIDENCE_ARCHIVE}"
mv "${FAILED_STAGE}" "${FAILED_STAGE_ARCHIVE}"

failed_evidence=(
  install-only-rpm-classification.tsv
  install-only-expected-package-names.txt
  install-only-actual-package-names.txt
  install-only-expected-upgrade-candidate-names.txt
  install-only-actual-upgrade-candidate-names.txt
  install-only-openeuler-rpm-signatures.txt
  dnf-install-only-assumeno.log
)
for evidence_file in "${failed_evidence[@]}"; do
  [[ -f "${EVIDENCE_DIR}/${evidence_file}" ]] || die "failed-attempt evidence is missing: ${evidence_file}"
  cp -p "${EVIDENCE_DIR}/${evidence_file}" "${FAILED_EVIDENCE_ARCHIVE}/"
done

printf 'decision\tfile\tnevra\treason\n' >"${CLASSIFICATION}"
copied_count=0
while IFS= read -r -d '' rpm_file; do
  name=$(rpm -qp --qf '%{NAME}' "${rpm_file}" 2>/dev/null)
  nevra=$(rpm -qp --qf '%{NAME}-%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}' "${rpm_file}" 2>/dev/null)
  if contains "${name}" "${KEEP_FROM_OLD_NAMES[@]}"; then
    cp -p "${rpm_file}" "${STAGE}/"
    printf 'KEEP_FROM_29\t%s\t%s\tno-installed-runtime-upgrade\n' \
      "$(basename "${rpm_file}")" "${nevra}" >>"${CLASSIFICATION}"
    copied_count=$((copied_count + 1))
  else
    printf 'ARCHIVE_FROM_29\t%s\t%s\tupgrade-or-incompatible-devel-version\n' \
      "$(basename "${rpm_file}")" "${nevra}" >>"${CLASSIFICATION}"
  fi
done < <(find "${RPM_ROOT}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z)
[[ "${copied_count}" -eq 13 ]] || die "expected 13 reusable RPMs, found ${copied_count}"

dnf --showduplicates list \
  gcc-c++ libstdc++-devel systemd-devel systemd-pam zlib-devel \
  >"${EVIDENCE_DIR}/compatible-devel-available-versions.txt" 2>&1

dnf -y install \
  --downloadonly \
  --downloaddir="${STAGE}" \
  --setopt=strict=True \
  --setopt=install_weak_deps=False \
  --setopt=localpkg_gpgcheck=False \
  "${EXACT_COMPATIBLE_PACKAGES[@]}" \
  >"${EVIDENCE_DIR}/dnf-compatible-devel-download.log" 2>&1

[[ $(find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' | wc -l) -eq "${EXPECTED_FINAL_COUNT}" ]] || \
  die "compatible closure does not contain 18 RPMs"

for expected_nevra in "${EXACT_COMPATIBLE_PACKAGES[@]}"; do
  matched=0
  while IFS= read -r -d '' rpm_file; do
    actual_nevra=$(rpm -qp --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}' "${rpm_file}" 2>/dev/null)
    if [[ "${actual_nevra}" == "${expected_nevra}" ]]; then
      printf 'DOWNLOAD_COMPATIBLE\t%s\t%s\tmatches-installed-runtime-version\n' \
        "$(basename "${rpm_file}")" "${actual_nevra}" >>"${CLASSIFICATION}"
      matched=$((matched + 1))
    fi
  done < <(find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z)
  [[ "${matched}" -eq 1 ]] || die "expected compatible RPM is not present exactly once: ${expected_nevra}"
done

printf '%s\n' "${EXPECTED_FINAL_NAMES[@]}" | sort -u \
  >"${EVIDENCE_DIR}/compatible-expected-package-names.txt"
find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 -r rpm -qp --qf '%{NAME}\n' 2>/dev/null | sort -u \
  >"${EVIDENCE_DIR}/compatible-actual-package-names.txt"
cmp -s "${EVIDENCE_DIR}/compatible-expected-package-names.txt" \
  "${EVIDENCE_DIR}/compatible-actual-package-names.txt" || \
  die "compatible closure package-name set is not the expected set"

open_euler_signature_log="${EVIDENCE_DIR}/compatible-openeuler-rpm-signatures.txt"
: >"${open_euler_signature_log}"
open_euler_count=0
while IFS= read -r -d '' rpm_file; do
  rpm --define '_pkgverify_level signature' -K "${rpm_file}" >>"${open_euler_signature_log}" 2>&1
  open_euler_count=$((open_euler_count + 1))
done < <(find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' \
  ! -name 'percona-orchestrator*.rpm' -print0 | sort -z)
[[ "${open_euler_count}" -eq "${EXPECTED_OPENEULER_COUNT}" ]] || \
  die "expected 15 openEuler RPMs"
[[ $(grep -Ec 'signatures OK$' "${open_euler_signature_log}") -eq "${open_euler_count}" ]] || \
  die "not every openEuler compatible RPM has a trusted signature"
if grep -Eiq 'BAD|NOT OK|NOKEY|NOTTRUSTED' "${open_euler_signature_log}"; then
  die "an openEuler compatible RPM signature is not trusted"
fi

percona_signature_evidence="${EVIDENCE_DIR}/orchestrator-rpm-signature-verified.txt"
[[ $(find "${STAGE}" -maxdepth 1 -type f -name 'percona-orchestrator*.rpm' | wc -l) -eq "${EXPECTED_PERCONA_COUNT}" ]] || \
  die "compatible closure does not contain three Percona RPMs"
[[ $(grep -Eci '8507efa5:[[:space:]]+OK' "${percona_signature_evidence}") -eq 3 ]] || \
  die "expected three verified Percona signatures"
if grep -Eiq 'BAD|NOT OK|NOKEY|NOTTRUSTED' "${percona_signature_evidence}"; then
  die "Percona signature evidence contains a failure"
fi

mapfile -d '' compatible_rpms < <(find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z)
set +e
dnf --disablerepo='*' \
  --setopt=strict=True \
  --setopt=install_weak_deps=False \
  --setopt=localpkg_gpgcheck=False \
  --assumeno install "${compatible_rpms[@]}" \
  >"${EVIDENCE_DIR}/dnf-compatible-closure-assumeno.log" 2>&1
dnf_rc=$?
set -e
[[ "${dnf_rc}" -eq 1 ]] || die "unexpected DNF assumeno exit code: ${dnf_rc}"
grep -Fq 'Transaction Summary' "${EVIDENCE_DIR}/dnf-compatible-closure-assumeno.log" || \
  die "compatible closure did not reach a transaction summary"
grep -Eq '^[[:space:]]*Install[[:space:]]+18[[:space:]]+Packages' \
  "${EVIDENCE_DIR}/dnf-compatible-closure-assumeno.log" || \
  die "compatible closure transaction is not exactly 18 installs"
if grep -Eiq '(^|[[:space:]])(Upgrading|Downgrading|Removing|Reinstalling|Obsoleting)(:|[[:space:]])|^[[:space:]]*(Upgrade|Downgrade|Remove|Reinstall|Obsolete)[[:space:]]+[0-9]+' \
  "${EVIDENCE_DIR}/dnf-compatible-closure-assumeno.log"; then
  die "compatible closure contains a non-install action"
fi
if grep -Eiq 'nothing provides|conflicting requests|transaction test error|protected packages|受保护的软件包|冲突的请求' \
  "${EVIDENCE_DIR}/dnf-compatible-closure-assumeno.log"; then
  die "compatible closure has an unresolved dependency or conflict"
fi

install -d -m 0700 "${EVIDENCE_DIR}/rejected-assets/rpms-29-upgrades-evidence"
shopt -s nullglob
old_evidence=(
  "${EVIDENCE_DIR}"/minimal-*
  "${EVIDENCE_DIR}"/dnf-minimal-closure-assumeno.log
  "${EVIDENCE_DIR}"/v8-*
  "${EVIDENCE_DIR}"/q040-v8-evidence-index.sha256
  "${EVIDENCE_DIR}"/nginx-role-install-dry-run.log
)
for evidence_file in "${old_evidence[@]}"; do
  cp -p "${evidence_file}" "${EVIDENCE_DIR}/rejected-assets/rpms-29-upgrades-evidence/"
done
shopt -u nullglob

mv "${RPM_ROOT}" "${REJECTED_ROOT}"
mv "${RPM_MANIFEST}" "${EVIDENCE_DIR}/rejected-assets/rpms-29-upgrades.sha256"
mv "${STAGE}" "${RPM_ROOT}"

manifest_temp=$(mktemp "${PACKAGE_ROOT}/.rpms.sha256.XXXXXX")
trap 'rm -f "${manifest_temp}"' EXIT
(
  cd "${PACKAGE_ROOT}"
  find rpms -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | xargs -0 sha256sum
) >"${manifest_temp}"
[[ $(wc -l <"${manifest_temp}") -eq "${EXPECTED_FINAL_COUNT}" ]] || \
  die "generated manifest does not contain 18 RPMs"
mv "${manifest_temp}" "${RPM_MANIFEST}"
chmod 0640 "${RPM_MANIFEST}"
chmod -R go-rwx "${RPM_ROOT}" "${REJECTED_ROOT}" \
  "${FAILED_STAGE_ARCHIVE}" "${FAILED_EVIDENCE_ARCHIVE}"

(
  cd "${PACKAGE_ROOT}"
  sha256sum -c rpms.sha256
) >"${EVIDENCE_DIR}/compatible-rpm-sha256-check.txt"
find "${RPM_ROOT}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 -r rpm -qp --qf '%{NAME}\t%{EPOCHNUM}:%{VERSION}-%{RELEASE}\t%{ARCH}\t%{SIZE}\t%{VENDOR}\n' \
  2>/dev/null | sort -u >"${EVIDENCE_DIR}/compatible-rpm-nevra.tsv"
find "${RPM_ROOT}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 sha256sum >"${EVIDENCE_DIR}/compatible-rpm-sha256-absolute.txt"

cp "${EVIDENCE_DIR}/compatible-rpm-nevra.tsv" "${EVIDENCE_DIR}/minimal-rpm-nevra.tsv"
cp "${EVIDENCE_DIR}/compatible-rpm-sha256-absolute.txt" "${EVIDENCE_DIR}/minimal-rpm-sha256-absolute.txt"
cp "${EVIDENCE_DIR}/compatible-openeuler-rpm-signatures.txt" \
  "${EVIDENCE_DIR}/minimal-openeuler-rpm-signatures.txt"
cp "${EVIDENCE_DIR}/dnf-compatible-closure-assumeno.log" \
  "${EVIDENCE_DIR}/dnf-minimal-closure-assumeno.log"

manifest_sha256=$(sha256sum "${RPM_MANIFEST}" | awk '{print $1}')
rpm_bytes=$(du -sb "${RPM_ROOT}" | awk '{print $1}')
{
  printf 'captured_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'rejected_rpm_count=29\n'
  printf 'rejected_upgrade_candidate_count=11\n'
  printf 'compatible_replacement_count=5\n'
  printf 'minimal_rpm_count=%s\n' "${EXPECTED_FINAL_COUNT}"
  printf 'openeuler_rpm_count=%s\n' "${open_euler_count}"
  printf 'percona_rpm_count=%s\n' "${EXPECTED_PERCONA_COUNT}"
  printf 'minimal_rpm_bytes=%s\n' "${rpm_bytes}"
  printf 'rpm_manifest_sha256=%s\n' "${manifest_sha256}"
  printf 'rpm_sha256_check=PASS\n'
  printf 'package_name_set_check=PASS\n'
  printf 'compatible_nevra_check=PASS\n'
  printf 'openeuler_signature_check=PASS\n'
  printf 'percona_signature_evidence=PASS_EXISTING\n'
  printf 'dnf_transaction_actions=INSTALL_ONLY\n'
  printf 'dnf_offline_dependency_resolution=PASS_ASSUMENO\n'
  printf 'install_execution=NOT_PERFORMED\n'
} >"${EVIDENCE_DIR}/minimal-rpm-closure-result.env"
cp "${EVIDENCE_DIR}/minimal-rpm-closure-result.env" \
  "${EVIDENCE_DIR}/compatible-rpm-closure-result.env"

chmod 0600 "${CLASSIFICATION}" \
  "${EVIDENCE_DIR}"/compatible-* \
  "${EVIDENCE_DIR}"/dnf-compatible-*.log \
  "${EVIDENCE_DIR}"/minimal-* \
  "${EVIDENCE_DIR}"/dnf-minimal-closure-assumeno.log

printf 'Q040_COMPATIBLE_RPM_CLOSURE_OK\n'
cat "${EVIDENCE_DIR}/compatible-rpm-closure-result.env"
