#!/bin/bash
# test/entrypoint-test.sh
#
# Runs inside the docker `test` stage. The repo is mounted read-write at
# /dotfiles (see the Makefile's `docker run -v "$PWD":/dotfiles`).
#
# Two tiers:
#   1. DRY PROBES  (always, fast, offline)
#       - `make -n` dry-runs to catch Makefile typos / missing scripts
#       - `stow --simulate` to prove every stow target resolves
#   2. REAL SMOKE  (guarded by RUN_SMOKE=1)
#       - exercises term/setup.sh package parsing and (optionally) installs
#       - verifies stow actually creates symlinks into $HOME
#   Full AUR/npm + kreation/buntes-monster only run with RUN_FULL=1.
#
# Env flags (set via `docker run -e`):
#   RUN_SMOKE=1   run the real pacman-install smoke
#   RUN_FULL=1    also attempt AUR/npm + hypr/kreation/buntes-monster
#   SKIP_NET=1    pretend no network; skip anything that needs the mirror

set -uo pipefail

# Resolve the repo root: prefer the container's /dotfiles mount, otherwise
# fall back to the directory above this script (a local checkout).
if [[ -d /dotfiles ]]; then
    cd /dotfiles || { echo "FATAL: repo not mounted at /dotfiles"; exit 1; }
else
    cd "$(dirname "${BASH_SOURCE[0]}")/.." || { echo "FATAL: cannot locate repo root"; exit 1; }
fi
REPO="$(pwd)"

# Guard: both resolution paths must find a real checkout.
if [[ ! -f "$REPO/Makefile" || ! -d "$REPO/test" ]]; then
    echo "FATAL: '$REPO' does not look like the dotfiles repo root." >&2
    echo "       In docker, run with -v \"\$PWD\":/dotfiles ." >&2
    exit 1
fi

PASS=0
FAIL=0
say_ok()  { printf '  ok:   %s\n' "$*"; PASS=$((PASS+1)); }
say_fail(){ printf '  FAIL: %s\n' "$*"; FAIL=$((FAIL+1)); }
header()  { printf '\n==== %s ====\n' "$*"; }

# Scratch file for captured stderr; kept OUTSIDE the repo mount so it never
# leaks into the host workspace (the mount is /dotfiles).
ERR_LOG="$(mktemp -t entrypoint.XXXXXX)"
cleanup() { rm -f "$ERR_LOG"; }
trap cleanup EXIT

if command -v git >/dev/null 2>&1 && [[ ! -d .git ]]; then
    # when mounted from a worktree, .git may be a file; fine.
    :
fi

echo "entrypoint-test.sh: running as $(id -un) in $(pwd)"

# ---------------------------------------------------------------------------
# 1. DRY PROBES - always run, cheap, offline.
# ---------------------------------------------------------------------------
header "Dry probes"

# 1a. Makefile dry runs for every target.
# Discover targets dynamically so we never drift from the Makefile.
targets=$(grep -oE '^[A-Za-z0-9_-]+:' Makefile | tr -d ':' | grep -v '^test' | sort -u)
for target in $targets; do
    # `make -n` must not error out from missing scripts / dirs.
    if ( cd "$REPO" && make -n "$target" >/dev/null 2>"$ERR_LOG" ); then
        say_ok "make -n $target parses"
    else
        say_fail "make -n $target (typo? missing script?)"
        sed 's/^/      /' "$ERR_LOG" 2>/dev/null
    fi
done

# 1b. stow --simulate for every package the Makefile stows.
while IFS= read -r pkg; do
    [[ -z "$pkg" ]] && continue
    if stow --simulate --dir="$REPO" --target="$REPO" "$pkg" 2>"$ERR_LOG"; then
        say_ok "stow --simulate $pkg resolves"
    else
        say_fail "stow --simulate $pkg (non-existent package or conflict)"
        sed 's/^/      /' "$ERR_LOG" 2>/dev/null
    fi
done < <(grep -P '^\t' Makefile | grep -oE 'stow [A-Za-z0-9_-]+' | awk '{print $2}' | sort -u)

