#!/usr/bin/env bash
#
#  Licensed to the Apache Software Foundation (ASF) under one or more
#  contributor license agreements.  See the NOTICE file distributed with
#  this work for additional information regarding copyright ownership.
#  The ASF licenses this file to You under the Apache License, Version 2.0
#  (the "License"); you may not use this file except in compliance with
#  the License.  You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#

# This file assumes the gnu version of coreutils is installed, which is not installed by default on a mac
set -e

DOWNLOAD_LOCATION="${1:-downloads}"
DOWNLOAD_LOCATION=$(realpath "${DOWNLOAD_LOCATION}")
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

CWD=$(pwd)

cleanup() {
  echo "❌ Verification failed. ❌"
}
trap cleanup ERR

cd "${DOWNLOAD_LOCATION}/grails"
echo "Searching under ${DOWNLOAD_LOCATION}"

mkdir -p "${DOWNLOAD_LOCATION}/grails/etc/bin/results"
if [[ -f "${DOWNLOAD_LOCATION}/grails/CHECKSUMS" ]]; then
  echo "✅ File 'CHECKSUMS' exists."
else
  echo "❌ File 'CHECKSUMS' not found. Grails Source Distributions should have a CHECKSUMS file at the root..."
  exit 1
fi

if [[ -f "${DOWNLOAD_LOCATION}/grails/BUILD_DATE" ]]; then
  echo "✅ File 'BUILD_DATE' exists."
else
  echo "❌ File 'BUILD_DATE' not found. Grails Source Distributions should have a BUILD_DATE file at the root..."
  exit 1
fi
export SOURCE_DATE_EPOCH=$(cat "${DOWNLOAD_LOCATION}/grails/BUILD_DATE")
export TEST_BUILD_REPRODUCIBLE='true'

if [[ -d "${DOWNLOAD_LOCATION}/grails/etc/bin/results/first" ]]; then
  echo "✅ Directory containing downloaded jar files exists ('first')."
else
  echo "❌ Directory 'first' not found. Please place the published jar files under ${DOWNLOAD_LOCATION}/grails/etc/bin/results/first..."
  exit 1
fi

killall -e java || true

# JDK 21 (default) pass: grails-gradle composite (no Micronaut island), root
# (Micronaut island skipped), grails-forge composite (transitively pulls in
# the root build via includeBuild('..'), island skipped there too).
cd grails-gradle
./gradlew publishToMavenLocal --rerun-tasks -PskipTests --no-build-cache --no-daemon
cd ..
./gradlew publishToMavenLocal --rerun-tasks -PskipTests --no-build-cache --no-daemon -PskipMicronautProjects
cd grails-forge
./gradlew publishToMavenLocal --rerun-tasks -PskipTests --no-build-cache --no-daemon -PskipMicronautProjects
cd ..

# Snapshot the JDK 21 build outputs before switching JDKs. The JDK 25 island pass
# below runs with --rerun-tasks, which recompiles grails-micronaut's transitive
# *project* dependencies (grails-core, grails-spring, the grails-gradle plugins,
# etc.) under JDK 25 and overwrites their JDK 21 jars in build/libs. The release
# builds those modules on JDK 21, so we restore them from this snapshot after the
# island pass. Only grails-micronaut / grails-micronaut-bom are meant to be JDK 25
# artifacts; they are skipped on the JDK 21 passes (-PskipMicronautProjects), so
# they are absent from this snapshot and are left as their JDK 25 outputs.
JDK21_LIBS_SNAPSHOT="${DOWNLOAD_LOCATION}/grails/etc/bin/results/jdk21-libs-snapshot"
rm -rf "${JDK21_LIBS_SNAPSHOT}"
mkdir -p "${JDK21_LIBS_SNAPSHOT}"
echo "Snapshotting JDK 21 build outputs to ${JDK21_LIBS_SNAPSHOT}..."
while IFS= read -r -d '' jar; do
  rel="${jar#./}"
  mkdir -p "${JDK21_LIBS_SNAPSHOT}/$(dirname "${rel}")"
  cp -p "${jar}" "${JDK21_LIBS_SNAPSHOT}/${rel}"
