#!/usr/bin/env bash

## Description: Run FlowDrop PHPUnit tests in parallel via Drupal core run-tests.sh
## Usage: phpunit-parallel [--quiet|--dots|--progress] [--fast] [run-tests.sh flags]
## Example: "ddev phpunit-parallel" or "ddev phpunit-parallel --dots" or "ddev phpunit-parallel --fast"
## ExecRaw: true

set -eu -o pipefail

# Drupal core's own parallel test runner — the same one drupal.org GitLab CI
# uses. Each test class runs in its own process with an isolated DB prefix
# and sites/simpletest directory, so Kernel/Functional tests don't collide.
#
# Pass any run-tests.sh flag to override the defaults below, e.g.
#   ddev phpunit-parallel --concurrency 4
#   ddev phpunit-parallel --types PHPUnit-Unit,PHPUnit-Kernel
#   ddev phpunit-parallel --class "Drupal\Tests\flowdrop_workflow\Kernel\SomeTest"
# A --class/--module argument replaces the default --directory target.
#
# Wrapper-only output flags (not run-tests.sh's):
#   --quiet / -q  silent until done; print only failures and the final summary.
#   --dots / -d   like --quiet, but stream one mark per finished test class
#                 ("." pass, red "F" fail) so runners can see progress.
#   --progress / -p  like --quiet, but rewrite a live "[ done/total classes ]"
#                 counter in place (plus failed count). Best on a TTY.
#   --fast / -f   run Unit+Kernel against a per-process in-memory SQLite DB
#                 instead of the shared MySQL container. Measured on this suite:
#                 72 Kernel classes in 1m02s vs 5m06s (12 concurrent kernel
#                 installs contend hard on one MySQL). For local iteration only
#                 — drupal.org CI runs MySQL, and SQLite differs on collation,
#                 type juggling and transaction semantics, so a green --fast run
#                 is not a green gate. Functional tests are excluded by
#                 construction: an in-memory DB is private to the phpunit child
#                 process, so the web server serving BrowserTestBase requests
#                 cannot see it (every assertion fails).

if [ ! -f "$DDEV_DOCROOT/core/scripts/run-tests.sh" ]; then
  echo "Drupal core is not installed ($DDEV_DOCROOT/core missing) — run 'ddev poser' and 'ddev symlink-project' first." >&2
  exit 1
fi

QUIET=0
DOTS=0
PROGRESS=0
FAST=0
CONCURRENCY="$(nproc)"
TARGET=(--directory "modules/custom/flowdrop")
DBURL="$SIMPLETEST_DB"
ARGS=()
USER_TYPES=""
WANT_TYPES=0

for arg in "$@"; do
  # --types takes its value as the next argument; capture it so --fast can tell
  # an explicit selection from the default one it wants to substitute.
  if [ "$WANT_TYPES" -eq 1 ]; then
    USER_TYPES="$arg"
    WANT_TYPES=0
  fi
  case "$arg" in
    --quiet|-q) QUIET=1; continue ;;
    --dots|-d) QUIET=1; DOTS=1; continue ;;
    --progress|-p) QUIET=1; PROGRESS=1; continue ;;
    --fast|-f) FAST=1; continue ;;
    --types) WANT_TYPES=1 ;;
    --class|--module|--all|--directory) TARGET=() ;;
  esac
  ARGS+=("$arg")
done

if [ "$FAST" -eq 1 ]; then
  # Each run-tests.sh child is its own process, so ":memory:" gives every test
  # class a private database — no prefix collisions, no shared-server contention.
  DBURL="sqlite://localhost/:memory:"
  if [ -z "$USER_TYPES" ]; then
    ARGS+=(--types "PHPUnit-Unit,PHPUnit-Kernel")
  elif [[ "$USER_TYPES" == *Functional* ]]; then
    echo "--fast cannot run Functional tests: an in-memory SQLite DB is not visible to the web server that serves BrowserTestBase requests. Drop --fast, or ask for --types PHPUnit-Unit,PHPUnit-Kernel." >&2
    exit 1
  fi
  # A --class/--module target can still name a Functional test, which we cannot
  # detect here; say what DB is in play so a wall of failures is self-explaining.
  echo "--fast: running against in-memory SQLite (Unit+Kernel only; not a CI-parity run)." >&2
fi

# --suppress-deprecations mirrors the repo phpunit.xml (failOnPhpunitDeprecation
# =false): without it run-tests.sh passes --fail-on-phpunit-deprecation to every
# child and the doc-comment-metadata deprecation turns green runs red.
run() {
  php "$DDEV_DOCROOT/core/scripts/run-tests.sh" \
    --color --non-html --suppress-deprecations \
    --sqlite /tmp/run-tests-results.sqlite \
    --dburl "$DBURL" \
    --url "$SIMPLETEST_BASE_URL" \
    --concurrency "$CONCURRENCY" \
    ${ARGS[@]+"${ARGS[@]}"} \
    ${TARGET[@]+"${TARGET[@]}"}
}

if [ "$QUIET" -eq 0 ]; then
  run
  exit $?
fi

OUT="$(mktemp)"
trap 'rm -f "$OUT"' EXIT
STATUS=0
# Streaming filters are bash read-loops, not awk: the image's default awk is
# mawk, which block-buffers piped input until EOF, so nothing would appear
# until the run finished.
is_class_line() { [[ "$1" =~ [0-9]+\.[0-9]+s\  ]]; }
is_class_pass() { [[ "$1" == *" passed"* && "$1" != *"exit code"* && "$1" != *fail* ]]; }

stream_dots() {
  # One mark per finished test class: "." pass, red "F" fail.
  local line
  while IFS= read -r line; do
    if is_class_line "$line"; then
      if is_class_pass "$line"; then printf '.'; else printf '\033[31mF\033[0m'; fi
    fi
  done
}

stream_progress() {
  # run-tests.sh lists every discovered class ("- Drupal\...") before the run
  # starts, so the total is known upfront and the counter updates in place.
  local line total=0 finished=0 failed=0
  while IFS= read -r line; do
    if [[ "$line" =~ ^[[:space:]]+-\ Drupal ]]; then
      total=$((total + 1))
    elif is_class_line "$line"; then
      finished=$((finished + 1))
      if ! is_class_pass "$line"; then failed=$((failed + 1)); fi
      printf '\r[ %d/%d classes ]' "$finished" "$total"
      if [ "$failed" -gt 0 ]; then printf ' \033[31m%d failed\033[0m' "$failed"; fi
    fi
  done
}

if [ "$DOTS" -eq 1 ] || [ "$PROGRESS" -eq 1 ]; then
  FILTER=stream_dots
  [ "$PROGRESS" -eq 1 ] && FILTER=stream_progress
  set +e
  run 2>&1 | tee "$OUT" | "$FILTER"
  STATUS="${PIPESTATUS[0]}"
  set -e
  echo
else
  run > "$OUT" 2>&1 || STATUS=$?
fi

# Failures, per-class lines that aren't clean passes, and the timing summary.
CLASSES="$(grep -cE '^\S*\s+[0-9.]+s ' "$OUT" || true)"
grep -iE 'exit code|fail|error|fatal|wall time' "$OUT" \
  | grep -vE ' [0-9]+ passed$|PhpunitErrorTriggered|No tests found|^\s*- Drupal' || true
echo "Classes run: ${CLASSES}, exit status: ${STATUS}"
exit "$STATUS"