# 1c. run the repo's own lint + probe scripts (they're mounted here).
if [[ -x test/lint.sh ]] || [[ -f test/lint.sh ]]; then
    if bash test/lint.sh >/dev/null 2>&1; then
        say_ok "test/lint.sh"
    else
        say_fail "test/lint.sh (see logs above / rerun outside container)"
    fi
else
    say_fail "test/lint.sh not present in mount"
fi

header "Smoke gates"
echo "  RUN_SMOKE=${RUN_SMOKE:-0}  RUN_FULL=${RUN_FULL:-0}  SKIP_NET=${SKIP_NET:-0}"

# ---------------------------------------------------------------------------
# 2. REAL SMOKE (RUN_SMOKE=1)
# ---------------------------------------------------------------------------
if [[ "${RUN_SMOKE:-0}" == "1" ]]; then
    header "Real smoke (RUN_SMOKE=1)"

    # 2a. Allow scripts to use sudo without a password (already NOPASSWD via
    # the Dockerfile for the wheel group).
    #
    # 2b. Override host-daemon commands with no-ops so nothing tries to talk
    # to systemd/dbus/dbus in the container.
    mkdir -p "$HOME/bin"
    cat > "$HOME/bin/systemctl" <<'NOOP'
#!/bin/bash
echo "[stub] systemctl $*"
exit 0
NOOP
    cat > "$HOME/bin/dbus-update-activation-environment" <<'NOOP'
#!/bin/bash
echo "[stub] dbus-update-activation-environment $*"
exit 0
NOOP
    cat > "$HOME/bin/kbuildsycoca6" <<'NOOP'
#!/bin/bash
echo "[stub] kbuildsycoca6 $*"
exit 0
NOOP
    cat > "$HOME/bin/updatedb" <<'NOOP'
#!/bin/bash
echo "[stub] updatedb $*"
exit 0
NOOP
    # chsh has no functional /etc/passwd in the container; make it a no-op so
    # set_fish_default_shell doesn't fail (documented in the header comment).
    cat > "$HOME/bin/chsh" <<'NOOP'
#!/bin/bash
echo "[stub] chsh $*"
exit 0
NOOP
    # The AUR section is stripped for the pacman-only smoke, so term/setup.sh
    # only ever passes repo packages here; route yay through pacman directly.
    # --noconfirm is needed because the container isn't interactive.
    cat > "$HOME/bin/yay" <<'NOOP'