done < <(find . -path ./etc -prune -o -type f -path '*/build/libs/*.jar' ! -name "buildSrc.jar" -print0)

# JDK 25 pass: the Grails-Micronaut "island" only (grails-micronaut,
# grails-micronaut-bom). Micronaut 5 platform GA targets JVM 25 bytecode so
# these two artifacts cannot be reproduced on JDK 21. The verification
# container provides ${JDK_25_HOME}; for local verification outside the
# container, install Liberica JDK matching $JAVA_VERSION_MICRONAUT in
# release.yml and export JDK_25_HOME before running this script.
if [[ -z "${JDK_25_HOME:-}" ]]; then
  echo "❌ JDK_25_HOME is not set; the Grails-Micronaut island requires a separate Liberica JDK 25 install."
  echo "   In the verification container this is set automatically. Outside the container, install Liberica JDK"
  echo "   matching JAVA_VERSION_MICRONAUT in .github/workflows/release.yml and export JDK_25_HOME=/path/to/jdk."
  exit 1
fi
killall -e java || true
echo "Switching to JDK 25 at ${JDK_25_HOME} for the Micronaut island..."
JAVA_HOME="${JDK_25_HOME}" PATH="${JDK_25_HOME}/bin:${PATH}" \
  ./gradlew :grails-micronaut:publishToMavenLocal :grails-micronaut-bom:publishToMavenLocal \
  --rerun-tasks -PskipTests --no-build-cache --no-daemon
killall -e java || true

