#!/bin/bash
#
# tools/native-compile — compile the on-device Python to native .so at BUILD
# time (Cython), per ADR-0021 (white-box code protection).
#
# Replaces the old "ship .py + compile-on-device" model: given a /opt/vendora_sbc
# tree, this cythonizes every services/**/*.py → .so, adds a thin .py launcher
# per service entry point, strips the .py source + intermediate .c, and patches
# the systemd unit ExecStart lines to point at the launchers.
#
# The compiling interpreter MUST match the TARGET platform's ABI (architecture
# AND CPython version), because a .so is per-(arch × py-version):
#   x86 server (Ubuntu 24.04) → the build VM's amd64 python3.12
#   Orange Pi One (Bookworm)  → a qemu-armhf python3.11
# Callers (build-deb, customize-image) pass the right interpreter.
#
# Cython directives (proven in the ADR-0021 Phase 0 spike):
#   binding=True            → compiled functions expose signatures, so FastAPI
#                             can introspect route handlers.
#   annotation_typing=False → type hints (`x: str`) are NOT enforced as C types,
#                             so FastAPI's `Form(...)`/`Depends(...)` defaults work.
#
# Usage:  native-compile <tree_root> [PYTHON]
#   tree_root : dir containing services/ (+ packaging/systemd/ to patch units)
#   PYTHON    : a Cython-capable interpreter for the target (default: python3)
#
set -euo pipefail

ROOT="${1:?usage: native-compile <tree_root> [python]}"
PY="${2:-python3}"
SERVICES="$ROOT/services"
UNITS="$ROOT/packaging/systemd"

log() { echo "[native-compile] $*"; }
die() { echo "[native-compile] ERROR: $*" >&2; exit 1; }

[ -d "$SERVICES" ] || die "no services/ under $ROOT"
"$PY" -c 'import Cython' 2>/dev/null || die "Cython not available for $PY (apt: python3-dev build-essential; pip: Cython setuptools)"

py_count=$(find "$SERVICES" -name '*.py' | wc -l)
log "cythonizing $py_count modules with $("$PY" -c 'import Cython,sys;print("Cython",Cython.__version__,"/",sys.version.split()[0])')"

# Compile every .py in place → .so. binding/annotation_typing per ADR-0021.
find "$SERVICES" -name '*.py' -print0 \
  | xargs -0 "$PY" -m Cython.Build.Cythonize -i -3 \
        -X binding=True -X annotation_typing=False >/tmp/native-compile.log 2>&1 \
  || { tail -20 /tmp/native-compile.log >&2; die "cythonize failed (see /tmp/native-compile.log)"; }

so_count=$(find "$SERVICES" -name '*.so' | wc -l)
[ "$so_count" -gt 0 ] || die "no .so produced"

# Thin launcher per service entry point (vendora_<svc>.so → vendora_<svc>_launch.py).
# A .so isn't directly runnable; the launcher imports it + calls main() (every
# service already defines def main()). The launcher carries no IP.
launchers=0
for d in "$SERVICES"/*/; do
    main_so=$(find "$d" -maxdepth 1 -name 'vendora_*.so' 2>/dev/null | head -1)
    [ -n "$main_so" ] || continue
    mod=$(basename "$main_so"); mod=${mod%%.*}   # vendora_admin.cpython-…-.so → vendora_admin
    printf 'import sys, os\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\nfrom %s import main\nsys.exit(main())\n' \
        "$mod" > "$d/${mod}_launch.py"
    launchers=$((launchers + 1))
done

# Strip source (keep the launchers) + cythonize -i intermediates (.c, per-dir
# build/ dirs which hold a DUPLICATE copy of each .so) + caches.
find "$SERVICES" -name '*.py' ! -name '*_launch.py' -delete
find "$SERVICES" -name '*.c' -delete
find "$SERVICES" -type d -name build -exec rm -rf {} + 2>/dev/null || true
find "$ROOT" -name __pycache__ -type d -exec rm -rf {} + 2>/dev/null || true

# Patch unit ExecStart: …/vendora_<svc>.py → …/vendora_<svc>_launch.py
if [ -d "$UNITS" ]; then
    for unit in "$UNITS"/vendora-*.service; do
        [ -f "$unit" ] || continue
        sed -i 's/\r$//; s|/vendora_\([a-z0-9]*\)\.py|/vendora_\1_launch.py|g' "$unit"
    done
fi

so_count=$(find "$SERVICES" -name '*.so' | wc -l)   # recount after the build/ cleanup
log "$so_count modules → .so; $launchers launchers; source stripped; units patched"
