#!/usr/bin/env bash
# camel-dryrun — the secretless, ephemeral, executable mock dry-run harness.
#
# The EXTERNAL ANCHOR for xMesh's codegen-first integration capability (see
# MeshWork/camel-capability-design.md, Slice 1 step 2): it actually RUNS a Camel route,
# bounded, in a throwaway sandbox, and gates the verdict on real execution — so a route's
# grounding sign is "it ran and produced the expected output," not "a model thinks it works"
# (which would be gap:selfscore, text judging text). This is what keeps the learning signal
# external (a > 1/2).
#
# HONEST SCOPE: a green run proves WIRING + SYNTAX + the transform under test. It does NOT
# prove the hard enterprise properties — idempotency, message ordering, exactly-once,
# transactional consistency under partial failure — which mocks cannot exercise and which
# fail silently in production. The critic MUST declare those unproven; overclaiming here is
# how a "validated" route causes an incident.
#
# Secretless: the route must use timer/direct/mock/log/stub endpoints or localhost mocks —
# NEVER real credentials or systems. No network reach is required for the canonical routes.
#
# Usage: dryrun.sh <route.yaml> [expect-substring] [max-messages] [timeout-seconds]
# Emits JSON to stdout; exit 0 = pass, 1 = fail, 2 = usage/setup error.
set -uo pipefail

JAVA_OPT="/opt/homebrew/opt/openjdk"
export JAVA_HOME="${JAVA_HOME:-$JAVA_OPT/libexec/openjdk.jdk/Contents/Home}"
export PATH="$JAVA_OPT/bin:$HOME/.jbang/bin:$PATH"

ROUTE="${1:-}"; EXPECT="${2:-}"; MAXMSG="${3:-1}"; TIMEOUT="${4:-90}"
[ -n "$ROUTE" ] || { echo '{"pass":false,"reason":"usage: dryrun.sh <route.yaml> [expect] [maxMsg] [timeoutS]"}'; exit 2; }
[ -f "$ROUTE" ] || { echo "{\"pass\":false,\"reason\":\"route not found: $ROUTE\"}"; exit 2; }
command -v camel >/dev/null 2>&1 || { echo '{"pass":false,"reason":"camel-jbang not installed (jbang app install camel@apache/camel)"}'; exit 2; }

SB="$(mktemp -d)"; trap 'rm -rf "$SB"' EXIT
cp "$ROUTE" "$SB/route.yaml"

# Bounded run in the sandbox; --max-messages makes a timer/poll source self-terminate.
( cd "$SB" && exec camel run route.yaml --max-messages="$MAXMSG" >run.log 2>&1 ) &
PID=$!; killed=false
for _ in $(seq 1 "$TIMEOUT"); do kill -0 "$PID" 2>/dev/null || break; sleep 1; done
if kill -0 "$PID" 2>/dev/null; then kill "$PID" 2>/dev/null; killed=true; fi
wait "$PID" 2>/dev/null

# Judge on REAL execution signals (strip ANSI colour first).
LOG="$SB/clean.log"; sed -E $'s/\x1B\\[[0-9;]*[mK]//g' "$SB/run.log" > "$LOG"
started=false; grep -qE "started in [0-9]|Started route" "$LOG" && started=true
errors=$(grep -cE " ERROR |Caused by:|[A-Za-z.]+Exception|Failed to (create|start|resolve|load)|Error:" "$LOG")
# expect is matched against NON-ERROR lines only: a stack trace can quote the expected substring
# (a route name, a body) while the route never ran — expectMatched=true on a broken route was
# harmless (started=false still failed it) but dishonest (dev-team-3, 2026-08-19).
matched=true; if [ -n "$EXPECT" ]; then grep -vE " ERROR |Caused by:|[A-Za-z.]+Exception|Failed to (create|start|resolve|load)|Error:|^\s+at " "$LOG" | grep -qF "$EXPECT" && matched=true || matched=false; fi
pass=false
[ "$started" = true ] && [ "$errors" -eq 0 ] && [ "$matched" = true ] && [ "$killed" = false ] && pass=true

python3 - "$pass" "$started" "$errors" "$matched" "$killed" "$LOG" <<'PY'
import sys, json
p, s, e, m, k, logf = sys.argv[1:7]
tail = "\n".join(open(logf).read().splitlines()[-14:])
print(json.dumps({
    "pass": p == "true",
    "started": s == "true",
    "errors": int(e),
    "expectMatched": m == "true",
    "watchdogKilled": k == "true",
    "proves": "wiring + syntax + the transform ran",
    "doesNotProve": "idempotency, ordering, exactly-once, transactional consistency (mocks can't) — declare these unproven",
    "logTail": tail,
}, indent=1))
PY
[ "$pass" = true ] && exit 0 || exit 1
