#!/usr/bin/env bash
set -euo pipefail

fail() { echo "D3 dev image build: $*" >&2; exit 1; }

usage() {
  cat <<'EOF'
Usage:
  build-d3-dev-images.sh [ROLE...]
  build-d3-dev-images.sh --local-only --tag-prefix PREFIX \
    --output-manifest PATH [ROLE...]

Roles default to runtime-api, runtime-worker, and integration-service.

The default mode publishes Test2 development images to their owner repositories.
Local-only mode instead loads unsigned, non-promotable images into the current
Docker daemon. PREFIX must be a canonical lowercase Docker repository prefix;
each image is tagged as PREFIX/ROLE:dev-SOURCE_COMMIT. PATH receives a manifest
that binds every role to that exact source commit, local image reference, and
Docker image ID.
EOF
}

mode="publish"
local_tag_prefix=""
output_manifest=""
roles=()
while [[ $# -gt 0 ]]; do
  case "$1" in
    --local-only)
      mode="local-only"
      shift
      ;;
    --tag-prefix)
      [[ $# -ge 2 ]] || fail "--tag-prefix requires a value"
      local_tag_prefix="$2"
      shift 2
      ;;
    --output-manifest)
      [[ $# -ge 2 ]] || fail "--output-manifest requires a value"
      output_manifest="$2"
      shift 2
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    --)
      shift
      roles+=("$@")
      break
      ;;
    -*) fail "unsupported option $1" ;;
    *)
      roles+=("$1")
      shift
      ;;
  esac
done

if [[ "$mode" == "local-only" ]]; then
  [[ -n "$local_tag_prefix" ]] || fail "--local-only requires --tag-prefix"
  [[ -n "$output_manifest" ]] || fail "--local-only requires --output-manifest"
  [[ "$local_tag_prefix" =~ ^[a-z0-9]+([._-][a-z0-9]+)*(/[a-z0-9]+([._-][a-z0-9]+)*)*$ ]] \
    || fail "--tag-prefix must be a canonical lowercase Docker repository prefix without a tag or digest"
  [[ "$output_manifest" != */ ]] || fail "--output-manifest must name a file"
else
  [[ -z "$local_tag_prefix" ]] || fail "--tag-prefix is valid only with --local-only"
  [[ -z "$output_manifest" ]] || fail "--output-manifest is valid only with --local-only"
fi

