#!/bin/sh
# Do the bundled Windows applications and runtimes actually run, the way a user
# reaches them?
#
# What this proves: PowerShell 7 and CPython are installed in the prefix, are
# found through the machine PATH rather than by absolute path, start, report
# their versions and propagate exit codes; Python has a working stdlib (SSL,
# sqlite) and pip, and is registered under PEP 514; both have Start-menu
# shortcuts for sg-start to list.
#
# What it does NOT prove: that any particular script or package works. It is
# here to catch the cheap failures -- a missing payload, a broken link, a PATH
# that was never written, an interpreter that cannot start.
set -eu

SG_LIB="${SG_LIB:-/usr/lib/stained-glass}"
# shellcheck source=lib/sg-common.sh
. "$SG_LIB/sg-common.sh"
sg_wine_env

RC=0
# printf, not echo: dash's echo reads "\7" in "...PowerShell\7" as an escape.
pass() { printf 'PASS  %s\n' "$*"; }
info() { printf 'info  %s\n' "$*"; }
fail() { printf 'FAIL  %s\n' "$*"; RC=1; }

if [ ! -d "$SG_APPS_DIR" ]; then
    info "no bundled applications at $SG_APPS_DIR -- nothing to check"
    echo; echo "RESULT: SKIP"
    exit 77
fi

# Wine's output carries \r and, run under a terminal, escape sequences.
clean() { tr -d '\r' | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g; s/\x1b\][^\a]*\a//g'; }

PROGRAMS="$SG_PREFIX/drive_c/ProgramData/Microsoft/Windows/Start Menu/Programs"

# A shortcut must exist and point at the right program. A .lnk stores its
# target's local path as a plain string, so grep can read it.
check_shortcut() {
    _lnk="$PROGRAMS/$1.lnk"
    if [ ! -f "$_lnk" ]; then
        fail "no '$1' shortcut in the Start menu"
    elif grep -qaF "$2" "$_lnk"; then
        pass "Start menu '$1' -> $2"
    else
        fail "Start menu '$1' does not point at $2"
    fi
}

# --- The user's own profile (debt D15) ---------------------------------------
# Every program a user runs writes to %TEMP%. It must be inside the user's own
# profile and writable by the user -- not the SYSTEM account's, which every
# user inherited from the Default User template before D15.
_me=$(id -un)
out=$(wine cmd /c 'echo %USERPROFILE%^|%TEMP%' 2>/dev/null | clean || true)
case "$out" in
    "C:\\users\\$_me|C:\\users\\$_me\\AppData\\Local\\Temp")
        pass "profile and TEMP are the user's own: $out" ;;
    *)  fail "profile|TEMP: expected C:\\users\\$_me|C:\\users\\$_me\\AppData\\Local\\Temp, got '$out'" ;;
esac
# %TEMP% is expanded first and the literal path used unquoted: cmd resolves an
# environment variable in a redirection target against the current drive, and
# (in Wine) fails a redirect to a *quoted* target -- neither has anything to do
# with whether TEMP is writable. The path has no spaces (a Unix user name).
_tmp=$(wine cmd /c 'echo %TEMP%' 2>/dev/null | clean || true)
wine cmd /c "echo probe> $_tmp\\sg-apps-probe.txt" >/dev/null 2>&1 || true
out=$(wine cmd /c "if exist $_tmp\\sg-apps-probe.txt (del $_tmp\\sg-apps-probe.txt & echo written)" 2>/dev/null | clean || true)
case "$out" in
    *written*) pass "a program can write the user's TEMP" ;;
    *)         fail "a program cannot write the user's TEMP ($_tmp): '$out'" ;;
esac

