#!/usr/bin/env bash

# The expected failures are asserted explicitly instead of delegated to `set -e`.
set -o pipefail

fail() {
  printf 'FAIL: %s\n' "$1" >&2
  exit 1
}

SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)" \
  || fail "cannot resolve script directory"
cd "$SCRIPT_DIR" || fail "cannot enter script directory"

for injected in JAVA_TOOL_OPTIONS JDK_JAVA_OPTIONS _JAVA_OPTIONS; do
  if test -n "${!injected:-}"; then
    fail "$injected must be unset for the strong-encapsulation probe"
  fi
done

test -n "${JDK17_HOME:-}" \
  || fail "set JDK17_HOME to a JDK 17 root"
JAVA="$JDK17_HOME/bin/java"
JAVAC="$JDK17_HOME/bin/javac"
JAVAP="$JDK17_HOME/bin/javap"
for tool in "$JAVA" "$JAVAC" "$JAVAP"; do
  test -x "$tool" || fail "missing executable: $tool"
done

JAVA_VERSION="$("$JAVA" -version 2>&1)" || fail "java -version failed"
JAVAC_VERSION="$("$JAVAC" -version 2>&1)" || fail "javac -version failed"
JAVAP_VERSION="$("$JAVAP" -version 2>&1)" || fail "javap -version failed"
grep -Eq '^(openjdk|java) version "17([.]|\")' <<<"$JAVA_VERSION" \
  || fail "java is not Java 17"
grep -Eq '^javac 17([.]|$)' <<<"$JAVAC_VERSION" \
  || fail "javac is not Java 17"
grep -Eq '^17([.]|$)' <<<"$JAVAP_VERSION" \
  || fail "javap is not Java 17"

printf '%s\n' "$JAVA_VERSION" "$JAVAC_VERSION" "javap $JAVAP_VERSION"

TMP_BASE="$(CDPATH= cd -- "${TMPDIR:-/tmp}" && pwd -P)" \
  || fail "cannot resolve temporary directory"
LAB_OUT="$(mktemp -d "$TMP_BASE/annotations-reflection-plan.XXXXXX")" \
  || fail "mktemp failed"
case "$LAB_OUT" in
  "$TMP_BASE"/annotations-reflection-plan.*) ;;
  *) fail "unsafe temporary path: $LAB_OUT" ;;
esac
cleanup() {
  if ! rm -rf -- "$LAB_OUT"; then
    printf 'FAIL: cannot clean temporary directory: %s\n' "$LAB_OUT" >&2
    trap - EXIT
    exit 1
  fi
}
trap cleanup EXIT

mkdir -p "$LAB_OUT/classes" || fail "cannot create class output"
"$JAVAC" --release 17 -Xlint:all -Werror \
  -d "$LAB_OUT/classes" MetadataRouterDemo.java \
  || fail "demo compilation failed"

"$JAVAP" -v -p -s -classpath "$LAB_OUT/classes" \
  'example.MetadataRouterDemo$Handler' >"$LAB_OUT/handler.javap" \
  || fail "javap Handler failed"
"$JAVAP" -v -p -s -classpath "$LAB_OUT/classes" \
  'example.MetadataRouterDemo$BaseHandler' >"$LAB_OUT/base-handler.javap" \
  || fail "javap BaseHandler failed"

awk '
  /^  public java.lang.String execute\(java.lang.String\);$/ { capture = 1 }
  capture { print }
  capture && /^}$/ { exit }
' "$LAB_OUT/handler.javap" >"$LAB_OUT/execute-method.block"
awk '
  /^RuntimeInvisibleAnnotations:$/ { capture = 1 }
  capture { print }
  capture && /^NestHost:/ { exit }
' "$LAB_OUT/handler.javap" >"$LAB_OUT/handler-invisible.block"
awk '
  /^RuntimeVisibleAnnotations:$/ { capture = 1 }
  capture { print }
  capture && /^NestHost:/ { exit }
' "$LAB_OUT/base-handler.javap" >"$LAB_OUT/base-visible.block"

test -s "$LAB_OUT/execute-method.block" \
  || fail "cannot isolate Handler.execute method block"
test -s "$LAB_OUT/handler-invisible.block" \
  || fail "cannot isolate Handler class-level invisible annotation block"
test -s "$LAB_OUT/base-visible.block" \
  || fail "cannot isolate BaseHandler class-level visible annotation block"

grep -Fq 'RuntimeInvisibleAnnotations:' "$LAB_OUT/handler-invisible.block" \
  || fail "CLASS-retained annotation attribute is missing"
grep -Fq 'example.MetadataRouterDemo$BuildIndex' \
  "$LAB_OUT/handler-invisible.block" \
  || fail "BuildIndex is missing from the invisible annotation attribute"
grep -Fq 'value="orders-v1"' "$LAB_OUT/handler-invisible.block" \
  || fail "BuildIndex value is missing from the invisible annotation attribute"
