#!/usr/bin/env bash
# check_packages.sh — Compare Ceph package counts between production and prerelease

set -euo pipefail

# ── Config ────────────────────────────────────────────────────────────────────
# Search locations per distro (<ver> = prod or prerelease version):
#   deb  jammy/focal/noble/bookworm/trixie:
#     prod: $PROD_BASE/debian-<ver>/       pre: $PRE_BASE/debian-<ver>/
#   RPM  centos9:
#     prod: $PROD_BASE/rpm-<ver>/el9/      pre: $PRE_BASE/rpm-<ver>/el9/
#   RPM  centos10 + rocky10 (shared el10 repo):
#     prod: $PROD_BASE/rpm-<ver>/el10/     pre: $PRE_BASE/rpm-<ver>/el10/
PROD_BASE="/data/download.ceph.com/www"
PRE_BASE="/data/download.ceph.com/www/prerelease/ceph"
TMPDIR_BASE=$(mktemp -d)
trap 'rm -rf "$TMPDIR_BASE"' EXIT

# ── Remembered values from last run ─────────────────────────────────────────
STATE_FILE="${HOME}/.cache/ceph-packages-check/last_run"
LAST_PRE_VERSION=""
LAST_PROD_VERSION=""
LAST_DISTROS=""
if [[ -f "$STATE_FILE" ]]; then
    # shellcheck disable=SC1090
    LAST_PRE_VERSION=$(grep '^LAST_PRE_VERSION=' "$STATE_FILE" | cut -d= -f2- | tr -d '"')
    LAST_PROD_VERSION=$(grep '^LAST_PROD_VERSION=' "$STATE_FILE" | cut -d= -f2- | tr -d '"')
    LAST_DISTROS=$(grep '^LAST_DISTROS=' "$STATE_FILE" | cut -d= -f2- | tr -d '"')
fi

save_state() {
    mkdir -p "$(dirname "$STATE_FILE")"
    cat > "$STATE_FILE" <<EOF
LAST_PRE_VERSION="${PRE_VERSION}"
LAST_PROD_VERSION="${PROD_VERSION}"
LAST_DISTROS="${DISTROS_INPUT}"
EOF
}

# ── Colors ────────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'

# ── Distro → filename pattern mapping ─────────────────────────────────────────
# Returns pipe-separated list of patterns (supports multiple)
distro_patterns() {
    case "$1" in
        jammy)    echo "*jam*deb" ;;
        focal)    echo "*focal*deb" ;;
        noble)    echo "*noble*deb" ;;
        bookworm) echo "*bpo12*deb|*bookworm*deb" ;;
        trixie)   echo "*trixie*deb" ;;
        centos9)  echo "*.el9.rpm" ;;
        centos10) echo "*.el10.rpm" ;;
        rocky10)  echo "*.el10.rpm" ;;
        *)        echo "*${1}*deb" ;;
    esac
}

# Display label for patterns
distro_pattern_label() {
    distro_patterns "$1" | tr '|' '/'
}

is_rpm_distro() {
    [[ "$1" == centos* || "$1" == rocky* ]]
}

el_version() {
    case "$1" in
        centos9)  echo "el9" ;;
        centos10) echo "el10" ;;
        rocky10)  echo "el10" ;;
        *)        echo "el9" ;;
    esac
}

# ── Canonical DISTROS per branch ────────────────────────────────────────────
# Source: https://docs.ceph.com/en/latest/dev/release-process/ Distro Codemap
# table — this is the actual release matrix (matches real Jenkins DISTROS
# params, e.g. tentacle build #48). The os-recommendations "Platforms" table
# lists some distros as "Upcoming" (e.g. noble for tentacle) but "Upcoming"
# does NOT mean "in the matrix, build it" — confirmed directly by Yuri:
# noble shipped in 20.2.1/20.2.2 outside the matrix, and won't be rebuilt for
# tentacle going forward since it was never actually in it. Don't re-add it
# from the Platforms table again. Windows (D) is excluded — this script only
# checks deb/rpm repos. Last verified 2026-08-07.
branch_for_version() {
    local major="${1%%.*}"
    case "$major" in
        19) echo "squid" ;;
        20) echo "tentacle" ;;
        21) echo "umbrella" ;;
        *)  echo "" ;;
    esac
}

