#!/bin/bash
# Build PythonStandalone.zip for bundling in Mugi.app.
# Downloads python-build-standalone, extracts, normalizes layout, creates zip.
# Output: MugiApp/Mugi/Resources/PythonStandalone.zip
#
# Usage: ./scripts/build_python_standalone_zip.sh
#
# Signing (read this if notarization fails on PythonStandalone.zip):
# - Ad-hoc (-s -) is fine for *local* debug builds only.
# - Notarization unpacks the zip and inspects *every* Mach-O (bin/python3,
#   libpython3.12.dylib, libtcl, .so, etc.). Each must be signed with *your*
#   "Developer ID Application" cert + hardened runtime, or upload fails with
#   "not signed with a valid Developer ID certificate" on those inner paths.
# - Set MUGI_CODESIGN_IDENTITY before running, OR leave it unset: if your
#   Keychain has exactly *one* "Developer ID Application" identity, it will
#   be used automatically. Multiple matches → error (pick one explicitly).
# - MUGI_ADHOC_ONLY=1 forces ad-hoc signing (not notarization-safe; local dev
#   / CI with no Apple dev cert only).
#     export MUGI_CODESIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)"
#     ./scripts/build_python_standalone_zip.sh
set -e

TAG="20260211"
PYVER="3.12.12"
ARCH="aarch64"
# install_only (not stripped): includes venv; stripped build omits it and breaks bootstrap
VARIANT="install_only"
URL="https://github.com/astral-sh/python-build-standalone/releases/download/${TAG}/cpython-${PYVER}+${TAG}-${ARCH}-apple-darwin-${VARIANT}.tar.gz"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OUT_ZIP="$REPO_ROOT/MugiApp/Mugi/Resources/PythonStandalone.zip"
WORK_DIR=$(mktemp -d -t mugi_python_build_XXXXXX)
trap 'rm -rf "$WORK_DIR"' EXIT

echo "Downloading $URL ..."
curl -sSLf -o "$WORK_DIR/py.tar.gz" "$URL"

echo "Extracting..."
cd "$WORK_DIR"
tar -xzf py.tar.gz

echo "Normalizing layout to python-3.12/..."
if [ -d python/install ] && [ -f python/install/bin/python3 ]; then
  mv python/install python-3.12
  rm -rf python
elif [ -f python/bin/python3 ]; then
  mv python python-3.12
else
  echo "Unexpected layout. Contents:"
  ls -la
  exit 1
fi

echo "Verifying python-3.12/bin/python3..."
test -x python-3.12/bin/python3 || { echo "python3 not executable"; exit 1; }
./python-3.12/bin/python3 --version

# Re-sign: either Developer ID (notarization) or ad-hoc (local dev only).
# Entitlements: only the real interpreter binary (for PyPI wheels / dlopen).
ENT="$REPO_ROOT/backend/mugi_python_runtime_entitlements.plist"

is_mach_o() {
  file -b "$1" 2>/dev/null | grep -q "Mach-O"
}

# Resolve to absolute for stable comparison (realpath is macOS 13+ / CLT).
resolve_path() {
  if [[ -x /usr/bin/realpath ]]; then
    /usr/bin/realpath "$1"
  else
    ./python-3.12/bin/python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$1"
  fi
}

# Sign all Mach-O under python-3.12 with Developer ID + hardened runtime.
# Interpreter gets entitlements; everything else is plain runtime signature.
sign_tree_developer_id() {
  local identity="$1"
  local ent="$2"
  local py_real fr
  py_real="$(./python-3.12/bin/python3 -c 'import os,sys; print(os.path.realpath(sys.executable))')"
  echo "Developer ID: signing all Mach-O under python-3.12 (interpreter: $py_real)"

  while IFS= read -r -d '' f; do
    is_mach_o "$f" || continue
    fr="$(resolve_path "$f")"
    if [[ "$fr" == "$py_real" ]]; then
      continue
    fi
    /usr/bin/codesign -f -s "$identity" --options runtime --timestamp "$f"
  done < <(find python-3.12 -type f -print0)

  if [[ -f "$ent" ]]; then
    /usr/bin/codesign -f -s "$identity" --options runtime --timestamp --entitlements "$ent" "$py_real"
  else
    /usr/bin/codesign -f -s "$identity" --options runtime --timestamp "$py_real"
  fi
  echo "Developer ID: codesign verify (interpreter)…"
  /usr/bin/codesign --verify --verbose=2 "$py_real"

  echo "Developer ID: verifying all Mach-O in tree…"
  while IFS= read -r -d '' f; do
    is_mach_o "$f" || continue
    if ! /usr/bin/codesign --verify --verbose=2 "$f" 2>/dev/null; then
      echo "ERROR: not signed or verify failed: $f" >&2
      /usr/bin/codesign -dv "$f" 2>&1 || true
      exit 1
    fi
  done < <(find python-3.12 -type f -print0)
}

