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

ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
UPSTREAM_DIR="$ROOT_DIR/../linuxcnc"
VENDOR_DIR="$ROOT_DIR/vendor/linuxcnc"
MANIFEST_FILE="$ROOT_DIR/tools/source-manifest.txt"
BUILD_DIR="$ROOT_DIR/build/native"
CACHE_KEY_FILE="$BUILD_DIR/vendor-sync.input.sha256"
CACHE_OK_FILE="$BUILD_DIR/vendor-sync.ok"

if [[ ! -d "$UPSTREAM_DIR" ]]; then
  echo "missing upstream directory: $UPSTREAM_DIR" >&2
  exit 1
fi

if [[ ! -d "$VENDOR_DIR" ]]; then
  echo "missing vendor directory: $VENDOR_DIR" >&2
  exit 1
fi

if [[ ! -f "$MANIFEST_FILE" ]]; then
  echo "missing manifest: $MANIFEST_FILE" >&2
  exit 1
fi

validate_manifest_path() {
  local src_rel="$1"
  if [[ "$src_rel" = /* || "$src_rel" == */ || "$src_rel" == *"//"* ]]; then
    return 1
  fi

  local component
  local -a path_components
  IFS=/ read -r -a path_components <<< "$src_rel"
  for component in "${path_components[@]}"; do
    if [[ -z "$component" || "$component" == "." || "$component" == ".." ]]; then
      return 1
    fi
  done
}

mkdir -p "$BUILD_DIR"

vendor_sync_fingerprint() {
  {
    git -C "$UPSTREAM_DIR" rev-parse HEAD
    sha256sum \
      "$ROOT_DIR/tools/verify_vendor_sync.sh" \
      "$MANIFEST_FILE"
    while IFS= read -r src_rel; do
      [[ -z "$src_rel" ]] && continue
      [[ "${src_rel:0:1}" == "#" ]] && continue
      printf '%s\0' "$UPSTREAM_DIR/$src_rel"
    done < "$MANIFEST_FILE" | xargs -0 -r sha256sum 2>/dev/null || true
    while IFS= read -r src_rel; do
      [[ -z "$src_rel" ]] && continue
      [[ "${src_rel:0:1}" == "#" ]] && continue
      printf '%s\0' "$VENDOR_DIR/$src_rel"
    done < "$MANIFEST_FILE" | xargs -0 -r sha256sum 2>/dev/null || true
    find "$VENDOR_DIR" -type f -printf 'vendor-list:%P\n' | sort
  } | sha256sum | awk '{ print $1 }'
}

VENDOR_SYNC_FINGERPRINT="$(vendor_sync_fingerprint)"
if [[ -f "$CACHE_KEY_FILE" && -f "$CACHE_OK_FILE" ]] &&
   [[ "$(tr -d '[:space:]' < "$CACHE_KEY_FILE")" == "$VENDOR_SYNC_FINGERPRINT" ]] &&
   [[ "$(tr -d '[:space:]' < "$CACHE_OK_FILE")" == "$VENDOR_SYNC_FINGERPRINT" ]]; then
  echo "vendor sync up to date"
  exit 0
fi

TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT

MANIFEST_CLEAN="$TMP_DIR/manifest.clean"
MANIFEST_SORTED="$TMP_DIR/manifest.sorted"
VENDOR_SORTED="$TMP_DIR/vendor.sorted"

while IFS= read -r src_rel; do
  [[ -z "$src_rel" ]] && continue
  [[ "${src_rel:0:1}" == "#" ]] && continue

  if ! validate_manifest_path "$src_rel"; then
    echo "invalid manifest path: $src_rel" >&2
    exit 1
  fi

  printf '%s\n' "$src_rel" >> "$MANIFEST_CLEAN"
done < "$MANIFEST_FILE"

if [[ ! -s "$MANIFEST_CLEAN" ]]; then
  echo "empty manifest: $MANIFEST_FILE" >&2
  exit 1
fi

sort "$MANIFEST_CLEAN" > "$MANIFEST_SORTED"

if duplicate_entries="$(uniq -d "$MANIFEST_SORTED")" && [[ -n "$duplicate_entries" ]]; then
  echo "duplicate manifest entries:" >&2
  printf '%s\n' "$duplicate_entries" >&2
  exit 1
fi

find "$VENDOR_DIR" -type f | sed "s#^$VENDOR_DIR/##" | sort > "$VENDOR_SORTED"

if extra_vendor="$(comm -23 "$VENDOR_SORTED" "$MANIFEST_SORTED")" && [[ -n "$extra_vendor" ]]; then
  echo "vendor files not listed in manifest:" >&2
  printf '%s\n' "$extra_vendor" >&2
  exit 1
fi

if missing_vendor="$(comm -13 "$VENDOR_SORTED" "$MANIFEST_SORTED")" && [[ -n "$missing_vendor" ]]; then
  echo "manifest files missing from vendor:" >&2
  printf '%s\n' "$missing_vendor" >&2
  exit 1
fi

while IFS= read -r src_rel; do
  upstream_file="$UPSTREAM_DIR/$src_rel"
  vendor_file="$VENDOR_DIR/$src_rel"

  if [[ ! -f "$upstream_file" ]]; then
    echo "missing upstream file: $src_rel" >&2
    exit 1
  fi

  if ! cmp -s "$upstream_file" "$vendor_file"; then
    echo "vendor drift from upstream: $src_rel" >&2
    exit 1
  fi
done < "$MANIFEST_SORTED"

printf '%s\n' "$VENDOR_SYNC_FINGERPRINT" > "$CACHE_KEY_FILE"
printf '%s\n' "$VENDOR_SYNC_FINGERPRINT" > "$CACHE_OK_FILE"
echo "vendor sync validation complete"
