#!/usr/bin/env bash

set -Eeuo pipefail
export LANG=C

EXPECTED_HOSTNAME="sqjcapp01"
EXPECTED_IPV4="10.2.203.104"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
BACKUP_ROOT="/var/backups/zhct-q050"
BUNDLE_ROOT="/opt/zhct-deploy"
PACKAGE_ROOT="${BUNDLE_ROOT}/packages"
RPM_ROOT="${PACKAGE_ROOT}/rpms"
STAGE="${PACKAGE_ROOT}/rpms.q050-stage"

ROOT_PACKAGES=(
  autoconf
  automake
  bison
  bzip2
  c-ares-devel
  cmake
  cronie
  crontabs
  curl
  diffutils
  file
  findutils
  flex
  freetype-devel
  gawk
  gcc
  gcc-c++
  gettext
  grep
  gzip
  libaio
  libcurl-devel
  libicu-devel
  libjpeg-turbo-devel
  libpng-devel
  libtool
  libwebp-devel
  libxml2-devel
  libzip-devel
  make
  ncurses-compat-libs
  oniguruma-devel
  openssl-devel
  patch
  pcre2-devel
  pkgconf-pkg-config
  python3
  python3-pip
  re2c
  rsync
  sed
  sqlite-devel
  systemd-devel
  tar
  which
  xz
  zlib-devel
)

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