if grep -Eq 'MetadataRouterDemo\$Routes|order\.create|order\.retry' \
  "$LAB_OUT/handler-invisible.block"; then
  fail "runtime Route metadata leaked into the invisible class attribute"
fi

grep -Fq 'RuntimeVisibleAnnotations:' "$LAB_OUT/execute-method.block" \
  || fail "runtime-visible route attribute is missing"
grep -Fq 'example.MetadataRouterDemo$Routes' "$LAB_OUT/execute-method.block" \
  || fail "repeatable Route container is missing"
grep -Fq 'value="order.create"' "$LAB_OUT/execute-method.block" \
  || fail "order.create metadata is missing"
grep -Fq 'value="order.retry"' "$LAB_OUT/execute-method.block" \
  || fail "order.retry metadata is missing"
if grep -Fq 'MetadataRouterDemo$BuildIndex' "$LAB_OUT/execute-method.block"; then
  fail "CLASS-retained BuildIndex leaked into the visible method attribute"
fi

grep -Fq 'RuntimeVisibleAnnotations:' "$LAB_OUT/base-visible.block" \
  || fail "runtime-visible Component attribute is missing"
grep -Fq 'example.MetadataRouterDemo$Component' \
  "$LAB_OUT/base-visible.block" \
  || fail "Component metadata is missing from BaseHandler"
grep -Fq 'value="base-handler"' "$LAB_OUT/base-visible.block" \
  || fail "Component value is missing"
if grep -Eq 'MetadataRouterDemo\$(BuildIndex|Routes)' \
  "$LAB_OUT/base-visible.block"; then
  fail "unrelated metadata leaked into the BaseHandler visible attribute"
fi

SUCCESS_OUTPUT="$("$JAVA" -cp "$LAB_OUT/classes" \
  example.MetadataRouterDemo success 2>"$LAB_OUT/success.err" \
  | tr -d '\r')" \
  || fail "success mode failed"
test ! -s "$LAB_OUT/success.err" || fail "success mode wrote stderr"
EXPECTED_SUCCESS="$(printf '%s\n' \
  'mode=success' \
  'candidate-count=1' \
  'component=base-handler' \
  'component-direct=false' \
  'class-retention-reflection-visible=false' \
  'repeatable-routes=2' \
  'plan-count=2' \
  'snapshot=immutable' \
  'unregistered-route-present=false' \
  'invoke=order.create result=handled:payload' \
  'invoke=order.retry result=handled:payload' \
  'invocation-count=2')" || fail "cannot build expected success output"
test "$SUCCESS_OUTPUT" = "$EXPECTED_SUCCESS" \
  || fail "unexpected success output"

assert_plan_failure() {
  mode="$1"
  expected_rejection="$2"
  "$JAVA" -cp "$LAB_OUT/classes" example.MetadataRouterDemo "$mode" \
    >"$LAB_OUT/$mode.out" 2>"$LAB_OUT/$mode.err"
  status=$?
  test "$status" -eq 2 || fail "$mode exit status is $status, expected 2"
  test ! -s "$LAB_OUT/$mode.out" || fail "$mode unexpectedly wrote stdout"
  actual="$(tr -d '\r' <"$LAB_OUT/$mode.err")" \
    || fail "cannot read $mode stderr"
  expected="$(printf '%s\n' \
    "mode=$mode" \
    "rejected=$expected_rejection" \
    'invocation-count=0')" || fail "cannot build $mode expectation"
  test "$actual" = "$expected" || fail "unexpected $mode stderr"
}

assert_plan_failure \
  'duplicate' \
  'duplicate-route=order.create'
assert_plan_failure \
  'bad-signature' \
  'invalid-signature=example.MetadataRouterDemo$BadSignatureHandler.execute expected=(String)String actual=(int)int'

ILLEGAL_OUTPUT="$("$JAVA" -cp "$LAB_OUT/classes" \
  example.MetadataRouterDemo illegal-access \
  2>"$LAB_OUT/illegal-access.err" | tr -d '\r')" \
  || fail "illegal-access mode failed"
test ! -s "$LAB_OUT/illegal-access.err" \
  || fail "illegal-access mode wrote stderr"
EXPECTED_ILLEGAL="$(printf '%s\n' \
  'mode=illegal-access' \
  'trySetAccessible=false' \
  'setAccessible=InaccessibleObjectException' \
  'invocation-count=0')" || fail "cannot build illegal-access expectation"
test "$ILLEGAL_OUTPUT" = "$EXPECTED_ILLEGAL" \
  || fail "unexpected illegal-access output"

printf '%s\n' \
  'class-retention=RuntimeInvisibleAnnotations' \
  'runtime-retention=RuntimeVisibleAnnotations' \
  'repeatable-routes=2' \
  'inherited-component=true' \
  'controlled-candidates=1' \
  'plan-snapshot=Map.copyOf' \
  'method-handle-keyed-invoke=true' \
  'duplicate=rejected-before-invoke' \
  'bad-signature=rejected-before-invoke' \
  'illegal-access=try-false+set-throws' \
  'PASS'
