#!/bin/bash
#
# Builds the TuneVault self-hosted release zip for the R2 download
# (https://releases.tunevault.app/tunevault-selfhosted-vX.Y.Z.zip).
#
# Uses `git archive` so the artifact contains only committed files — no .env,
# no node_modules, no .git, no .claude worktrees. Dev/CI/cloud-only paths are
# stripped via the `export-ignore` rules in .gitattributes. The archive extracts
# to a top-level `tunevault/` directory (matches the `unzip … && cd tunevault`
# instructions on /download).
#
# Usage:  bash scripts/build-selfhosted-release.sh [version] [git-ref]
#         version defaults to 1.0.0, ref defaults to HEAD.
set -e

VERSION="${1:-1.0.0}"
REF="${2:-HEAD}"
OUT_DIR="dist"
NAME="tunevault-selfhosted-v${VERSION}.zip"

cd "$(git rev-parse --show-toplevel)"
mkdir -p "$OUT_DIR"

echo "Building ${NAME} from ${REF}…"
# `-c core.autocrlf=false` so the archive emits committed blobs verbatim (LF).
# Without it, building on a machine with autocrlf=true (e.g. Windows) would rewrite
# every text file — including .sh and oracle-proxy.py — to CRLF and break them.
git -c core.autocrlf=false archive --format=zip --prefix=tunevault/ -o "${OUT_DIR}/${NAME}" "${REF}"

# ── Ensure shell scripts ship with Unix (LF) line endings ─────────────────────
# `git archive` already emits committed blobs as-is (which are LF), so this is a
# belt-and-suspenders pass against a .sh ever being committed with CRLF. It runs
# only when the needed tools are present and is never fatal — the build still
# produces a valid (LF) zip if normalization is skipped.
ZIP_ABS="$(cd "$OUT_DIR" && pwd)/${NAME}"
if command -v unzip >/dev/null 2>&1 && command -v zip >/dev/null 2>&1; then
  TMP="$(mktemp -d)"
  unzip -q "$ZIP_ABS" -d "$TMP"
  find "$TMP" -name '*.sh' -exec sed -i 's/\r$//' {} +
  # Ensure the entry-point scripts ship executable (git archive already emits the
  # exec bit for mode-100755 blobs; this is belt-and-suspenders if that bit is ever
  # dropped from a commit, so `bash setup.sh` / `./update.sh` never fail on chmod).
  for s in setup.sh update.sh install.sh uninstall.sh; do
    [ -f "$TMP/tunevault/$s" ] && chmod +x "$TMP/tunevault/$s"
  done
  rm -f "$ZIP_ABS"
  ( cd "$TMP" && zip -qr "$ZIP_ABS" . )
  rm -rf "$TMP"
  echo "✓ Normalized .sh line endings to LF + set exec bits (zip/unzip)"
elif PY_BIN="$(for c in python3 python py; do         command -v "$c" >/dev/null 2>&1 && "$c" -c 'pass' >/dev/null 2>&1 && { echo "$c"; break; };       done)"; [ -n "$PY_BIN" ]; then
  # Gated on the interpreter actually RUNNING, not just command -v — Windows ships
  # a non-functional python3 stub that command -v falsely matches. It also ships
  # python as `python`/`py` and NOT as `python3`, which is why this normalization
  # silently skipped on every Windows build until 2026-09-02; harmless, because
  # git archive already emits LF, but a belt-and-braces pass that never runs is
  # not a belt.
  "$PY_BIN" - "$ZIP_ABS" <<'PY'
import sys, zipfile, os
src = sys.argv[1]; tmp = src + '.tmp'
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as zout:
    for it in zin.infolist():
        data = zin.read(it.filename)
        if it.filename.endswith('.sh'):
            data = data.replace(b'\r\n', b'\n')
        zout.writestr(it, data)
os.replace(tmp, src)
PY
  echo "✓ Normalized .sh line endings to LF ($PY_BIN)"
else
  echo "• Skipped .sh LF normalization (no zip+unzip, no working python) — git archive already emits LF"
fi

# ── Licence guard — a zip must never ship without the EULA ────────────────────
# There is no explicit file manifest: `git archive` ships every committed file
# except the `export-ignore` paths in .gitattributes. That means LICENSE.txt is
# included by default — and would be dropped SILENTLY if anyone ever added an
# export-ignore rule covering it, or deleted it from the repo. Distributing an
# unlicensed build is the exact gap this guard exists to prevent, so a miss is
# fatal rather than a warning.
if ! git -c core.autocrlf=false archive --format=tar --prefix=tunevault/ "${REF}" \
     | tar -tf - | grep -qx 'tunevault/LICENSE.txt'; then
  echo "✗ FATAL: LICENSE.txt is not in the archive — refusing to build an unlicensed release." >&2
  echo "  Check that LICENSE.txt is committed at the repo root and is not covered by an" >&2
  echo "  export-ignore rule in .gitattributes." >&2
  rm -f "${OUT_DIR}/${NAME}"
  exit 1
fi
echo "✓ LICENSE.txt present in archive"

SIZE=$(du -h "${OUT_DIR}/${NAME}" | cut -f1)
COUNT=$(git -c core.autocrlf=false archive --format=tar --prefix=tunevault/ "${REF}" | tar -tf - | grep -vc '/$' || true)

echo "✓ ${OUT_DIR}/${NAME} (${SIZE}, ${COUNT} files)"
echo ""
echo "Upload to R2 — the --remote flag is NOT optional:"
echo ""
echo "  wrangler r2 object put tunevault-releases/${NAME} \\"
echo "    --file ${OUT_DIR}/${NAME} --content-type application/zip --remote"
echo ""
echo "  WITHOUT --remote, wrangler writes to its LOCAL simulator (.wrangler/state)"
echo "  and STILL prints \"Upload complete.\" Nothing reaches Cloudflare. The only"
echo "  tell is the line above it reading \"Resource location: local\"."
echo ""
echo "Then VERIFY the published bytes are the bytes you built — a 200 is not proof:"
echo ""
echo "  curl -sSL -o dist/pub-check.zip https://releases.tunevault.app/${NAME}"
echo "  sha256sum ${OUT_DIR}/${NAME} dist/pub-check.zip   # hashes must match"
echo ""
echo "  (the old instructions here printed an 'aws s3 cp' with <bucket> and"
echo "   <account> placeholders — not runnable, and no aws CLI is installed)"
echo "Served at: https://releases.tunevault.app/${NAME}"