[[ "${EUID}" -eq 0 ]] || die "must run as root"
[[ $# -eq 1 ]] || die "usage: $0 <q050-baseline-run-id>"
RUN_ID="$1"
[[ "${RUN_ID}" =~ ^[0-9]{8}T[0-9]{6}[+-][0-9]{4}$ ]] || die "invalid run id"
EVIDENCE_DIR="${BACKUP_ROOT}/${RUN_ID}"

[[ "$(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"
[[ -f "${EVIDENCE_DIR}/source-assets-result.env" ]] || \
  die "source asset result is missing"
grep -Fxq 'source_asset_download=PASS' "${EVIDENCE_DIR}/source-assets-result.env" || \
  die "source asset download gate is not PASS"
command -v dnf >/dev/null || die "dnf not found"
[[ ! -e "${RPM_ROOT}" ]] || die "RPM closure already exists: ${RPM_ROOT}"

umask 077
if [[ -d "${STAGE}" ]]; then
  find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print -quit | \
    grep -q . || die "existing RPM stage is empty: ${STAGE}"
  STAGE_REUSED=1
else
  [[ ! -e "${STAGE}" ]] || die "RPM stage path is not a directory: ${STAGE}"
  install -d -m 0750 "${STAGE}"
  STAGE_REUSED=0

  dnf -v repolist --all >"${EVIDENCE_DIR}/dnf-repolist-before-rpm-closure.txt" 2>&1
  find /etc/yum.repos.d -maxdepth 2 -type f -print0 | sort -z | \
    xargs -0 -r sha256sum >"${EVIDENCE_DIR}/yum-repo-files.sha256"
  grep -RHE '^[[:space:]]*(\[|name=|baseurl=|metalink=|mirrorlist=|enabled=|gpgcheck=|repo_gpgcheck=|gpgkey=)' \
    /etc/yum.repos.d >"${EVIDENCE_DIR}/yum-repo-effective-lines.txt" || true

  dnf --showduplicates list "${ROOT_PACKAGES[@]}" \
    >"${EVIDENCE_DIR}/root-packages-available-versions.txt" 2>&1
  rpm -qa --qf '%{NAME}\n' | sort -u \
    >"${EVIDENCE_DIR}/rpm-installed-name-excludes.txt"
  INSTALLED_NAME_EXCLUDES="$(paste -sd, "${EVIDENCE_DIR}/rpm-installed-name-excludes.txt")"
  [[ -n "${INSTALLED_NAME_EXCLUDES}" ]] || die "installed package exclusion set is empty"
  REQUESTED_PACKAGES=()
  : >"${EVIDENCE_DIR}/rpm-requested-uninstalled-roots.txt"
  for root_package in "${ROOT_PACKAGES[@]}"; do
    if ! rpm -q --whatprovides "${root_package}" >/dev/null 2>&1; then
      REQUESTED_PACKAGES+=("${root_package}")
      printf '%s\n' "${root_package}" \
        >>"${EVIDENCE_DIR}/rpm-requested-uninstalled-roots.txt"
    fi
  done
  [[ "${#REQUESTED_PACKAGES[@]}" -gt 0 ]] || die "all root packages are already installed"
  dnf -y install \
    --downloadonly \
    --nobest \
    --exclude="${INSTALLED_NAME_EXCLUDES}" \
    --downloaddir="${STAGE}" \
    --setopt=strict=True \
    --setopt=install_weak_deps=False \
    --setopt=localpkg_gpgcheck=False \
    "${REQUESTED_PACKAGES[@]}" \
    >"${EVIDENCE_DIR}/dnf-rpm-closure-download.log" 2>&1
fi

PRUNED_INSTALLED="${EVIDENCE_DIR}/rpm-pruned-installed.tsv"
: >"${PRUNED_INSTALLED}"
while IFS= read -r -d '' rpm_file; do
  rpm_name="$(rpm -qp --qf '%{NAME}' "${rpm_file}")"
  if rpm -q "${rpm_name}" >/dev/null 2>&1; then
    printf '%s\t%s\t%s\n' \
      "${rpm_name}" \
      "$(rpm -q --qf '%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}' "${rpm_name}")" \
      "$(rpm -qp --qf '%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}' "${rpm_file}")" \
      >>"${PRUNED_INSTALLED}"
    rm -f -- "${rpm_file}"
  fi
done < <(find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z)

RPM_COUNT="$(find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' | wc -l)"
[[ "${RPM_COUNT}" -gt 0 ]] || die "DNF did not download any RPM"
PRUNED_INSTALLED_COUNT="$(wc -l <"${PRUNED_INSTALLED}")"

find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 -r rpm --define '_pkgverify_level signature' -K \
  >"${EVIDENCE_DIR}/rpm-signatures.txt" 2>&1
[[ "$(grep -Ec 'signatures OK$' "${EVIDENCE_DIR}/rpm-signatures.txt")" -eq "${RPM_COUNT}" ]] || \
  die "not every RPM has a trusted signature"
if grep -Eiq 'BAD|NOT OK|NOKEY|NOTTRUSTED' "${EVIDENCE_DIR}/rpm-signatures.txt"; then
  die "an RPM signature is not trusted"
fi

find "${STAGE}" -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}/rpm-nevra.tsv"
find /var/cache/dnf -type f -path '*/repodata/repomd.xml' -print0 | sort -z | \
  xargs -0 -r sha256sum >"${EVIDENCE_DIR}/dnf-repomd-sha256.txt"

mapfile -d '' closure_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 "${closure_rpms[@]}" \
  >"${EVIDENCE_DIR}/dnf-offline-closure-assumeno.log" 2>&1
DNF_RC=$?
set -e
[[ "${DNF_RC}" -eq 1 ]] || die "unexpected offline DNF assumeno exit code: ${DNF_RC}"
if grep -Eiq 'nothing provides|conflicting requests|transaction test error|protected packages|受保护的软件包|冲突的请求' \
  "${EVIDENCE_DIR}/dnf-offline-closure-assumeno.log"; then
  die "RPM closure has an unresolved dependency or conflict"
fi
grep -Fq 'Transaction Summary' "${EVIDENCE_DIR}/dnf-offline-closure-assumeno.log" || \
  die "offline closure did not reach a transaction summary"
if grep -Eiq '(^|[[:space:]])(Upgrading|Downgrading|Removing|Reinstalling|Obsoleting)(:|[[:space:]])|^[[:space:]]*(Upgrade|Downgrade|Remove|Reinstall|Obsolete)[[:space:]]+[0-9]+' \
  "${EVIDENCE_DIR}/dnf-offline-closure-assumeno.log"; then
  die "RPM closure contains a non-install action; stage and evidence preserved for compatible-version refinement"
fi
mv "${STAGE}" "${RPM_ROOT}"
(
  cd "${PACKAGE_ROOT}"
  find rpms -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | xargs -0 sha256sum
) >"${PACKAGE_ROOT}/rpms.sha256"
chmod 0640 "${PACKAGE_ROOT}/rpms.sha256"
chmod -R go-rwx "${RPM_ROOT}"

(
  cd "${PACKAGE_ROOT}"
  sha256sum -c rpms.sha256
) >"${EVIDENCE_DIR}/rpm-sha256-check.txt"

RPM_MANIFEST_SHA256="$(sha256sum "${PACKAGE_ROOT}/rpms.sha256" | awk '{print $1}')"
RPM_BYTES="$(du -sb "${RPM_ROOT}" | awk '{print $1}')"
{
  printf 'captured_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'rpm_count=%s\n' "${RPM_COUNT}"
  printf 'rpm_bytes=%s\n' "${RPM_BYTES}"
  printf 'rpm_manifest_sha256=%s\n' "${RPM_MANIFEST_SHA256}"
  printf 'rpm_sha256_check=PASS\n'
  printf 'rpm_signature_check=PASS\n'
  printf 'rpm_stage_reused=%s\n' "${STAGE_REUSED}"
  printf 'rpm_pruned_installed_count=%s\n' "${PRUNED_INSTALLED_COUNT}"
  printf 'dnf_transaction_actions=INSTALL_ONLY\n'
  printf 'dnf_offline_dependency_resolution=PASS_ASSUMENO\n'
  printf 'install_execution=NOT_PERFORMED\n'
} >"${EVIDENCE_DIR}/rpm-closure-result.env"
chmod 0600 "${EVIDENCE_DIR}"/dnf-* \
  "${EVIDENCE_DIR}"/root-packages-* \
  "${EVIDENCE_DIR}"/rpm-* \
  "${EVIDENCE_DIR}"/yum-repo-*

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