#!/bin/bash
echo "[stub] yay $* (routed through pacman)"
exec sudo pacman --noconfirm "$@"
NOOP
    chmod +x "$HOME/bin/"*
    export PATH="$HOME/bin:$PATH"

    # 2c. setup.sh package parsing sanity (mirrors read_packages) for each
    # package dir: basic/term/utils.
    for d in basic term utils; do
        if ( cd "$REPO" && bash -n "$d/setup.sh" ); then
            say_ok "$d/setup.sh syntax OK"
        else
            say_fail "$d/setup.sh syntax error"
        fi
        pacman_count=$(awk '
            /^@pacman/{s="pacman"}
            /^@aur/{s="aur"}
            /^@npm/{s="npm"}
            /^[[:space:]]*#/{next}
            /^[[:space:]]*$/{next}
            s=="pacman"{pc++} s=="aur"{ac++} s=="npm"{nc++}
            END{printf "%d %d %d", pc,ac,nc}
        ' "$REPO/$d/packages.txt")
        read -r PC AC NC <<< "$pacman_count"
        say_ok "$d/packages.txt -> ${PC} pacman, ${AC} aur, ${NC} npm entries"
    done

    # 2d. If network allowed and not skipped, actually install the pacman
    # section of term/setup.sh (mirrors `make term` without AUR/npm).
    if [[ "${SKIP_NET:-0}" != "1" ]]; then
        header "make term (pacman packages, AUR/npm skipped)"
        # Patch packages.txt to drop @aur/@npm so we don't hit AUR/npm.
        cp "$REPO/term/packages.txt" "$HOME/packages.txt.full"
        awk '
            /^@aur$/{s="skip"}; /^@npm$/{s="skip"}; /^@pacman$/{s="pacman"}
            { if (s!="skip" && NF>0 && $1 !~ /^#/) print }
        ' "$REPO/term/packages.txt" > "$HOME/packages.txt.term-only"
        # set a scratch HOME for setup.sh so it doesn't touch the real one.
        mkdir -p "$HOME/term"
        cp "$REPO/term/setup.sh" "$HOME/term/setup.sh"
        cp "$HOME/packages.txt.term-only" "$HOME/term/packages.txt"
        # chsh is stubbed via $HOME/bin so even `chsh` works without a pty.
        setup_ok=0
        if ( cd "$HOME/term" && bash setup.sh ) ; then
            say_ok "term/setup.sh (term-only) completed"
            setup_ok=1
        else
            say_fail "term/setup.sh (term-only) failed (exit $?)"
        fi

        # 2d2. Verify that every package is really installed and its binary
        # runs. Only meaningful if the install itself succeeded.
        if [[ "$setup_ok" == "1" ]]; then
            if [[ -x test/verify_packages.sh ]]; then
                header "verify installed packages"
                if bash test/verify_packages.sh "$HOME/packages.txt.term-only"; then
                    say_ok "verify_packages.sh: all packages verified"
                else
                    say_fail "verify_packages.sh: one or more packages failed verification (see above)"
                fi
            else
                say_fail "test/verify_packages.sh missing"
            fi
        else
            echo "  skipping package verification (install did not complete)"
        fi
    else
        echo "  SKIP_NET=1 -> skipping real package install and verification"
    fi

    # 2e. stow --actual into $HOME to prove real symlinks are created.
    header "stow real symlinks into \$HOME"
    # term/setup.sh may have just re-created the yazi plugin (`ya pkg add`)
    # as real files. The repo vendors the same plugin under term/.config/yazi,
    # so drop the duplicate first, letting stow take ownership of that config.
    rm -rf "$HOME/.config/yazi"
    for pkg in term hypr kreation local_ai; do
        if [[ -d "$REPO/$pkg" ]]; then
            if stow --dir="$REPO" --target="$HOME" "$pkg" 2>"$ERR_LOG"; then
                say_ok "stow $pkg into \$HOME"
            else
                say_fail "stow $pkg into \$HOME"
                sed 's/^/      /' "$ERR_LOG" 2>/dev/null
            fi
        else
            say_fail "package dir missing: $pkg"
        fi
    done
    # sanity: representative symlinks must now exist in $HOME.
    missing=""
    for link in .config/fish .config/hypr .vst3/MT-PowerDrumKit.vst3; do
        [[ -e "$HOME/$link" ]] || missing="$missing $link"
    done
    if [[ -z "$missing" ]]; then
        say_ok "stow created representative \$HOME symlinks"
    else
        say_fail "missing expected stow symlinks:$missing"
    fi

    # 2f. kreation package: verify the VST path is stow-able and present.
    header "kreation VST"
    if [[ -f "$REPO/kreation/.vst3/MT-PowerDrumKit.vst3/Contents/x86_64-linux/MT-PowerDrumKit.so" ]]; then
        say_ok "MT-PowerDrumKit.vst3 binary present"
    else
        say_fail "MT-PowerDrumKit.vst3 binary missing"
    fi
else
    echo "  RUN_SMOKE not set -> skipping real installs & symlink creation."
fi

# ---------------------------------------------------------------------------
# 3. FULL (RUN_FULL=1) - opt-in heavy path (AUR/npm/hypr/kreation)
# ---------------------------------------------------------------------------
if [[ "${RUN_FULL:-0}" == "1" ]] && [[ "${SKIP_NET:-0}" != "1" ]]; then
    header "Full smoke (RUN_FULL=1)"
    if ( cd "$REPO" && make buntes-monster ); then
        say_ok "make buntes-monster"
    else
        say_fail "make buntes-monster (exit $?)"
    fi
else
    echo "  RUN_FULL not set (or SKIP_NET) -> skipping full/hypr/kreation install."
fi

# ---------------------------------------------------------------------------
# Summary & exit code
# ---------------------------------------------------------------------------
echo
echo "=============================================="
echo "entrypoint-test.sh: $PASS passed, $FAIL failed"
echo "=============================================="
# Exit non-zero if anything failed.
[[ $FAIL -eq 0 ]]