# List "Developer ID Application" identities (full string, one per line).
list_developer_id_application_identities() {
  security find-identity -v -p codesigning 2>/dev/null | \
    sed -n 's/.*"\(Developer ID Application:[^"]*\)".*/\1/p' | sort -u
}

# Resolve MUGI_CODESIGN_IDENTITY: explicit env, or exactly one in Keychain.
resolve_signing_identity() {
  if [[ -n "${MUGI_ADHOC_ONLY:-}" ]]; then
    return 1
  fi
  if [[ -n "${MUGI_CODESIGN_IDENTITY:-}" ]]; then
    echo "Using MUGI_CODESIGN_IDENTITY from environment."
    return 0
  fi
  local list count
  list=$(list_developer_id_application_identities || true)
  count=$(echo "$list" | sed '/^$/d' | wc -l | tr -d ' ')
  if [[ "$count" -eq 0 ]]; then
    return 1
  fi
  if [[ "$count" -gt 1 ]]; then
    echo "ERROR: Multiple 'Developer ID Application' identities in Keychain; not picking one:" >&2
    echo "$list" | sed 's/^/  /' >&2
    echo "Set MUGI_CODESIGN_IDENTITY to the one you use for Mugi, or use MUGI_ADHOC_ONLY=1 (not for notarization)." >&2
    exit 1
  fi
  MUGI_CODESIGN_IDENTITY=$(echo "$list" | sed '/^$/d' | head -1)
  export MUGI_CODESIGN_IDENTITY
  echo "Auto-selected: $MUGI_CODESIGN_IDENTITY"
  return 0
}

ad_hoc_sign_interpreter_only() {
  if [[ -f "$ENT" ]]; then
    local REAL
    REAL="$(./python-3.12/bin/python3 -c 'import os,sys; print(os.path.realpath(sys.executable))')"
    {
      echo ""
      echo "**********************************************************************" >&2
      echo "  NOT NOTARIZATION-SAFE: ad-hoc signed ONLY the main interpreter at" >&2
      echo "  $REAL" >&2
      echo "  python-3.12/lib/*.dylib and other binaries are UNSIGNED." >&2
      echo "  App Store / notary will reject with errors inside PythonStandalone.zip" >&2
      echo "  Remove MUGI_ADHOC_ONLY and ensure a Developer ID is available, or set" >&2
      echo "  MUGI_CODESIGN_IDENTITY. See: TROUBLESHOOTING.md" >&2
      echo "**********************************************************************" >&2
      echo ""
    }
    echo "codesign (ad-hoc + entitlements) for local dev -> $REAL"
    /usr/bin/codesign -s - --force --timestamp=none --entitlements "$ENT" "$REAL" || {
      echo "WARNING: codesign failed (non-fatal for zip build on some CI hosts)"
    }
  else
    echo "Skipping codesign (no entitlements plist)"
  fi
}

if [[ -x /usr/bin/codesign ]]; then
  if resolve_signing_identity; then
    if [[ -f "$ENT" ]]; then
      sign_tree_developer_id "$MUGI_CODESIGN_IDENTITY" "$ENT"
    else
      echo "ERROR: entitlements missing: $ENT"
      exit 1
    fi
  elif [[ -f "$ENT" ]]; then
    ad_hoc_sign_interpreter_only
  else
    echo "Skipping codesign (no entitlements plist)"
  fi
else
  echo "Skipping codesign (no /usr/bin/codesign)"
fi

echo "Creating PythonStandalone.zip..."
mkdir -p "$(dirname "$OUT_ZIP")"
rm -f "$OUT_ZIP"
zip -r -q "$OUT_ZIP" python-3.12

echo "Verifying zip layout..."
unzip -l "$OUT_ZIP" | head -15
if unzip -l "$OUT_ZIP" | grep -q "python-3.12/bin/python3"; then
  echo "OK: zip contains python-3.12/bin/python3"
else
  echo "ERROR: zip missing python-3.12/bin/python3"
  exit 1
fi

echo ""
echo "Done: $OUT_ZIP"
ls -la "$OUT_ZIP"
