#!/bin/bash
#
# Logic self-check for .extras/test-build. Covers the pure, host-side parts:
# dependency resolution, override application, unknown-dep -> UNMET, cache
# decisions, and BLOCKED-BY-DEP propagation. No docker.
#
# Run: bash .extras/test-logic.sh
#
set -uo pipefail

SCRIPT="$(dirname "$0")/test-build"
T=$(mktemp -d)
BASE_DB=$(mktemp -d); mkdir -p "$BASE_DB"

cleanup() { rm -rf "$T" "$BASE_DB"; }
trap cleanup EXIT

# Source the script without running main(). Sourcing re-runs the CONFIG block,
# so set test vars AFTER the source.
LIB=$(mktemp)
sed '/^main "\$@"$/d' "$SCRIPT" > "$LIB"
# shellcheck disable=SC1090
source "$LIB" 2>/dev/null
rm -f "$LIB"

pass=0; fail=0
ok()  { echo "  ok: $1"; ((pass++)); return 0; }
bad() { echo "  FAIL: $1"; ((fail++)); return 0; }

# Placeholder assertion so this file runs before any logic exists.
ok "script sources without executing main"

# --- version selection ------------------------------------------------------
SBO_TREE_CURRENT="/trees/current"
SBO_TREE_STABLE="/trees/stable"
IMAGE_CURRENT="sbo-testbuild:current"
IMAGE_STABLE="sbo-testbuild:15.0"

VERSION_ID="current"; select_version_paths
[[ "$ACTIVE_TREE" == "/trees/current" ]] && ok "current -> current tree" || bad "current tree wrong: [$ACTIVE_TREE]"
[[ "$ACTIVE_IMAGE" == "sbo-testbuild:current" ]] && ok "current -> current image" || bad "current image wrong: [$ACTIVE_IMAGE]"

VERSION_ID="15.0"; select_version_paths
[[ "$ACTIVE_TREE" == "/trees/stable" ]] && ok "15.0 -> stable tree" || bad "stable tree wrong: [$ACTIVE_TREE]"
[[ "$ACTIVE_IMAGE" == "sbo-testbuild:15.0" ]] && ok "15.0 -> stable image" || bad "stable image wrong: [$ACTIVE_IMAGE]"

# --- resolution -------------------------------------------------------------
# Fake SBo tree under one category. mk <prog> "<REQUIRES>".
mk() { mkdir -p "$T/cat/$1"; echo "REQUIRES=\"$2\"" > "$T/cat/$1/$1.info"; }
mk c ""
mk b "c"
mk a "b %README%"
mk d "nonexistentpkg"
mk e "f"
mk f "e"
mk g "b"

SBO_TREE_ROOTS=("$T")   # resolver reads this global (set by select in real runs)

