#!/usr/bin/env bash
# Fixture test for deckctl install (artifact modules): subtree split, state symlinks,
# health-gated current flip, prune keeps current + one predecessor.
set -euo pipefail
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
DECKCTL="$ROOT/bin/deckctl"
FIX=$(mktemp -d "${TMPDIR:-/tmp}/deckctl-install-test-XXXXXX")
trap 'rm -rf "$FIX"' EXIT
pass=0; fail=0
ok()  { echo "  PASS: $1"; pass=$((pass+1)); }
bad() { echo "  FAIL: $1"; fail=$((fail+1)); }

export TEST_HOME="$FIX/home"
mkdir -p "$TEST_HOME/.local/opt/overdeck" "$TEST_HOME/.local/state/overdeck"

run_deckctl() { # $1=repo_root +=args
  HOME="$TEST_HOME" "$1/bin/deckctl" "${@:2}"
}

mk_repo() { # $1=dest
  local dest="$1"
  git init -q -b main "$dest"
  mkdir -p "$dest/spec" "$dest/bin" "$dest/lib/deckctl" "$dest/modules/artimod/bin"
  cp "$ROOT/spec/deck-module.schema.json" "$dest/spec/"
  cp "$ROOT/bin/deckctl" "$dest/bin/"
  cp "$ROOT/lib/deckctl/doctor.sh" "$dest/lib/deckctl/"
  cp "$ROOT/lib/deckctl/status.sh" "$dest/lib/deckctl/"
  cp "$ROOT/lib/deckctl/install.sh" "$dest/lib/deckctl/"

  cat >"$dest/modules/artimod/deck.module.json" <<'EOF'
{
  "schema_version": 1,
  "name": "artimod",
  "kind": "cli",
  "health": "./health.sh",
  "install": { "mode": "artifact", "needs_git": true },
  "state_dirs": ["data"]
}
EOF

  printf '#!/usr/bin/env bash\necho ok\n' >"$dest/modules/artimod/bin/hello"
  chmod +x "$dest/modules/artimod/bin/hello"

  cat >"$dest/modules/artimod/health.sh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
root="${DECKCTL_ARTIFACT_ROOT:-}"
[[ -n "$root" ]] || root="$(cd "$(dirname "$0")" && pwd)"
[[ -x "$root/bin/hello" ]] || { echo "health: missing hello" >&2; exit 1; }
[[ -L "$root/data" ]] || { echo "health: data symlink missing" >&2; exit 1; }
exit 0
EOF
  chmod +x "$dest/modules/artimod/health.sh"

  git -C "$dest" -c user.email=t@t -c user.name=t add .
  git -C "$dest" -c user.email=t@t -c user.name=t commit -qm "artimod v1"
}