# --- Python -----------------------------------------------------------------
if [ -f "$SG_APPS_DIR/python/python.exe" ]; then
    PYVER=$(cat "$SG_APPS_DIR/python/SG_VERSION" 2>/dev/null || echo "")
    PYTAG=$(echo "$PYVER" | cut -d. -f1-2)

    # By bare name through cmd, so the machine PATH is what finds it.
    out=$(wine cmd /c python --version 2>/dev/null | clean || true)
    case "$out" in
        "Python $PYVER"*) pass "python on PATH: $out" ;;
        *)                fail "python on PATH: expected 'Python $PYVER', got '$out'" ;;
    esac

    # Status and output captured separately: a pipeline's status is its last
    # command's, and POSIX sh has no pipefail to say otherwise.
    raw=$(wine cmd /c python -c "import ssl, sqlite3, json, sys; print('ok', ssl.OPENSSL_VERSION, 'sqlite', sqlite3.sqlite_version); sys.exit(7)" 2>/dev/null) && rc=0 || rc=$?
    out=$(printf '%s\n' "$raw" | clean)
    case "$out" in
        ok\ OpenSSL*) pass "python stdlib: $out" ;;
        *)            fail "python stdlib: got '$out'" ;;
    esac
    if [ "$rc" = 7 ]; then pass "python exit code propagates (7)"
    else fail "python exit code: expected 7, got $rc"; fi

    out=$(wine cmd /c python -m pip --version 2>/dev/null | clean || true)
    case "$out" in
        pip\ *) pass "pip: $(printf '%s\n' "$out" | cut -d' ' -f1-2)" ;;
        *)      fail "pip: got '$out'" ;;
    esac

    if wine reg query "HKLM\\Software\\Python\\PythonCore\\$PYTAG\\InstallPath" /v ExecutablePath >/dev/null 2>&1; then
        pass "Python $PYTAG is registered (PEP 514)"
    else
        fail "Python $PYTAG is not registered under HKLM\\Software\\Python\\PythonCore"
    fi

    check_shortcut "Python $PYTAG" "C:\\Program Files\\Python$(echo "$PYTAG" | tr -d .)\\python.exe"
else
    info "no Python in the payload"
fi

# --- PowerShell ---------------------------------------------------------------
if [ -f "$SG_APPS_DIR/powershell/pwsh.exe" ]; then
    # Under a pseudo-terminal: with no console and its output redirected,
    # pwsh's ConsoleHost fails on Wine (a NullReferenceException in
    # ConsoleHost.Start). A person always runs it with one -- a console window
    # or a terminal -- so that is what is tested. `script` is in bsdutils,
    # which is Essential.
    #
    # Through cmd, which does the PATH lookup the way a user's prompt does.
    raw=$(script -qec "wine cmd /c pwsh -NoLogo -NoProfile -NonInteractive -Command '\$PSVersionTable.PSVersion.ToString(); \$PSHOME; exit 7'" /dev/null 2>/dev/null) && rc=0 || rc=$?
    out=$(printf '%s\n' "$raw" | clean | sed '/^$/d')
    ver=$(printf '%s\n' "$out" | sed -n '1p')
    case "$ver" in
        7.*) pass "pwsh on PATH: PowerShell $ver, PSHOME $(printf '%s\n' "$out" | sed -n '2p')" ;;
        *)   fail "pwsh on PATH: got '$(printf '%s\n' "$out" | tr '\n' ' ')'" ;;
    esac
    if [ "$rc" = 7 ]; then pass "pwsh exit code propagates (7)"
    else fail "pwsh exit code: expected 7, got $rc"; fi

    # The user's Windows identity has a name (debt D12): .NET's
    # WindowsIdentity turns the token's SID into COMPUTER\user.
    raw=$(script -qec "wine cmd /c pwsh -NoLogo -NoProfile -NonInteractive -Command '[Security.Principal.WindowsIdentity]::GetCurrent().Name'" /dev/null 2>/dev/null || true)
    out=$(printf '%s\n' "$raw" | clean | sed '/^$/d' | tail -n 1)
    _lc=$(printf '%s' "$out" | tr '[:upper:]' '[:lower:]')
    case "$_lc" in
        *\\"$_me") pass "the user's identity in the prefix is named: $out" ;;
        *)          fail "the user's identity in the prefix: expected ...\\$_me, got '$out'" ;;
    esac

    check_shortcut "PowerShell 7" "C:\\Program Files\\PowerShell\\7\\pwsh.exe"
else
    info "no PowerShell in the payload"
fi

