#!/bin/bash
#
# tools/build-deb — build a vendora-sbc.deb from the current source tree.
#
# Runs on a build host with dpkg-deb, python3, rsync, awk, sed.
# Produces:    vendora-sbc_<version>_all.deb (+ matching .sha256 sidecar)
# Default out: $HOME/armbian-build/output/deb/
# Override:    DEB_OUTPUT_DIR=<path> tools/build-deb
#
# The .deb is "application only" — services/ + tools/ + packaging/systemd/.
# System-level configs (nftables, dnsmasq, NetworkManager) and pip-managed
# Python libs (FastAPI / uvicorn / gpiod) are NOT included; those stay
# image-managed via image/armbian-build-config/customize-image.sh. The .deb
# is intended for IN-PLACE UPGRADES of an already-imaged Vendora SBC, not
# for greenfield apt installs on a bare Debian box.
#
# Per Phase 8.1 (S17): unsigned. Phase 8.2 layers GPG signing of the apt
# repo metadata + the .deb itself.
#
# Usage:
#   cd ~/vendora_sbc        # or anywhere; paths resolve relative to script
#   ./tools/build-deb
#
# Output ends with the SHA-256 + install command for the operator to copy
# to their notes or paste into a deploy script.

set -euo pipefail

# ---------------------------------------------------------------------------
# Paths + config
# ---------------------------------------------------------------------------

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd)"

DEB_OUTPUT_DIR="${DEB_OUTPUT_DIR:-$HOME/armbian-build/output/deb}"

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

# ---------------------------------------------------------------------------
# Args — native compilation (ADR-0021)
# ---------------------------------------------------------------------------
# Default (no args): a NATIVE per-arch .deb (ADR-0021) — cythonize services/ to
# .so, ship .so + thin launchers (no .py), Architecture = the build host's arch.
#   --no-native           build the legacy bytecode .deb instead: Architecture:
#                         all, ships .py, compiles on-device (Tier-2 fallback).
#   --native              explicit native (the default; kept for clarity).
#   --arch <amd64|armhf>  override the target Architecture (default: the build
#                         host's). A .so is per-(arch x py-version), so --python
#                         MUST match the TARGET ABI when cross-targeting.
#   --python <path>       Cython-capable interpreter for the target (default
#                         python3): amd64 → the build host's py3.12; armhf →
#                         a qemu-armhf py3.11.
NATIVE=1   # ADR-0021 Phase 1: native (.so) is the DEFAULT (--no-native opts out)
NATIVE_ARCH=""
CYTHON_PY="python3"
while [ $# -gt 0 ]; do
    case "$1" in
        --native)    NATIVE=1; shift ;;
        --no-native) NATIVE=0; shift ;;   # opt out -> legacy bytecode Architecture: all .deb
        --arch)   NATIVE_ARCH="${2:-}"; shift 2 ;;
        --python) CYTHON_PY="${2:-}"; shift 2 ;;
        *) die "unknown arg: $1 (usage: build-deb [--native --arch <amd64|armhf> [--python <path>]])" ;;
    esac
done
if [ "$NATIVE" = 1 ]; then
    # A .so is per-arch; default the target arch to the BUILD HOST's arch
    # (amd64 on the build VM, armhf inside the qemu-armhf container/chroot).
    # Pass --arch only when cross-targeting.
    [ -n "$NATIVE_ARCH" ] || NATIVE_ARCH="$(dpkg --print-architecture 2>/dev/null || echo amd64)"
    [ -f "$PROJECT_ROOT/tools/native-compile" ] || die "tools/native-compile missing"
    "$CYTHON_PY" -c 'import Cython' 2>/dev/null \
        || die "--native: '$CYTHON_PY' lacks Cython — need a Cython-capable interpreter matching the target arch+pyver"
fi

# ---------------------------------------------------------------------------
# Sanity checks
# ---------------------------------------------------------------------------

command -v dpkg-deb  >/dev/null || die "dpkg-deb not found (apt install -y dpkg)"
command -v rsync     >/dev/null || die "rsync not found (apt install -y rsync)"
command -v python3   >/dev/null || die "python3 not found"

[ -f "$PROJECT_ROOT/services/common/version.py" ] \
    || die "version.py missing — \$PROJECT_ROOT=$PROJECT_ROOT looks wrong"
[ -f "$PROJECT_ROOT/packaging/debian/control.template" ] \
    || die "packaging/debian/control.template missing"
[ -d "$PROJECT_ROOT/packaging/systemd" ] \
    || die "packaging/systemd/ missing"

# ---------------------------------------------------------------------------
# Read version from the canonical source
# ---------------------------------------------------------------------------

VERSION=$(python3 - <<EOF
import re, sys
with open("$PROJECT_ROOT/services/common/version.py") as f:
    for line in f:
        m = re.match(r'\s*SBC_VERSION\s*=\s*"(.+)"', line)
        if m:
            print(m.group(1))
            sys.exit(0)
sys.exit(1)
EOF
)
[ -n "$VERSION" ] || die "could not extract SBC_VERSION from version.py"
log "package version: $VERSION (from services/common/version.py)"

# Debian's version field has strict syntax. Most permissively: upstream
# version chars are [A-Za-z0-9.+~:-]. We don't enforce here; dpkg-deb will
# reject anything invalid downstream.

# ---------------------------------------------------------------------------
# Stage the package contents
# ---------------------------------------------------------------------------

STAGING=$(mktemp -d -t vendora-deb-XXXXXX)
trap 'rm -rf "$STAGING"' EXIT

log "staging at $STAGING"

# DEBIAN/ metadata
mkdir -p "$STAGING/DEBIAN"
sed "s|@@VERSION@@|$VERSION|g" \
    "$PROJECT_ROOT/packaging/debian/control.template" \
    > "$STAGING/DEBIAN/control"