rev_count() { # $1=opt_base
  local base="$1" n=0 entry
  for entry in "$base"/*; do
    [[ -e "$entry" ]] || continue
    [[ "$(basename "$entry")" == "current" ]] && continue
    [[ -L "$entry" ]] && continue
    [[ -d "$entry" ]] && n=$((n + 1))
  done
  printf '%s' "$n"
}

echo "=== prereq: install.sh exists ==="
[[ -f "$ROOT/lib/deckctl/install.sh" ]] && ok "install.sh exists" || bad "missing install.sh"

echo "=== bash -n clean ==="
bash -n "$ROOT/lib/deckctl/install.sh" && ok "bash -n install.sh" || bad "bash -n install.sh"

echo "=== in-place module rejected ==="
REPO="$FIX/repo"
mk_repo "$REPO"
mkdir -p "$REPO/modules/inplace"
cat >"$REPO/modules/inplace/deck.module.json" <<'EOF'
{
  "schema_version": 1,
  "name": "inplace",
  "kind": "config",
  "install": { "mode": "in-place", "needs_git": false }
}
EOF
git -C "$REPO" -c user.email=t@t -c user.name=t add modules/inplace
git -C "$REPO" -c user.email=t@t -c user.name=t commit -qm "add inplace"
set +e
out=$(run_deckctl "$REPO" install inplace 2>&1)
rc=$?
set -e
if [[ $rc -ne 0 ]] && printf '%s\n' "$out" | grep -q 'not install.mode artifact'; then
  ok "in-place module rejected"
else
  bad "in-place module rc=$rc output=$out"
fi

echo "=== install artifact: current bin executable + state symlink ==="
set +e
out=$(run_deckctl "$REPO" install artimod 2>&1)
rc=$?
set -e
opt="$TEST_HOME/.local/opt/overdeck/artimod"
state="$TEST_HOME/.local/state/overdeck/artimod"
if [[ $rc -eq 0 ]]; then ok "install exits 0"; else bad "install rc=$rc: $out"; fi
if [[ -L "$opt/current" && -x "$opt/current/bin/hello" ]]; then
  ok "current/bin/hello executable"
else
  bad "current/bin/hello missing or not executable"
fi
if [[ -d "$state/data" && -L "$opt/current/data" ]]; then
  target=$(readlink "$opt/current/data")
  [[ "$target" == "$state/data" ]] && ok "state_dirs symlink -> state" || bad "data symlink target=$target want $state/data"
else
  bad "state dir or data symlink missing"
fi

echo "=== idempotent re-install same rev ==="
set +e
out=$(run_deckctl "$REPO" install artimod 2>&1)
rc=$?
set -e
if [[ $rc -eq 0 ]] && printf '%s\n' "$out" | grep -q 'already current'; then
  ok "re-install same rev is no-op"
else
  bad "re-install rc=$rc output=$out"
fi

echo "=== failed health leaves current unchanged ==="
before=$(readlink "$opt/current")
git -C "$REPO" -c user.email=t@t -c user.name=t commit -q --allow-empty -m "empty for bad health"
printf '#!/usr/bin/env bash\nexit 1\n' >"$REPO/modules/artimod/health.sh"
chmod +x "$REPO/modules/artimod/health.sh"
git -C "$REPO" -c user.email=t@t -c user.name=t add modules/artimod/health.sh
git -C "$REPO" -c user.email=t@t -c user.name=t commit -qm "bad health"
set +e
out=$(run_deckctl "$REPO" install artimod HEAD 2>&1)
rc=$?
set -e
after=$(readlink "$opt/current")
if [[ $rc -ne 0 ]]; then ok "failed health install exits nonzero"; else bad "failed health install rc=$rc"; fi
if [[ "$before" == "$after" ]]; then ok "current unchanged after failed health"; else bad "current changed: $before -> $after"; fi
if [[ -x "$opt/current/bin/hello" ]]; then ok "previous artifact still runnable"; else bad "previous artifact broken"; fi

echo "=== prune keeps current + one predecessor ==="
git -C "$REPO" checkout -q HEAD~1 -- modules/artimod/health.sh
git -C "$REPO" -c user.email=t@t -c user.name=t add modules/artimod/health.sh
git -C "$REPO" -c user.email=t@t -c user.name=t commit -qm "good health again"
run_deckctl "$REPO" install artimod HEAD >/dev/null
first_rev=$(basename "$(readlink -f "$opt/current")")
git -C "$REPO" -c user.email=t@t -c user.name=t commit -q --allow-empty -m "rev2"
run_deckctl "$REPO" install artimod HEAD >/dev/null
second_rev=$(basename "$(readlink -f "$opt/current")")
git -C "$REPO" -c user.email=t@t -c user.name=t commit -q --allow-empty -m "rev3"
run_deckctl "$REPO" install artimod HEAD --prune >/dev/null
n=$(rev_count "$opt")
if [[ "$n" -eq 2 ]]; then
  ok "prune keeps 2 artifact dirs (got $n)"
else
  bad "prune kept $n dirs (want 2)"
fi
if [[ -d "$opt/$second_rev" || -d "$opt/$first_rev" ]]; then
  ok "prune retained a predecessor dir"
else
  bad "no predecessor dir retained"
fi
if [[ -L "$opt/current" && -x "$opt/current/bin/hello" ]]; then
  ok "current still valid after prune"
else
  bad "current broken after prune"
fi

echo "----"
echo "PASS=$pass FAIL=$fail"
[[ $fail -eq 0 ]]