# Restore the JDK 21 build outputs that the island pass recompiled under JDK 25.
# This overwrites the JDK 25 jars of grails-micronaut's transitive dependencies
# with their JDK 21 equivalents, matching the published release. The island
# modules themselves are not in the snapshot, so their JDK 25 jars are preserved.
echo "Restoring JDK 21 build outputs (keeping only the JDK 25 Micronaut island)..."
while IFS= read -r -d '' snap; do
  rel="${snap#"${JDK21_LIBS_SNAPSHOT}/"}"
  case "${rel}" in
    grails-micronaut/*|grails-bom/micronaut/*) continue ;; # leave island JDK 25 outputs in place
  esac
  mkdir -p "$(dirname "${rel}")"
  cp -p "${snap}" "${rel}"
done < <(find "${JDK21_LIBS_SNAPSHOT}" -type f -name '*.jar' -print0)
rm -rf "${JDK21_LIBS_SNAPSHOT}"

echo "Generating Checksums for Built Jars"
"${SCRIPT_DIR}/generate-build-artifact-hashes.groovy" "${DOWNLOAD_LOCATION}/grails" > "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second.txt"
if [ -e "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second.txt" ] && [ ! -s "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second.txt" ]; then
  echo "❌ Error: Could not find any checksums for built jar files!"
  exit 1
fi

echo "Flattening Checksum file"
## Flatten the jar files since our published artifacts are flat
tmpfile=$(mktemp)
while read -r filepath checksum; do
  printf '%s %s\n' "$(basename "$filepath")" "$checksum"
done < "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second.txt" > "$tmpfile" && mv "$tmpfile" "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second.txt"

echo "Filtering non-published jars"
# filter to only published jars to compare against
cut -d' ' -f1 "${DOWNLOAD_LOCATION}/grails/CHECKSUMS" | grep -Ff - "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second.txt" > "${DOWNLOAD_LOCATION}/grails/etc/bin/results/filtered.txt"
rm -f "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second.txt"
mv "${DOWNLOAD_LOCATION}/grails/etc/bin/results/filtered.txt" "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second.txt"

mkdir -p "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second"
find . -path ./etc -prune -o -type f -path '*/build/libs/*.jar' ! -name "buildSrc.jar" -exec cp -t "${DOWNLOAD_LOCATION}/grails/etc/bin/results/second/" -- {} +

cd "${DOWNLOAD_LOCATION}/grails/etc/bin/results"

echo "Checking for differences in checksums"
# diff -u CHECKSUMS second.txt
DIFF_RESULTS=$(comm -3 <(sort ../../../CHECKSUMS) <(sort second.txt) | cut -d' ' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' | uniq | sort)
echo "$DIFF_RESULTS" > diff.txt

if [ -s diff.txt ]; then
  echo "Differences were found, diffing jar files ..."
  if [[ ! -f "vineflower.jar" ]]; then
      echo "Downloading Vineflower decompiler..."
      curl -sL -o "vineflower.jar" https://github.com/Vineflower/vineflower/releases/download/1.12.0/vineflower-1.12.0.jar
      if [[ $? -ne 0 ]]; then
          echo "❌ Failed to download vineflower.jar ❌"
          exit 1
      fi
  fi

  : > diff_purged.txt  # Ensure the file exists and is empty
  while IFS= read -r jar_file; do
      echo "Checking jar ${jar_file}..."

      echo "Extracting ${jar_file}"
      "${SCRIPT_DIR}/extract-build-artifact.sh" "${jar_file}" "${DOWNLOAD_LOCATION}/grails/etc/bin/results"
      echo "✅ Extracted ${jar_file} to firstArtifact and secondArtifact directories."

      # Check extraction success
      if [[ ! -d "firstArtifact" || ! -d "secondArtifact" ]]; then
          echo "❌ Missing extracted artifacts for ${jar_file} ❌"
          echo "${jar_file}" >> diff_purged.txt
          continue
      fi

      rm -rf "firstSource" "secondSource" || true
      mkdir -p "firstSource" "secondSource"

      echo "Decompiling ${jar_file} class files..."
      java -jar vineflower.jar firstArtifact firstSource > /dev/null 2>&1
      java -jar vineflower.jar secondArtifact secondSource > /dev/null 2>&1
      echo "✅ Decompiled ${jar_file}"

      # Annotation member order in class files is not semantically meaningful and Groovy
      # emits it nondeterministically for annotations copied from precompiled classes
      # (e.g. @DelegatesTo on trait methods), so canonicalize it before comparing
      "${SCRIPT_DIR}/normalize_annotations.groovy" "firstSource" "secondSource"
      echo "✅ Normalized annotation member order for ${jar_file}"

      set +e
      DIFF_RESULT=$(diff -r -q "firstSource" "secondSource")
      set -e

      if [[ -z "${DIFF_RESULT}" ]]; then
          echo "✅ No differences remain for ${jar_file}. Removing from diff.txt."
      else
          echo "❌ Differences still found in ${jar_file}."
          echo "${jar_file}" >> diff_purged.txt
      fi

  done < diff.txt
  mv diff_purged.txt diff.txt
  rm -rf firstArtifact secondArtifact firstSource secondSource || true

  if [ -s diff.txt ]; then
  echo "❌ Differences Found ❌"
  cat diff.txt
  echo "❌ Differences Found ❌"
  else
    echo "✅ Differences were resolved via decompilation. ✅"
    exit 0
  fi
else
  echo "✅ No Differences Found. ✅"
  exit 0
fi

printf '%s\n' "$DIFF_RESULTS" | sed 's|^etc/bin/results/||' > toPurge.txt
find first -type f -name '*.jar' -print | sed 's|^first/||' | grep -F -x -v -f toPurge.txt |
  while IFS= read -r f; do
    rm -f "./first/$f"
  done
find second -type f -name '*.jar' -print | sed 's|^second/||' | grep -F -x -v -f toPurge.txt |
  while IFS= read -r f; do
    rm -f "./second/$f"
  done
rm toPurge.txt
find . -type d -empty -delete
cd "$CWD"
exit 1