resolve_target "$T/cat/a"
order=""; for x in "${RESOLVED_ORDER[@]}"; do order+="$(basename "$x") "; done
order="${order% }"
[[ "$order" == "c b a" ]] && ok "topo order c b a" || bad "topo order, got: [$order]"
[[ "${HAS_README[$T/cat/a]:-}" == "1" ]] && ok "%README% recorded" || bad "%README% not recorded"
[[ ${#UNMET[@]} -eq 0 ]] && ok "no false unmet" || bad "unexpected unmet"

resolve_target "$T/cat/d"
[[ ${#UNMET[@]} -eq 1 ]] && ok "unmet-dep caught" || bad "unmet-dep missed"

resolve_target "$T/cat/e"
[[ ${#CYCLES[@]} -ge 1 ]] && ok "cycle caught" || bad "cycle missed"

# --- overrides --------------------------------------------------------------
VERSION_ID="current"   # reset: left at 15.0 by the version-selection checks above
OV=$(mktemp)
cat > "$OV" <<'EOF'
# comment ignored
drop:   dropme
rename: oldname -> newname
fetch:  fetchme
EOF
TB_OVERRIDES="$OV"
load_overrides

[[ "${OV_DROP[dropme]:-}" == "1" ]] && ok "override drop parsed" || bad "drop not parsed"
[[ "${OV_RENAME[oldname]:-}" == "newname" ]] && ok "override rename parsed" || bad "rename not parsed"
[[ "${OV_FETCH[fetchme]:-}" == "1" ]] && ok "override fetch parsed" || bad "fetch not parsed"

# rename maps a token
[[ "$(apply_rename oldname)" == "newname" ]] && ok "apply_rename maps" || bad "apply_rename wrong: [$(apply_rename oldname)]"
[[ "$(apply_rename untouched)" == "untouched" ]] && ok "apply_rename passthrough" || bad "apply_rename mangled untouched"

# drop filters the order (build a fake order of dirs; dropme is removed)
mk dropme ""
mk keepme ""
RESOLVED_ORDER=("$T/cat/dropme" "$T/cat/keepme")
apply_overrides_to_order
kept=""; for x in "${RESOLVED_ORDER[@]}"; do kept+="$(basename "$x") "; done
kept="${kept% }"
[[ "$kept" == "keepme" ]] && ok "drop removes from order" || bad "drop failed, order=[$kept]"
rm -f "$OV"

# overrides only apply on current; on 15.0 they are a no-op
VERSION_ID="15.0"
TB_OVERRIDES="$OV"   # file gone; must not error, must clear maps
load_overrides
[[ ${#OV_DROP[@]} -eq 0 ]] && ok "overrides inert on 15.0" || bad "overrides applied on 15.0"
VERSION_ID="current"

# --- cache ------------------------------------------------------------------
PKG_CACHE=$(mktemp -d)
CACHE_ROOT="$PKG_CACHE/sha256-deadbeef"   # simulate a resolved digest namespace
mkc() { mkdir -p "$CACHE_ROOT/$1/$2"; : > "$CACHE_ROOT/$1/$2/$3"; }

mkc net libfoo "libfoo-1.1-x86_64-1_danix.txz"
[[ "$(cache_decision net libfoo 1.1)" == "cached" ]] && ok "cache hit on version match" || bad "cache_decision got [$(cache_decision net libfoo 1.1)]"
[[ "$(cache_decision net libfoo 1.2)" == "bump:1.1:1.2" ]] && ok "cache bump reported" || bad "cache bump got [$(cache_decision net libfoo 1.2)]"
[[ "$(cache_decision net libbar 1.0)" == "new" ]] && ok "cache new for absent" || bad "cache new got [$(cache_decision net libbar 1.0)]"

hit="$(cache_path net libfoo 1.1)"
[[ "$hit" == "$CACHE_ROOT/net/libfoo/libfoo-1.1-x86_64-1_danix.txz" ]] && ok "cache_path returns hit" || bad "cache_path got [$hit]"
[[ -z "$(cache_path net libfoo 9.9)" ]] && ok "cache_path empty on miss" || bad "cache_path not empty on miss"

srctmp=$(mktemp -d); : > "$srctmp/libfoo-1.2-x86_64-1_danix.txz"
cache_store net libfoo "$srctmp/libfoo-1.2-x86_64-1_danix.txz"
count=$(find "$CACHE_ROOT/net/libfoo" -name '*.t?z' | wc -l)
[[ "$count" -eq 1 ]] && ok "cache_store evicts to one file" || bad "cache_store left $count files"
[[ -e "$CACHE_ROOT/net/libfoo/libfoo-1.2-x86_64-1_danix.txz" ]] && ok "cache_store stored new file" || bad "cache_store did not store"
rm -rf "$srctmp"

# disabled cache -> new
CACHE_ROOT_SAVE="$CACHE_ROOT"; USE_CACHE=0
[[ "$(cache_decision net libfoo 1.2)" == "new" ]] && ok "--no-cache disables (new)" || bad "disabled cache got [$(cache_decision net libfoo 1.2)]"
USE_CACHE=1; CACHE_ROOT="$CACHE_ROOT_SAVE"

# --- BLOCKED-BY-DEP (depends_on_failed) -------------------------------------
dead=(b)
if depends_on_failed "$T/cat/g" dead; then ok "g blocked when b dead"; else bad "g should block on b"; fi
if depends_on_failed "$T/cat/a" dead; then ok "a blocked when b dead (direct)"; else bad "a should block on b"; fi

dead=(c)
if depends_on_failed "$T/cat/a" dead; then bad "a wrongly blocked on c"; else ok "a not directly blocked by c"; fi
if depends_on_failed "$T/cat/b" dead; then ok "b blocked when c dead"; else bad "b should block on c"; fi

dead=()
if depends_on_failed "$T/cat/a" dead; then bad "a blocked with empty dead"; else ok "no block when nothing dead"; fi

dead=("%README%")
if depends_on_failed "$T/cat/a" dead; then bad "%README% treated as dep"; else ok "%README% not treated as dep"; fi

echo
echo "$pass passed, $fail failed"
[[ $fail -eq 0 ]] || exit 1
echo "ALL LOGIC CHECKS PASS"