# Native .debs carry compiled .so (arch-specific); flip Architecture: all → the
# target arch so apt/dpkg install only the matching build (ADR-0021).
if [ "$NATIVE" = 1 ]; then
    sed -i "s|^Architecture:.*|Architecture: $NATIVE_ARCH|" "$STAGING/DEBIAN/control"
fi

# Maintainer scripts must be 0755 AND LF-terminated. A CRLF shebang makes
# dpkg's configure step fail to exec the script ("No such file or directory"
# on the interpreter "/bin/bash\r"). `sed 's/\r$//'` normalizes any CRLF the
# source may carry (Windows checkout / git-archive export) — belt-and-braces
# alongside the repo's .gitattributes eol=lf rule. chmod 0755 so the source
# tree's mode doesn't leak (Windows checkouts can come through with 0644).
for script in postinst prerm postrm; do
    sed 's/\r$//' "$PROJECT_ROOT/packaging/debian/$script" > "$STAGING/DEBIAN/$script"
    chmod 0755 "$STAGING/DEBIAN/$script"
done

# /opt/vendora_sbc/ payload
log "  rsync services/ → /opt/vendora_sbc/services/"
mkdir -p "$STAGING/opt/vendora_sbc"
rsync -a \
    --exclude='__pycache__' \
    --exclude='*.pyc' \
    --exclude='.pytest_cache' \
    --exclude='.mypy_cache' \
    --exclude='.venv' \
    --exclude='venv' \
    "$PROJECT_ROOT/services" "$STAGING/opt/vendora_sbc/"

log "  rsync tools/ → /opt/vendora_sbc/tools/"
rsync -a "$PROJECT_ROOT/tools" "$STAGING/opt/vendora_sbc/"

log "  rsync packaging/systemd/ → /opt/vendora_sbc/packaging/systemd/"
mkdir -p "$STAGING/opt/vendora_sbc/packaging"
rsync -a "$PROJECT_ROOT/packaging/systemd" \
    "$STAGING/opt/vendora_sbc/packaging/"

# pppd ip-up/ip-down hooks (ADR-0018 b4) — the postinst installs them into
# /etc/ppp/*.d/ for PPPoE per-session shaping. Must be in the payload.
log "  rsync packaging/ppp/ → /opt/vendora_sbc/packaging/ppp/"
rsync -a "$PROJECT_ROOT/packaging/ppp" \
    "$STAGING/opt/vendora_sbc/packaging/"

# NM dispatcher LAN transition handler (ADR-0022) — the postinst installs it
# into /etc/NetworkManager/dispatcher.d/. Must be in the payload, same as the
# ppp hooks: without this the .deb ships a postinst that silently finds nothing
# to install and the dead-USB-Eth recovery never arms.
log "  rsync packaging/network-manager/ → /opt/vendora_sbc/packaging/network-manager/"
rsync -a "$PROJECT_ROOT/packaging/network-manager" \
    "$STAGING/opt/vendora_sbc/packaging/"

# Native compilation (ADR-0021): cythonize the staged services/ → .so, add the
# launchers, strip .py source, and patch the staged unit ExecStart. Runs AFTER
# the services + systemd rsync (it edits both). The .deb then carries .so only;
# the postinst detects the .so and skips its on-device compile.
if [ "$NATIVE" = 1 ]; then
    log "  native-compile (Cython → .so, $NATIVE_ARCH) via $CYTHON_PY"
    bash "$PROJECT_ROOT/tools/native-compile" "$STAGING/opt/vendora_sbc" "$CYTHON_PY"
fi

# Show what's about to ship (helps catch accidental inclusions/exclusions)
payload_files=$(find "$STAGING/opt" -type f 2>/dev/null | wc -l)
payload_size=$(du -sh "$STAGING/opt" 2>/dev/null | awk '{print $1}')
log "  staged payload: $payload_files files, $payload_size"

# ---------------------------------------------------------------------------
# Build the .deb
# ---------------------------------------------------------------------------

mkdir -p "$DEB_OUTPUT_DIR"
ARCH_SUFFIX="all"; [ "$NATIVE" = 1 ] && ARCH_SUFFIX="$NATIVE_ARCH"
DEB_PATH="$DEB_OUTPUT_DIR/vendora-sbc_${VERSION}_${ARCH_SUFFIX}.deb"

log "building $DEB_PATH"
# --root-owner-group: force ownership to root:root regardless of the
# build host's effective uid/gid. Important — building as non-root on
# the build VM should still produce a package whose installed files
# end up root-owned on the target.
dpkg-deb --build --root-owner-group "$STAGING" "$DEB_PATH" >/dev/null

# Sidecar sha256 file makes it easy for the apt repo (or operators
# downloading directly) to verify before installing.
sha=$(sha256sum "$DEB_PATH" | awk '{print $1}')
echo "$sha  $(basename "$DEB_PATH")" > "${DEB_PATH%.deb}.sha256"

size=$(du -h "$DEB_PATH" | awk '{print $1}')

# ---------------------------------------------------------------------------
# Report
# ---------------------------------------------------------------------------

log ""
log "DONE."
log "  path:    $DEB_PATH"
log "  size:    $size"
log "  sha256:  $sha"
log ""
log "Install on a target device (image-flashed SBC):"
log "  scp $DEB_PATH vendora-sbc:/tmp/"
log "  ssh vendora-sbc 'sudo apt install -y /tmp/$(basename "$DEB_PATH")'"
log ""
log "Inspect:"
log "  dpkg-deb --info     $DEB_PATH"
log "  dpkg-deb --contents $DEB_PATH"