if [[ ${#roles[@]} -eq 0 ]]; then
  roles=(runtime-api runtime-worker integration-service)
fi
for role in "${roles[@]}"; do
  case "$role" in
    runtime-api|runtime-worker|integration-service) ;;
    *) fail "unsupported role $role" ;;
  esac
done
if [[ "$mode" == "local-only" ]]; then
  for ((i = 0; i < ${#roles[@]}; i++)); do
    for ((j = i + 1; j < ${#roles[@]}; j++)); do
      [[ "${roles[$i]}" != "${roles[$j]}" ]] || fail "duplicate role ${roles[$i]}"
    done
  done
fi

repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
source_sha="$(git -C "$repo_root" rev-parse HEAD)"
[[ "$source_sha" =~ ^[0-9a-f]{40}$ ]] || fail "source revision is not exact"

verify_source_identity() {
  [[ "$(git -C "$repo_root" rev-parse HEAD)" == "$source_sha" ]] \
    || fail "source revision changed during the build"
  [[ -z "$(git -C "$repo_root" status --porcelain)" ]] \
    || fail "source worktree must remain clean during the build"
}

verify_source_identity

command -v go >/dev/null || fail "host Go toolchain is required"
command -v docker >/dev/null || fail "Docker is required"
command -v jq >/dev/null || fail "jq is required"
if [[ "$mode" == "publish" ]]; then
  command -v gh >/dev/null || fail "host gh CLI is required"
  gh auth status >/dev/null 2>&1 || fail "host gh CLI is not authenticated"
fi
docker buildx version >/dev/null || fail "Docker Buildx is required"

has_role() {
  local expected="$1" role
  for role in "${roles[@]}"; do
    [[ "$role" == "$expected" ]] && return 0
  done
  return 1
}

build_root="$(mktemp -d)"
metadata_root="$(mktemp -d)"
output_manifest_tmp=""
cleanup() {
  rm -rf "$build_root" "$metadata_root"
  [[ -z "$output_manifest_tmp" ]] || rm -f -- "$output_manifest_tmp"
}
trap cleanup EXIT

version="dev-${source_sha}"
backend_root="$repo_root/backend"
export CGO_ENABLED=0 GOOS=linux GOARCH=amd64

echo "[dev-image] compiling exact NexusCRM source $source_sha"
(
  cd "$backend_root"
  if has_role runtime-api; then
    GOWORK=off go build -trimpath -ldflags="-s -w -X main.version=$source_sha" \
      -o "$build_root/runtime-api" ./cmd/runtime-api/
    GOWORK=off go build -trimpath -ldflags="-s -w" \
      -o "$build_root/l0-controlplane" ./cmd/setup/
  fi
  if has_role runtime-worker; then
    GOWORK=off go build -trimpath -ldflags="-s -w -X main.version=$source_sha" \
      -o "$build_root/runtime-worker" ./cmd/runtime-worker/
  fi
  if has_role integration-service; then
    GOWORK=off go build -trimpath -ldflags="-s -w -X main.version=$version" \
      -o "$build_root/integration-service" ./cmd/integration-service/
  fi
)
if has_role runtime-api; then
  cp "$backend_root/internal/runtimeconfig/schema/runtime-api.schema.json" "$build_root/"
  cp "$backend_root/internal/runtimeproduction/host-compatibility.json" "$build_root/"
fi
if has_role runtime-worker; then
  cp "$backend_root/internal/runtimeconfig/schema/runtime-worker.schema.json" "$build_root/"
fi
verify_source_identity

if [[ "$mode" == "publish" ]]; then
  registry_user="${GITHUB_ACTOR:-}"
  if [[ -z "$registry_user" ]]; then
    registry_user="$(gh api user --jq .login)"
  fi
  gh auth token | docker login ghcr.io --username "$registry_user" --password-stdin >/dev/null
fi

build_image() {
  local role="$1"
  local image="$2"
  local metadata="$metadata_root/$role.json"
  echo "[dev-image] publishing $role for $source_sha"
  docker buildx build \
    --platform linux/amd64 \
    --provenance=false \
    --sbom=true \
    --push \
    --target "$role" \
    --build-arg "VERSION=$version" \
    --build-arg "SOURCE_REVISION=$source_sha" \
    --tag "$image:dev-$source_sha" \
    --metadata-file "$metadata" \
    -f "$repo_root/infra/dev-service.Dockerfile" "$build_root" >/dev/null
  digest="$(jq -er '."containerimage.digest"' "$metadata")"
  [[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || fail "$role returned no canonical digest"
  docker pull --quiet --platform linux/amd64 "$image@$digest" >/dev/null
  [[ "$(docker image inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}' "$image@$digest")" == "$source_sha" ]] \
    || fail "$role image revision disagrees with source"
  [[ "$(docker image inspect --format '{{ index .Config.Labels "dev.nexuscrm.release.class" }}' "$image@$digest")" == test2-dev-candidate ]] \
    || fail "$role image is not marked as a Test2 dev candidate"
  jq -cn --arg role "$role" --arg image "$image" --arg digest "$digest" \
    --arg source_commit "$source_sha" \
    '{role:$role,oci_repository:$image,digest:$digest,source_commit:$source_commit}'
}

local_manifest_entries="$metadata_root/local-images.jsonl"

candidate_role() {
  case "$1" in
    runtime-api) echo runtime_api ;;
    runtime-worker) echo runtime_worker ;;
    integration-service) echo integration_service ;;
    *) fail "unsupported local candidate role $1" ;;
  esac
}

image_role_label() {
  case "$1" in
    runtime_api) echo runtime-api ;;
    runtime_worker) echo runtime-worker ;;
    integration_service) echo integration-service ;;
    *) fail "unsupported local candidate role $1" ;;
  esac
}

local_image_field() {
  local image="$1"
  local jq_field="$2"
  docker image inspect "$image" | jq -er ".[0]$jq_field"
}

verify_local_image() {
  local role="$1"
  local image_ref="$2"
  local image_id="$3"
  local resolved_id revision release_class image_role image_os image_architecture

  resolved_id="$(local_image_field "$image_ref" '.Id')"
  [[ "$resolved_id" == "$image_id" ]] \
    || fail "$role local image reference drifted from $image_id to $resolved_id"
  revision="$(local_image_field "$image_id" '.Config.Labels["org.opencontainers.image.revision"]')"
  [[ "$revision" == "$source_sha" ]] || fail "$role local image revision disagrees with source"
  release_class="$(local_image_field "$image_id" '.Config.Labels["dev.nexuscrm.release.class"]')"
  [[ "$release_class" == test-branch-candidate ]] \
    || fail "$role local image is not marked as a branch Test candidate"
  image_role="$(docker image inspect "$image_id" | jq -er \
    '.[0].Config.Labels["dev.nexuscrm.runtime.role"] // .[0].Config.Labels["dev.nexuscrm.integration.role"]')"
  [[ "$image_role" == "$(image_role_label "$role")" ]] \
    || fail "$role local image role label is $image_role"
  image_os="$(local_image_field "$image_id" '.Os')"
  image_architecture="$(local_image_field "$image_id" '.Architecture')"
  [[ "$image_os/$image_architecture" == linux/amd64 ]] \
    || fail "$role local image platform is $image_os/$image_architecture, expected linux/amd64"
}

build_local_image() {
  local build_role="$1"
  local role
  role="$(candidate_role "$build_role")"
  local image_ref="$local_tag_prefix/$build_role:dev-$source_sha"
  local image_id

  echo "[dev-image] loading unsigned local-only $role for $source_sha"
  docker buildx build \
    --platform linux/amd64 \
    --provenance=false \
    --load \
    --target "$build_role" \
    --build-arg "VERSION=$version" \
    --build-arg "SOURCE_REVISION=$source_sha" \
    --label "dev.nexuscrm.release.class=test-branch-candidate" \
    --tag "$image_ref" \
    -f "$repo_root/infra/dev-service.Dockerfile" "$build_root" >/dev/null
  image_id="$(local_image_field "$image_ref" '.Id')"
  [[ "$image_id" =~ ^sha256:[0-9a-f]{64}$ ]] || fail "$role returned no canonical local image ID"
  verify_source_identity
  verify_local_image "$role" "$image_ref" "$image_id"
  jq -cn --arg role "$role" --arg image_ref "$image_ref" --arg image_id "$image_id" \
    --arg source_commit "$source_sha" \
    '{role:$role,image_ref:$image_ref,image_id:$image_id,source_commit:$source_commit}' \
    >> "$local_manifest_entries"
}

write_local_manifest() {
  local output_dir output_name
  output_dir="$(dirname -- "$output_manifest")"
  output_name="$(basename -- "$output_manifest")"

  verify_source_identity
  while IFS=$'\t' read -r role image_ref image_id; do
    verify_local_image "$role" "$image_ref" "$image_id"
  done < <(jq -r '[.role,.image_ref,.image_id] | @tsv' "$local_manifest_entries")

  mkdir -p -- "$output_dir"
  output_dir="$(cd -- "$output_dir" && pwd)"
  output_manifest_tmp="$(mktemp "$output_dir/.$output_name.XXXXXX")"
  jq -s --arg source_commit "$source_sha" \
    '{schema:"nexuscrm.d3-local-images/v1",release_class:"test-only-unsigned-non-promotable",source_commit:$source_commit,images:sort_by(.role)}' \
    "$local_manifest_entries" > "$output_manifest_tmp"
  mv -- "$output_manifest_tmp" "$output_dir/$output_name"
  output_manifest_tmp=""
  echo "[dev-image] wrote local-only manifest $output_dir/$output_name"
}

if [[ "$mode" == "publish" ]]; then
  for role in "${roles[@]}"; do
    case "$role" in
      runtime-api) build_image runtime-api ghcr.io/pingcap-inc/nexuscrm-runtime-api ;;
      runtime-worker) build_image runtime-worker ghcr.io/pingcap-inc/nexuscrm-runtime-worker ;;
      integration-service) build_image integration-service ghcr.io/pingcap-inc/nexuscrm-integration-service ;;
    esac
  done
else
  : > "$local_manifest_entries"
  for role in "${roles[@]}"; do
    build_local_image "$role"
  done
  write_local_manifest
fi
