#!/bin/bash
# Shared arm64 publisher (buildx / QEMU). Bakes a WAR into jetty-slim FROM
# thoughtframe-os:latest-arm64 — one OS base for both product images:
#   Edge (lean):   WAR_PROJECT=edge   IMAGE_NAME=thoughtframe-edge     → prefer ./edge/scripts/publish-arm64.sh
#   ThoughtFrame (full): WAR_PROJECT=damsel IMAGE_NAME=thoughtframe-runtime  → this script defaults
#
# Do not build on the Pi. Do not fold into the fast amd64 ThoughtFrame job.
#
#   BUILD_NUMBER=15 ./docker/jetty-slim/scripts/publish-arm64.sh
#   BUILD_OS=1 ...   # rebuild arm64 OS base (apt under QEMU — very slow)
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MONOREPO="$(cd "$ROOT_DIR/../.." && pwd)"
OS_DIR="${MONOREPO}/docker/thoughtframe-os"
RUNTIME_DIR="${MONOREPO}/docker/jetty-slim"

# Push via loopback + buildx network=host. :5001 is the plain registry backend on
# damsel; :5000 is nginx TLS (Tailscale / Let's Encrypt). Existence probe must
# NOT use 127.0.0.1 from inside the Jenkins container — that loopback is the
# container itself, so imagetools always fails and we rebuild OS under QEMU.
REGISTRY="${REGISTRY:-127.0.0.1:5001}"
REGISTRY_PROBE="${REGISTRY_PROBE:-registry.thoughtframe.ai}"
IMAGE_NAME="${IMAGE_NAME:-thoughtframe-runtime}"
WAR_PROJECT="${WAR_PROJECT:-damsel}"
OS_IMAGE_NAME="${OS_IMAGE_NAME:-thoughtframe-os}"
IMAGE_TAG="${BUILD_NUMBER:-${TAG:-local}}-arm64"
OS_TAG="${OS_TAG:-latest-arm64}"
BUILD_OS="${BUILD_OS:-0}"
WITH_LIBREOFFICE="${WITH_LIBREOFFICE:-0}"
# numcodecs has no aarch64 wheel; compiling it under QEMU fails and only ThoughtFrame uses the CLI.
WITH_COPERNICUS="${WITH_COPERNICUS:-0}"
BUILDER_NAME="${BUILDER_NAME:-thoughtframe-arm64}"

OS_IMAGE="${REGISTRY}/${OS_IMAGE_NAME}:${OS_TAG}"
RUNTIME_IMAGE="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
RUNTIME_LATEST="${REGISTRY}/${IMAGE_NAME}:latest-arm64"

WAR="${MONOREPO}/${WAR_PROJECT}/deploy/ROOT.war"
if [ ! -f "${WAR}" ]; then
	echo "No ${WAR} — running :${WAR_PROJECT}:createWar"
	(
		cd "$MONOREPO"
		./gradlew ":${WAR_PROJECT}:createWar"
	)
fi
if [ ! -f "${WAR}" ]; then
	echo "Missing ${WAR} after createWar" >&2
	exit 1
fi
cp -f "${WAR}" "${RUNTIME_DIR}/ROOT.war"

mkdir -p /tmp/buildkit
if [[ "${REGISTRY}" == 127.0.0.1:* ]] || [[ "${REGISTRY}" == localhost:* ]]; then
	cat > /tmp/buildkit/buildkitd.toml <<EOF
[registry."${REGISTRY}"]
  http = true
  insecure = true
EOF
else
	# HTTPS + public CA (damsel.tail71632d.ts.net) — buildkit default TLS
	: >/tmp/buildkit/buildkitd.toml
fi

echo "Ensuring buildx builder ${BUILDER_NAME} ..."
docker buildx rm "${BUILDER_NAME}" >/dev/null 2>&1 || true
docker buildx create \
	--name "${BUILDER_NAME}" \
	--use \
	--driver-opt network=host \
	--config /tmp/buildkit/buildkitd.toml
docker buildx inspect --bootstrap >/dev/null

MANIFEST_ACCEPT="application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json"