canonical_distros_for_branch() {
    case "$1" in
        squid)    echo "jammy centos9 bookworm" ;;
        tentacle) echo "jammy centos9 rocky10 bookworm" ;;
        umbrella) echo "jammy centos9 rocky10 bookworm noble trixie" ;;
        *)        echo "" ;;
    esac
}

# ── Auto-decrement patch version ──────────────────────────────────────────────
decrement_patch() {
    local ver="$1"
    local major minor patch
    IFS='.' read -r major minor patch <<< "$ver"
    if [[ "$patch" -gt 0 ]]; then
        echo "${major}.${minor}.$((patch - 1))"
    else
        echo ""
    fi
}

# ── Summary tracking ──────────────────────────────────────────────────────────
declare -a SUMMARY_ROWS=()
declare -a PATH_ROWS=()

add_summary() {
    local distro="$1" pattern="$2" prod_count="$3" pre_count="$4" status="$5"
    SUMMARY_ROWS+=("$distro|$pattern|$prod_count|$pre_count|$status")
}

print_summary() {
    if [[ ${#SUMMARY_ROWS[@]} -eq 0 ]]; then
        echo -e "\n${YELLOW}No distros were compared.${RESET}"
        return
    fi
    echo -e "\n${BOLD}Summary${RESET}  (prod: ${PROD_VERSION}  →  prerelease: ${PRE_VERSION})"
    echo "────────────────────────────────────────────────────────────────"
    printf "  %-18s  %-26s  %6s  %6s  %s\n" "DISTRO" "PATTERN" "PROD" "PRE" "STATUS"
    echo "  ──────────────────────────────────────────────────────────────"
    for row in "${SUMMARY_ROWS[@]}"; do
        IFS='|' read -r distro pattern prod pre status <<< "$row"
        if [[ "$status" == "OK" ]]; then
            color="$GREEN"
        else
            color="$RED"
        fi
        printf "  %-18s  %-26s  %6s  %6s  ${color}%s${RESET}\n" \
            "$distro" "($pattern)" "$prod" "$pre" "$status"
    done
    echo "────────────────────────────────────────────────────────────────"
}

print_paths() {
    [[ ${#PATH_ROWS[@]} -eq 0 ]] && return
    echo -e "\n${BOLD}Search paths${RESET}"
    for row in "${PATH_ROWS[@]}"; do
        IFS='|' read -r label prod_dir pre_dir <<< "$row"
        printf "  %-18s  prod: %s\n" "$label" "$prod_dir"
        printf "  %-18s  pre:  %s\n" "" "$pre_dir"
    done
}

# Warn about canonical-for-branch distros that this run never checked
# (skipped at the [Y/n] prompt, or left off DISTROS_INPUT entirely) —
# this is what would have caught noble silently missing from 20.2.3.
print_coverage_warning() {
    local branch="$1" canonical="$2"
    shift 2
    local -a checked=("$@")
    [[ -z "$canonical" ]] && return
    local -a missing=()
    local c found
    for c in $canonical; do
        found=0
        for d in "${checked[@]}"; do
            [[ "$c" == "$d" ]] && { found=1; break; }
        done
        [[ "$found" -eq 0 ]] && missing+=("$c")
    done
    [[ ${#missing[@]} -eq 0 ]] && return
    echo -e "\n${YELLOW}${BOLD}⚠ Not checked this run — canonical for '${branch}' per docs.ceph.com/en/latest/start/os-recommendations/:${RESET}"
    printf "  ${YELLOW}%s${RESET}\n" "${missing[*]}"
}

# ── Extra-distro detection ──────────────────────────────────────────────────
# Independent of anything the human typed: lists what was ACTUALLY built
# (real dists/ subdirs on disk, real el<N> dirs) and flags anything outside
# the matrix. This is what would have caught noble being built for a release
# where it isn't in canonical_distros_for_branch — e.g. 20.2.4 building
# jammy/centos9/rocky10/bookworm as expected, but noble also showing up.
actual_deb_dists() {
    local dir="$1"
    [[ -d "$dir/dists" ]] || return 0
    find "$dir/dists" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | sort -u
}

canonical_deb_codenames_for_branch() {
    local d
    for d in $(canonical_distros_for_branch "$1"); do
        is_rpm_distro "$d" || echo "$d"
    done
}

# Prompts (does not block/exit) for each extra deb codename found on disk
# that isn't in the branch's canonical list.
check_extra_distros() {
    local branch="$1" prod_debian_dir="$2" pre_debian_dir="$3"
    [[ -z "$branch" ]] && return
    local -a canonical
    canonical=($(canonical_deb_codenames_for_branch "$branch"))
    local -a on_disk=()
    local d
    for d in $(actual_deb_dists "$prod_debian_dir") $(actual_deb_dists "$pre_debian_dir"); do
        on_disk+=("$d")
    done
    local -a extras=()
    local found c
    for d in $(printf '%s\n' "${on_disk[@]}" | sort -u); do
        found=0
        for c in "${canonical[@]}"; do
            [[ "$d" == "$c" ]] && { found=1; break; }
        done
        [[ "$found" -eq 0 ]] && extras+=("$d")
    done
    [[ ${#extras[@]} -eq 0 ]] && return
    echo -e "\n${RED}${BOLD}⚠ Built but NOT in the '${branch}' matrix (possible mistake):${RESET}"
    printf "  ${RED}%s${RESET}\n" "${extras[*]}"
    for d in "${extras[@]}"; do
        read -erp "$(echo -e "  ${d} — was this intentional? [y/N]: ")" ans
        if [[ "${ans,,}" == "y" ]]; then
            echo -e "    ${YELLOW}Acknowledged as intentional — consider updating the matrix/docs.${RESET}"
        else
            echo -e "    ${RED}Flagged — likely built by mistake, follow up before promoting.${RESET}"
        fi
    done
}

# ── List files in a dir matching one or more pipe-separated patterns ───────────
find_by_patterns() {
    local dir="$1"
    local patterns="$2"   # pipe-separated, e.g. "*bpo12*deb|*bookworm*deb"
    local tmpout
    tmpout=$(mktemp "$TMPDIR_BASE/find_XXXXXX")

    IFS='|' read -ra pats <<< "$patterns"
    for pat in "${pats[@]}"; do
        find "$dir" -name "$pat" -printf '%f\n' 2>/dev/null >> "$tmpout"
    done

    # de-duplicate and sort
    sort -u "$tmpout"
    rm -f "$tmpout"
}

# ── Compare a single distro ───────────────────────────────────────────────────
compare() {
    local label="$1"
    local prod_dir="$2"
    local pre_dir="$3"
    local patterns="$4"   # pipe-separated
    local pattern_label
    pattern_label=$(echo "$patterns" | tr '|' '/')

    PATH_ROWS+=("$label|$prod_dir|$pre_dir")

    local prod_list pre_list
    prod_list=$(mktemp "$TMPDIR_BASE/prod_XXXXXX")
    pre_list=$(mktemp "$TMPDIR_BASE/pre_XXXXXX")

    if [[ -d "$prod_dir" ]]; then
        find_by_patterns "$prod_dir" "$patterns" > "$prod_list"
    else
        touch "$prod_list"
        echo -e "  ${YELLOW}WARNING: prod dir not found: $prod_dir${RESET}"
    fi

    if [[ -d "$pre_dir" ]]; then
        find_by_patterns "$pre_dir" "$patterns" > "$pre_list"
    else
        touch "$pre_list"
        echo -e "  ${YELLOW}WARNING: prerelease dir not found: $pre_dir${RESET}"
    fi

    local prod_count pre_count
    prod_count=$(wc -l < "$prod_list")
    pre_count=$(wc -l < "$pre_list")

    printf "  %-30s  prod: %4d  pre: %4d" "$label" "$prod_count" "$pre_count"

    if [[ "$prod_count" -eq "$pre_count" ]]; then
        echo -e "  ${GREEN}✓ match${RESET}"
        add_summary "$label" "$pattern_label" "$prod_count" "$pre_count" "OK"
        return
    fi

    echo -e "  ${RED}✗ MISMATCH (diff: $(( pre_count - prod_count )))${RESET}"
    add_summary "$label" "$pattern_label" "$prod_count" "$pre_count" "MISMATCH ($(( pre_count - prod_count )))"

    local only_prod only_pre
    only_prod=$(comm -23 "$prod_list" "$pre_list")
    only_pre=$(comm -13 "$prod_list" "$pre_list")

    if [[ -n "$only_prod" ]]; then
        echo -e "\n    ${CYAN}Only in PROD (${PROD_VERSION}):${RESET}"
        echo "$only_prod" | sed 's/^/      /'
    fi
    if [[ -n "$only_pre" ]]; then
        echo -e "\n    ${CYAN}Only in PRERELEASE (${PRE_VERSION}):${RESET}"
        echo "$only_pre" | sed 's/^/      /'
    fi
    echo
}

# ── Self-test ────────────────────────────────────────────────────────────────
selftest() {
    local fail=0
    [[ "$(branch_for_version 20.2.3)" == "tentacle" ]] || { echo "FAIL: branch_for_version 20.2.3"; fail=1; }
    [[ "$(branch_for_version 19.2.4)" == "squid" ]]    || { echo "FAIL: branch_for_version 19.2.4"; fail=1; }
    [[ "$(branch_for_version 21.1.0)" == "umbrella" ]] || { echo "FAIL: branch_for_version 21.1.0"; fail=1; }
    [[ "$(canonical_distros_for_branch tentacle)" != *noble* ]] || { echo "FAIL: tentacle canonical wrongly includes noble (not in the real matrix)"; fail=1; }
    [[ "$(canonical_distros_for_branch umbrella)" == *noble* ]] || { echo "FAIL: umbrella canonical missing noble"; fail=1; }

    local out
    out=$(print_coverage_warning "tentacle" "jammy centos9 rocky10 bookworm" jammy centos9 rocky10)
    [[ "$out" == *bookworm* ]] || { echo "FAIL: coverage warning didn't flag missing bookworm"; fail=1; }
    out=$(print_coverage_warning "tentacle" "jammy centos9 rocky10 bookworm" jammy centos9 rocky10 bookworm)
    [[ -z "$out" ]] || { echo "FAIL: coverage warning fired with nothing missing"; fail=1; }

    # check_extra_distros: 20.2.4-style fixture — matrix expects
    # jammy/centos9/rocky10/bookworm, but noble also got built on disk.
    local fixdir; fixdir=$(mktemp -d)
    mkdir -p "$fixdir/pre/dists/jammy" "$fixdir/pre/dists/bookworm" "$fixdir/pre/dists/noble"
    out=$(echo "n" | check_extra_distros "tentacle" "$fixdir/prod-missing" "$fixdir/pre")
    [[ "$out" == *"noble"* ]] || { echo "FAIL: check_extra_distros didn't flag noble"; fail=1; }
    out=$(echo "n" | check_extra_distros "umbrella" "$fixdir/prod-missing" "$fixdir/pre")
    [[ -z "$out" ]] || { echo "FAIL: check_extra_distros flagged noble for umbrella, where it's canonical"; fail=1; }
    rm -rf "$fixdir"

    if [[ "$fail" -eq 0 ]]; then echo "selftest: PASS"; else echo "selftest: FAIL"; fi
    return "$fail"
}
[[ "${1:-}" == "--selftest" ]] && { selftest; exit $?; }

# ── Main ──────────────────────────────────────────────────────────────────────
echo -e "\n${BOLD}Ceph Package Comparison Tool${RESET}"
echo "────────────────────────────────────────"

# 1. Prerelease version
if [[ -n "$LAST_PRE_VERSION" ]]; then
    read -erp "$(echo -e "${CYAN}Prerelease version (e.g. 20.2.1) [${LAST_PRE_VERSION}]: ${RESET}")" PRE_VERSION
    PRE_VERSION="${PRE_VERSION:-$LAST_PRE_VERSION}"
else
    read -erp "$(echo -e "${CYAN}Prerelease version (e.g. 20.2.1): ${RESET}")" PRE_VERSION
fi
[[ -z "$PRE_VERSION" ]] && { echo "Prerelease version required."; exit 1; }

# 2. Prod version — default to patch-1, falling back to last-used value
PROD_DEFAULT=$(decrement_patch "$PRE_VERSION")
PROD_DEFAULT="${PROD_DEFAULT:-$LAST_PROD_VERSION}"
if [[ -n "$PROD_DEFAULT" ]]; then
    read -erp "$(echo -e "${CYAN}Production version [${PROD_DEFAULT}]: ${RESET}")" PROD_VERSION
    PROD_VERSION="${PROD_VERSION:-$PROD_DEFAULT}"
else
    read -erp "$(echo -e "${CYAN}Production version: ${RESET}")" PROD_VERSION
fi
[[ -z "$PROD_VERSION" ]] && { echo "Production version required."; exit 1; }

# 3. Distros — default to the canonical list for this release's branch
#    (docs.ceph.com Platforms table), not just whatever was typed last time.
BRANCH=$(branch_for_version "$PRE_VERSION")
CANONICAL_DISTROS=$(canonical_distros_for_branch "$BRANCH")
echo -e "${CYAN}Available distros: jammy focal noble bookworm trixie centos9 centos10 rocky10${RESET}"
if [[ -n "$BRANCH" ]]; then
    echo -e "${CYAN}Branch: ${BRANCH}  →  canonical DISTROS: ${CANONICAL_DISTROS}${RESET}"
fi
DISTROS_DEFAULT="${CANONICAL_DISTROS:-$LAST_DISTROS}"
if [[ -n "$DISTROS_DEFAULT" ]]; then
    read -erp "$(echo -e "${CYAN}Distros to check (space-separated) [${DISTROS_DEFAULT}]: ${RESET}")" DISTROS_INPUT
    DISTROS_INPUT="${DISTROS_INPUT:-$DISTROS_DEFAULT}"
else
    read -erp "$(echo -e "${CYAN}Distros to check (space-separated): ${RESET}")" DISTROS_INPUT
fi
read -ra DISTROS <<< "$DISTROS_INPUT"
[[ ${#DISTROS[@]} -eq 0 ]] && { echo "At least one distro required."; exit 1; }

save_state

echo
echo -e "${BOLD}Prod:${RESET}       ${PROD_VERSION}  →  ${PROD_BASE}/debian-${PROD_VERSION}  |  rpm-${PROD_VERSION}/"
echo -e "${BOLD}Prerelease:${RESET} ${PRE_VERSION}  →  ${PRE_BASE}/debian-${PRE_VERSION}  |  rpm-${PRE_VERSION}/"
echo "────────────────────────────────────────"

# 4. Loop distros interactively
CHECKED_DISTROS=()
for distro in "${DISTROS[@]}"; do
    echo
    if is_rpm_distro "$distro"; then
        local_el=$(el_version "$distro")
        read -erp "$(echo -e "${BOLD}Compare ${distro} (RPM/${local_el})? [Y/n]: ${RESET}")" yn
        [[ "${yn,,}" == "n" ]] && { echo -e "  ${YELLOW}Skipped.${RESET}"; continue; }
        echo -e "${BOLD}[${distro}]${RESET}"
        prod_dir="$PROD_BASE/rpm-${PROD_VERSION}/${local_el}"
        pre_dir="$PRE_BASE/rpm-${PRE_VERSION}/${local_el}"
        compare "RPM (${distro}/${local_el})" "$prod_dir" "$pre_dir" "*.rpm"
        CHECKED_DISTROS+=("$distro")
    else
        patterns=$(distro_patterns "$distro")
        pattern_label=$(distro_pattern_label "$distro")
        read -erp "$(echo -e "${BOLD}Compare ${distro} (${pattern_label})? [Y/n]: ${RESET}")" yn
        [[ "${yn,,}" == "n" ]] && { echo -e "  ${YELLOW}Skipped.${RESET}"; continue; }
        echo -e "${BOLD}[${distro}]${RESET}"
        prod_dir="$PROD_BASE/debian-${PROD_VERSION}"
        pre_dir="$PRE_BASE/debian-${PRE_VERSION}"
        compare "deb ($distro)" "$prod_dir" "$pre_dir" "$patterns"
        CHECKED_DISTROS+=("$distro")
    fi
done

print_paths
print_summary
print_coverage_warning "$BRANCH" "$CANONICAL_DISTROS" "${CHECKED_DISTROS[@]}"
check_extra_distros "$BRANCH" "$PROD_BASE/debian-${PROD_VERSION}" "$PRE_BASE/debian-${PRE_VERSION}"

echo -e "\n${BOLD}Done.${RESET}\n"
