#!/usr/bin/env bash
# test-release-tarball-windows.sh — verify a Windows nucleos artefact
# from Linux via Wine.
#
# Mirrors scripts/test-release-tarball.sh (the unix runner) but
# adapted for Windows targets: extracts the `.zip` / `.tar.gz`
# main tarball, runs `nucleos.exe` and `nuctest.exe` under Wine,
# uses `WINEPATH` to make the bundled `mcrl2/bin/ltscompare.exe`
# findable by `nucleos verify`'s own resolver.
#
# Bundled into `nucleos-test-suite-<version>-x86_64-pc-windows-gnu.{zip,tar.gz}`
# alongside the matching `nuctest.exe`. Lets a Linux developer
# verify the Windows release without needing a Windows machine.
#
# Prereqs (Linux host):
#   - `wine` on PATH (the `wine` package, not the wine32-only one).
#     Debian/Ubuntu: `sudo apt install wine64` (or `wine-stable`).
#     A 64-bit wine prefix is required since both nucleos.exe and
#     ltscompare.exe are x86_64.
#   - `unzip` (for .zip input) and/or `tar` (for .tar.gz fallback).
#   - The matching Windows test-suite tarball — this script is
#     bundled inside it, so if you're reading this you've already
#     unpacked it.
#
# Usage:
#   test-release-tarball-windows.sh <main-tarball-path>
#                                   [--category CAT] [-j N]
#                                   [--keep]
#                                   [--no-wine]   # if running on
#                                                 #  native Windows
#
# Examples:
#   # Verify a published rc from Linux via wine:
#   curl -OL .../nucleos-0.2.0-rc.0-x86_64-pc-windows-gnu.zip
#   curl -OL .../nucleos-test-suite-0.2.0-rc.0-x86_64-pc-windows-gnu.zip
#   unzip nucleos-test-suite-0.2.0-rc.0-x86_64-pc-windows-gnu.zip
#   cd nucleos-test-suite-0.2.0-rc.0-x86_64-pc-windows-gnu
#   ./test-release-tarball-windows.sh \
#       ../nucleos-0.2.0-rc.0-x86_64-pc-windows-gnu.zip
#
#   # On native Windows (MSYS2 / Git Bash): same command + --no-wine.

set -euo pipefail

# ── Args ──────────────────────────────────────────────────────────────────
CATEGORY=all
JOBS=""
KEEP=0
NO_WINE=0
TARBALL=""
while [[ $# -gt 0 ]]; do
  case "$1" in
    --category)  CATEGORY=$2; shift 2 ;;
    -j|--jobs)   JOBS=$2;     shift 2 ;;
    --keep)      KEEP=1;      shift ;;
    --no-wine)   NO_WINE=1;   shift ;;
    -h|--help)
      sed -n '2,/^set/p' "$0" | sed -n 's/^# \{0,1\}//p' | head -50
      exit 0 ;;
    -*)
      echo "error: unknown flag $1" >&2; exit 64 ;;
    *)
      if [[ -z "$TARBALL" ]]; then TARBALL=$1
      else echo "error: extra positional arg $1" >&2; exit 64
      fi
      shift ;;
  esac
done

if [[ -z "$TARBALL" ]]; then
  echo "usage: $0 <main-tarball-path> [--category CAT] [-j N] [--keep] [--no-wine]" >&2
  exit 64
fi
if [[ ! -f "$TARBALL" ]]; then
  echo "error: tarball not found: $TARBALL" >&2
  exit 66  # EX_NOINPUT
fi

# ── TTY-aware colour helpers (match nuctest's reporter convention) ────────
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
  C_PASS=$'\033[1;32m'; C_FAIL=$'\033[1;31m'
  C_WARN=$'\033[1;33m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m'
else
  C_PASS=''; C_FAIL=''; C_WARN=''; C_DIM=''; C_RST=''
fi
pass() { printf '%s[ OK ]%s %s\n' "$C_PASS" "$C_RST" "$1"; }
fail() { printf '%s[FAIL]%s %s\n' "$C_FAIL" "$C_RST" "$1" >&2; exit 1; }
warn() { printf '%s[warn]%s %s\n' "$C_WARN" "$C_RST" "$1"; }
note() { printf '%s%s%s\n'        "$C_DIM"  "$1" "$C_RST"; }

# ── Resolve wine prefix ───────────────────────────────────────────────────
# RUN_PREFIX expands to either "" (native Windows) or "wine "
# (Linux + Wine). Set via --no-wine or auto-detected from uname.
if [[ "$NO_WINE" -eq 1 || "$(uname -s)" == "MINGW"* || "$(uname -s)" == "MSYS"* ]]; then
  RUN_PREFIX=()
  note "running natively (no wine prefix)"
else
  if ! command -v wine >/dev/null 2>&1; then
    fail "wine not found on PATH. Install (e.g. apt install wine64) or pass --no-wine."
  fi
  RUN_PREFIX=(wine)
  # Quiet wine debug spam by default — users overriding for
  # diagnostics can `WINEDEBUG=… ./test-release-tarball-windows.sh`.
  export WINEDEBUG="${WINEDEBUG:-fixme-all,err-all}"
  note "wine: $(wine --version 2>/dev/null || echo unknown)"