# --- Wine Mono (.NET Framework) and Wine Gecko ------------------------------
# Only when the image staged them (sg_addon_staged): then sg-prefix-init lets
# wineboot install them, and they must be there.
if sg_addon_staged mono; then
    # A real round trip: .NET Framework's own C# compiler -- itself a .NET
    # program -- builds a program, and the program runs on the Framework
    # runtime through mscoree. Wine Mono puts csc.exe where Windows has it.
    #
    # The program is built into the user's %TEMP%, where a Windows program
    # would build it. The source sits in a world-readable directory: the
    # wineserver opens files with its own rights (debt D14), so a private
    # directory of the user's is unreadable to it.
    _t=$(mktemp -d); chmod 755 "$_t"
    cat > "$_t/hello.cs" <<'EOF'
using System;
class Hello {
    static int Main() {
        Console.WriteLine(".NET Framework " + Environment.Version);
        return 7;
    }
}
EOF
    chmod 644 "$_t/hello.cs"
    _wtemp=$(wine cmd /c echo %TEMP% 2>/dev/null | clean | sed 's/ *$//')
    _exe="$_wtemp\\sg-apps-check-$$.exe"
    info "building into %TEMP% ($_wtemp)"
    wine 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe' /nologo \
        "/out:$_exe" "$(winepath -w "$_t/hello.cs")" >/dev/null 2>&1 || true
    if [ -n "$(wine cmd /c if exist "$_exe" echo yes 2>/dev/null | clean)" ]; then
        pass ".NET Framework compiler (csc) built a program"
        raw=$(wine "$_exe" 2>/dev/null) && rc=0 || rc=$?
        out=$(printf '%s\n' "$raw" | clean)
        case "$out" in
            ".NET Framework 4."*) pass "it runs on the Framework runtime: $out" ;;
            *)                    fail ".NET Framework program printed '$out'" ;;
        esac
        if [ "$rc" = 7 ]; then pass ".NET Framework exit code propagates (7)"
        else fail ".NET Framework exit code: expected 7, got $rc"; fi
        wine cmd /c del "$_exe" >/dev/null 2>&1 || true
    else
        fail ".NET Framework compiler (csc) did not build a program into %TEMP% -- is Wine Mono installed?"
    fi
    rm -rf "$_t"
else
    info "Wine Mono not staged -- .NET Framework not checked"
fi

if sg_addon_staged gecko; then
    # Does mshtml find the engine? Asked of mshtml itself: a script creates an
    # HTML document, and mshtml's trace says where it looked and what it found.
    # The engine needs a display to initialise, so borrow the session's; with
    # no session, say so rather than guess. This shows the engine is found and
    # accepted, not that it renders: creating the document through the script
    # host fails later, in Wine's JScript ActiveXObject layer, not in Gecko.
    # The session publishes its display a little after login; wait for it, as
    # sg-session-check does, rather than race it and skip.
    _w=0
    while [ ! -f "$(sg_session_env)" ] && [ "$_w" -lt "${SG_CHECK_TIMEOUT:-60}" ]; do
        sleep 2; _w=$((_w + 2))
    done
    # shellcheck source=/dev/null  # written by sg-run-explorer at session start
    [ -f "$(sg_session_env)" ] && . "$(sg_session_env)" 2>/dev/null
    if [ -n "${DISPLAY:-}" ]; then
        export DISPLAY
        _t=$(mktemp -d); chmod 755 "$_t"
        printf '%s\n' 'try { new ActiveXObject("htmlfile"); } catch (e) {}' > "$_t/probe.js"
        chmod 644 "$_t/probe.js"
        _trace=$(WINEDEBUG=+mshtml wine cscript //nologo "$(winepath -w "$_t/probe.js")" 2>&1 | clean)
        rm -rf "$_t"
        _found=$(printf '%s\n' "$_trace" | sed -n 's/.*check_version .*: "\(Wine Gecko [0-9.]*\)".*/\1/p' | head -1)
        if [ -n "$_found" ]; then pass "mshtml finds and accepts the HTML engine: $_found"
        else fail "mshtml did not find Wine Gecko (staged, but not found)"; fi
    else
        info "no session display -- Gecko is staged but its loading was not checked"
    fi
else
    info "Wine Gecko not staged -- HTML engine not checked"
fi

echo
if [ "$RC" -eq 0 ]; then echo "RESULT: PASS"; else echo "RESULT: FAIL"; fi
exit "$RC"