# Digest of the OS recipe, stamped into the image by dockerOsBuild. Must match the
# shell and Gradle sides byte for byte, so both hash the Dockerfile itself.
OS_BASE_REVISION="$(sha256sum "${OS_DIR}/Dockerfile" | cut -c1-12)"

# An index whose per-arch children were garbage-collected still answers 200 for the
# tag, so buildx would "reuse" a base it cannot pull. Every child must resolve too.
#
# Reuse also requires the published base to carry the current recipe revision. The
# arm64 base is only rebuilt when it is missing, so the Aug 31 image kept being reused
# after the Sep 12 DCCL layer landed and Edge nodes shipped without /usr/bin/dccl.
os_base_complete() {
	local index digests digest manifest config blob revision_seen
	index="$(curl -sf -m 10 -H "Accept: ${MANIFEST_ACCEPT}" \
		"https://${REGISTRY_PROBE}/v2/${OS_IMAGE_NAME}/manifests/${OS_TAG}")" || return 1
	case "${index}" in
		*'"manifests"'*) ;;
		*) return 0 ;;
	esac
	digests="$(printf '%s' "${index}" | grep -o 'sha256:[0-9a-f]\{64\}' | sort -u)" || return 1
	[ -n "${digests}" ] || return 1
	revision_seen=0
	for digest in ${digests}; do
		# The registry pretty-prints manifests, so collapse whitespace before matching.
		manifest="$(curl -sf -m 10 -H "Accept: ${MANIFEST_ACCEPT}" \
			"https://${REGISTRY_PROBE}/v2/${OS_IMAGE_NAME}/manifests/${digest}" | tr -d ' \n\t')" || return 1
		config="$(printf '%s' "${manifest}" \
			| grep -o '"config":{[^}]*"digest":"sha256:[0-9a-f]\{64\}"' \
			| grep -o 'sha256:[0-9a-f]\{64\}' | head -1)"
		[ -n "${config}" ] || continue
		blob="$(curl -sf -m 20 "https://${REGISTRY_PROBE}/v2/${OS_IMAGE_NAME}/blobs/${config}" \
			| tr -d ' \n\t')" || return 1
		case "${blob}" in
			*"\"org.thoughtframe.os-base-revision\":\"${OS_BASE_REVISION}\""*) revision_seen=1 ;;
		esac
	done
	if [ "${revision_seen}" != "1" ]; then
		echo "Published ${OS_IMAGE_NAME}:${OS_TAG} predates OS recipe ${OS_BASE_REVISION}"
		return 1
	fi
	return 0
}

need_os=0
if [ "${BUILD_OS}" = "1" ]; then
	need_os=1
elif ! os_base_complete; then
	echo "No complete ${OS_IMAGE_NAME}:${OS_TAG} at ${REGISTRY_PROBE} — will build OS base"
	need_os=1
fi

if [ "${need_os}" = "1" ]; then
	echo "Building + pushing ${OS_IMAGE} (linux/arm64, WITH_LIBREOFFICE=${WITH_LIBREOFFICE}, WITH_COPERNICUS=${WITH_COPERNICUS})"
	docker buildx build \
		--platform linux/arm64 \
		--build-arg "WITH_LIBREOFFICE=${WITH_LIBREOFFICE}" \
		--build-arg "WITH_COPERNICUS=${WITH_COPERNICUS}" \
		--build-arg "OS_BASE_REVISION=${OS_BASE_REVISION}" \
		-t "${OS_IMAGE}" \
		--push \
		"${OS_DIR}"
else
	echo "Reusing ${OS_IMAGE}"
fi

echo "Building + pushing ${RUNTIME_IMAGE} (FROM ${OS_IMAGE})"
docker buildx build \
	--platform linux/arm64 \
	--build-arg "OS_IMAGE=${OS_IMAGE}" \
	-t "${RUNTIME_IMAGE}" \
	-t "${RUNTIME_LATEST}" \
	--push \
	"${RUNTIME_DIR}"

echo "Published ${RUNTIME_IMAGE} and ${RUNTIME_LATEST}"
if [ "${need_os}" = "1" ]; then
	echo "Published ${OS_IMAGE}"
fi