fi

# ── Resolve test-models/ root ─────────────────────────────────────────────
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
SUITE_ROOT="$SCRIPT_DIR"
[[ ! -d "$SUITE_ROOT/test-models" ]] && SUITE_ROOT="$SCRIPT_DIR/.."
SUITE_ROOT=$(cd "$SUITE_ROOT" && pwd)
if [[ ! -d "$SUITE_ROOT/test-models" ]]; then
  fail "test-models/ not found next to script ($SCRIPT_DIR) or its parent."
fi
note "test-suite root: $SUITE_ROOT"

# ── Extract main tarball to a temp dir ────────────────────────────────────
EXTRACT_DIR=$(mktemp -d -t nucleos-rel-XXXXXX)
trap '[[ $KEEP -eq 0 ]] && rm -rf "$EXTRACT_DIR"; \
      [[ $KEEP -eq 1 ]] && note "keeping extract dir: $EXTRACT_DIR"' EXIT
note "extracting $(basename "$TARBALL") → $EXTRACT_DIR"

case "$TARBALL" in
  *.zip)
    command -v unzip >/dev/null 2>&1 || fail "unzip not on PATH (needed for .zip input)"
    unzip -q "$TARBALL" -d "$EXTRACT_DIR"
    ;;
  *.tar.gz|*.tgz)
    tar -xzf "$TARBALL" -C "$EXTRACT_DIR"
    ;;
  *)
    fail "unrecognised tarball extension (need .zip / .tar.gz): $TARBALL"
    ;;
esac

# Find the single top-level dir the archive unpacked into.
INNER_COUNT=$(find "$EXTRACT_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l)
if [[ "$INNER_COUNT" -ne 1 ]]; then
  fail "tarball layout: expected exactly 1 top-level dir, got $INNER_COUNT"
fi
RELEASE_DIR=$(find "$EXTRACT_DIR" -mindepth 1 -maxdepth 1 -type d)
note "release dir: $(basename "$RELEASE_DIR")"

# ── Layout checks ─────────────────────────────────────────────────────────
require_file() {
  if [[ -f "$RELEASE_DIR/$1" ]]; then pass "layout: $1"
  else                                fail "layout missing: $1"
  fi
}
require_file LICENSE
require_file mcrl2/LICENSE
require_file mcrl2/bin/ltscompare.exe
require_file nucleos.exe

NUCTEST_EXE="$SUITE_ROOT/nuctest.exe"
if [[ ! -f "$NUCTEST_EXE" ]]; then
  fail "nuctest.exe not found in test-suite root: $NUCTEST_EXE"
fi
pass "test-suite: nuctest.exe present"

# ── Architecture sanity ───────────────────────────────────────────────────
arch_out=$(file "$RELEASE_DIR/nucleos.exe" 2>/dev/null || true)
case "$arch_out" in
  *"PE32+ executable"*"x86-64"*) pass "nucleos.exe is PE32+ x86_64" ;;
  *) fail "nucleos.exe arch check: $arch_out" ;;
esac
arch_out=$(file "$RELEASE_DIR/mcrl2/bin/ltscompare.exe" 2>/dev/null || true)
case "$arch_out" in
  *"PE32+ executable"*"x86-64"*) pass "ltscompare.exe is PE32+ x86_64" ;;
  *) fail "ltscompare.exe arch check: $arch_out" ;;
esac

# ── --version sanity (under wine if applicable) ───────────────────────────
ver_out=$("${RUN_PREFIX[@]}" "$RELEASE_DIR/nucleos.exe" --version 2>&1 | head -1) \
  || fail "nucleos.exe --version failed: $ver_out"
case "$ver_out" in
  "nucleos "[0-9]*) pass "nucleos.exe --version: $ver_out" ;;
  *) fail "nucleos.exe --version: unexpected first line: $ver_out" ;;
esac

"${RUN_PREFIX[@]}" "$NUCTEST_EXE" --help >/dev/null 2>&1 \
  || fail "nuctest.exe --help failed under wine"
pass "nuctest.exe --help runs under ${RUN_PREFIX[*]:-native}"

# ── Plug nuctest in: make bundled ltscompare.exe findable ────────────────
# Under wine, augmenting Unix-side PATH wouldn't make the binary
# discoverable by a wine-launched .exe doing a CreateProcess. The
# safe path is WINEPATH — wine's mapping of Unix dirs into the
# Windows PATH visible inside the wine prefix.
#
# Native Windows path uses ; as separator; wine respects that.
if [[ "${#RUN_PREFIX[@]}" -gt 0 ]]; then
  # Wine: prepend mcrl2/bin to WINEPATH (Unix-style; wine maps it).
  export WINEPATH="$RELEASE_DIR/mcrl2/bin${WINEPATH:+;$WINEPATH}"
  note "WINEPATH augmented with $RELEASE_DIR/mcrl2/bin"
else
  # Native Windows shell (MSYS2/Git Bash): PATH-style.
  export PATH="$RELEASE_DIR/mcrl2/bin:$PATH"
fi

# ── Run the full nuctest suite against the extracted nucleos.exe ──────────
JOBS_ARG=()
if [[ -n "$JOBS" ]]; then JOBS_ARG=(-j "$JOBS"); fi

NUCTEST_CMD=(
  "${RUN_PREFIX[@]}"
  "$NUCTEST_EXE"
  --category "$CATEGORY"
  --nucleos  "$RELEASE_DIR/nucleos.exe"
  --root     "$SUITE_ROOT"
  "${JOBS_ARG[@]}"
)
note "running: ${NUCTEST_CMD[*]}"
echo

set +e
"${NUCTEST_CMD[@]}"
NUCTEST_EXIT=$?
set -e

# ── misc category (native bash) ───────────────────────────────────────────
# nuctest.exe SKIPs the bash-based `misc` fixtures on Windows (they can't
# spawn a Windows bash). Re-run them here in the container's NATIVE bash,
# with $NUCLEOS pointed at a `wine nucleos.exe` wrapper. This mirrors
# check_misc's env contract (NUCLEOS / NUCTEST_TMPDIR / MODEL_DIR /
# NUC_PANIC_LOG / NUC_RUN_LOG, cwd = the per-test tmpdir).
#
# Path mapping: nucleos.exe under wine resolves a leading-`/` argv path
# against the current drive (Win32 rule), and `/work` == wine's `Z:\work`.
# So keep NUCTEST_TMPDIR + MODEL_DIR under $SUITE_ROOT and run with cwd on
# that drive — the same unix paths then work for both bash and nucleos.exe.
MISC_PASS=0
MISC_FAIL=0
MISC_FAILED_NAMES=()
if [[ "$CATEGORY" == "all" || "$CATEGORY" == "misc" ]]; then
  MISC_DIR="$SUITE_ROOT/.nuctest-misc"
  mkdir -p "$MISC_DIR/tmp"
  # Clean up the scratch dir on exit too (survives fail()'s `exit 1`).
  trap '[[ $KEEP -eq 0 ]] && rm -rf "$EXTRACT_DIR" "$SUITE_ROOT/.nuctest-misc"; \
        [[ $KEEP -eq 1 ]] && note "keeping extract dir: $EXTRACT_DIR"' EXIT
  NUCLEOS_WRAPPER="$MISC_DIR/nucleos-wine"
  if [[ "${#RUN_PREFIX[@]}" -gt 0 ]]; then
    printf '#!/bin/sh\nexec wine "%s" "$@"\n' "$RELEASE_DIR/nucleos.exe" > "$NUCLEOS_WRAPPER"
  else
    printf '#!/bin/sh\nexec "%s" "$@"\n' "$RELEASE_DIR/nucleos.exe" > "$NUCLEOS_WRAPPER"
  fi
  chmod +x "$NUCLEOS_WRAPPER"
  note "running misc category in native bash ($NUCLEOS_WRAPPER)"
  for run_sh in "$SUITE_ROOT"/test-models/misc_*/run.sh; do
    [[ -f "$run_sh" ]] || continue
    model_dir=$(cd "$(dirname "$run_sh")" && pwd)
    name=$(basename "$model_dir")
    tmp="$MISC_DIR/tmp/$name"
    rm -rf "$tmp"; mkdir -p "$tmp"
    if ( cd "$tmp" && \
         NUCLEOS="$NUCLEOS_WRAPPER" \
         NUCTEST_TMPDIR="$tmp" \
         MODEL_DIR="$model_dir" \
         NUC_PANIC_LOG=/dev/null \
         NUC_RUN_LOG=/dev/null \
         bash "$run_sh" >/dev/null 2>&1 ); then
      MISC_PASS=$((MISC_PASS + 1))
    else
      MISC_FAIL=$((MISC_FAIL + 1)); MISC_FAILED_NAMES+=("$name")
      warn "misc: $name FAILED"
    fi
    rm -rf "$tmp"
  done
  note "misc (native bash): $MISC_PASS passed, $MISC_FAIL failed"
fi

echo
if [[ $NUCTEST_EXIT -eq 0 && $MISC_FAIL -eq 0 ]]; then
  pass "nuctest --category $CATEGORY: 0 FAIL (under ${RUN_PREFIX[*]:-native})"
  [[ "$CATEGORY" == "all" || "$CATEGORY" == "misc" ]] && \
    pass "misc category: $MISC_PASS passed, 0 failed (native bash)"
  pass "release tarball $(basename "$TARBALL") VERIFIED"
else
  [[ $NUCTEST_EXIT -ne 0 ]] && warn "nuctest exited $NUCTEST_EXIT"
  [[ $MISC_FAIL -ne 0 ]]    && warn "misc (native bash) failures: ${MISC_FAILED_NAMES[*]}"
  fail "release tarball did NOT pass (nuctest exit=$NUCTEST_EXIT, misc fails=$MISC_FAIL)"
fi
