#!/usr/bin/env bash
# next/tests/http_publish_fold.sh — Step 9-pre-fold integration.
#
# Proves the full POST → publish → broadcast → projection-fold
# chain through HTTP without a real TCP socket. The kernel
# orchestrator threads :projections into the publish Context,
# so outbox:publish broadcasts the signed activity to every
# registered projection process and each fold runs.
#
# Step 9a/b smoke tests will exercise the same path via curl
# once Step 8b-start lights up actual TCP. 10 cases.

set -uo pipefail
cd "$(git rev-parse --show-toplevel)"

SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
  SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
  echo "ERROR: sx_server.exe not found." >&2
  exit 1
fi

VERBOSE="${1:-}"
PASS=0; FAIL=0; ERRORS=""
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT

PRELUDE='KM = <<1,2,3,4>>, KS = [{key_id,k1},{algorithm,ed25519},{value,KM}], AS = [{public_keys,[[{id,k1},{created,0},{value,KM}]]}], projection:start_link(p_count, 0, fun (_A, S) -> S + 1 end), projection:start_link(p_collect, [], fun (A, S) -> [A | S] end), nx_kernel:start_link(alice, KS, AS), nx_kernel:with_projections([p_count, p_collect]), Token = <<102,111,111>>, AuthKey = <<97,117,116,104,111,114,105,122,97,116,105,111,110>>, AuthVal = <<66,101,97,114,101,114,32,102,111,111>>, Cfg = [{publish_token, Token}], BuildReq = fun (B) -> [{method, <<80,79,83,84>>}, {path, http_server:activity_path()}, {headers, [{AuthKey, AuthVal}]}, {body, B}] end,'

cat > "$TMPFILE" <<EPOCHS
(epoch 1)
(load "lib/erlang/tokenizer.sx")
(load "lib/erlang/parser.sx")
(load "lib/erlang/parser-core.sx")
(load "lib/erlang/parser-expr.sx")
(load "lib/erlang/parser-module.sx")
(load "lib/erlang/transpile.sx")
(load "lib/erlang/runtime.sx")
(load "lib/erlang/vm/dispatcher.sx")
(epoch 2)
(eval "(er-load-gen-server!)")
(epoch 3)
(eval "(get (erlang-load-module (file-read \"next/kernel/envelope.erl\")) :name)")
(epoch 4)
(eval "(get (erlang-load-module (file-read \"next/kernel/log.erl\")) :name)")
(epoch 5)
(eval "(get (erlang-load-module (file-read \"next/kernel/pipeline.erl\")) :name)")
(epoch 6)
(eval "(get (erlang-load-module (file-read \"next/kernel/projection.erl\")) :name)")
(epoch 7)
(eval "(get (erlang-load-module (file-read \"next/kernel/outbox.erl\")) :name)")
(epoch 8)
(eval "(get (erlang-load-module (file-read \"next/kernel/nx_kernel.erl\")) :name)")
(epoch 9)
(eval "(get (erlang-load-module (file-read \"next/kernel/http_server.erl\")) :name)")

;; Single authorized POST advances both projection counters
(epoch 10)
(eval "(erlang-eval-ast \"${PRELUDE} http_server:route(BuildReq(<<104,105>>), Cfg), projection:query(p_count)\")")

(epoch 11)
(eval "(erlang-eval-ast \"${PRELUDE} http_server:route(BuildReq(<<104,105>>), Cfg), length(projection:query(p_collect))\")")

;; Three POSTs -> both projections at 3
(epoch 12)
(eval "(get (erlang-eval-ast \"${PRELUDE} http_server:route(BuildReq(<<104,105>>), Cfg), http_server:route(BuildReq(<<104,105>>), Cfg), http_server:route(BuildReq(<<104,105>>), Cfg), {projection:query(p_count), length(projection:query(p_collect))} =:= {3, 3}\") :name)")

;; Log tip and projection counter agree
(epoch 13)
(eval "(get (erlang-eval-ast \"${PRELUDE} http_server:route(BuildReq(<<104,105>>), Cfg), http_server:route(BuildReq(<<104,105>>), Cfg), {nx_kernel:log_tip(), projection:query(p_count)} =:= {2, 2}\") :name)")

;; Unauthorized POST does NOT advance projection state
(epoch 14)
(eval "(erlang-eval-ast \"${PRELUDE} BadAuth = <<66,101,97,114,101,114,32,98,97,100>>, BadReq = [{method, <<80,79,83,84>>}, {path, http_server:activity_path()}, {headers, [{AuthKey, BadAuth}]}, {body, <<104,105>>}], http_server:route(BadReq, Cfg), projection:query(p_count)\")")

;; Sig-failed POST does NOT advance projection state (kernel rejects)
(epoch 15)
(eval "(erlang-eval-ast \"OtherKM = <<9,9,9,9>>, BadKS = [{key_id,k1},{algorithm,ed25519},{value,OtherKM}], AS = [{public_keys,[[{id,k1},{created,0},{value,<<1,2,3,4>>}]]}], projection:start_link(p_count, 0, fun (_A, S) -> S + 1 end), nx_kernel:start_link(alice, BadKS, AS), nx_kernel:with_projections([p_count]), Token = <<102,111,111>>, AuthKey = <<97,117,116,104,111,114,105,122,97,116,105,111,110>>, AuthVal = <<66,101,97,114,101,114,32,102,111,111>>, Cfg = [{publish_token, Token}], Req = [{method, <<80,79,83,84>>}, {path, http_server:activity_path()}, {headers, [{AuthKey, AuthVal}]}, {body, <<>>}], http_server:route(Req, Cfg), projection:query(p_count)\")")

;; The body posted is what the projection sees inside the activity's :object
(epoch 16)
(eval "(get (erlang-eval-ast \"${PRELUDE} http_server:route(BuildReq(<<120,121,122>>), Cfg), [Act] = projection:query(p_collect), case envelope:get_field(object, Act) of {ok, <<120,121,122>>} -> ok; _ -> bad end\") :name)")

;; Three POSTs -> log entries match (round-trip via the kernel log)
(epoch 17)
(eval "(erlang-eval-ast \"${PRELUDE} http_server:route(BuildReq(<<104,105>>), Cfg), http_server:route(BuildReq(<<104,105>>), Cfg), http_server:route(BuildReq(<<104,105>>), Cfg), length(log:entries(nx_kernel:log_state(nx_kernel:query())))\")")

;; Single POST: projection seq number proves fold ran (state changed)
(epoch 18)
(eval "(get (erlang-eval-ast \"${PRELUDE} http_server:route(BuildReq(<<104,105>>), Cfg), projection:query(p_count) =/= 0\") :name)")
EPOCHS

OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)

check() {
  local epoch="$1" desc="$2" expected="$3"
  local actual
  actual=$(echo "$OUTPUT" | awk -v e="$epoch" '
    $0 ~ "^\\(ok-len " e " " { getline; print; exit }
    $0 ~ "^\\(ok " e " "     { print; exit }
    $0 ~ "^\\(error " e " "  { print; exit }
  ')
  [ -z "$actual" ] && actual="<no output for epoch $epoch>"
  if echo "$actual" | grep -qF -- "$expected"; then
    PASS=$((PASS+1))
    [ "$VERBOSE" = "-v" ] && echo "  ok $desc"
  else
    FAIL=$((FAIL+1))
    ERRORS+="  FAIL [$desc] (epoch $epoch) expected: $expected | actual: $actual
"
  fi
}

check  9  "http_server loaded"                "http_server"
check 10  "POST -> p_count = 1"               "1"
check 11  "POST -> p_collect length = 1"      "1"
check 12  "three POSTs -> both at 3"          "true"
check 13  "log_tip == p_count"                "true"
check 14  "unauthorized POST no fold"         "0"
check 15  "sig failure no fold"               "0"
check 16  "projection sees body as :object"   "ok"
check 17  "log entries = 3 after 3 POSTs"     "3"
check 18  "single POST changes proj state"    "true"

TOTAL=$((PASS+FAIL))
if [ $FAIL -eq 0 ]; then
  echo "ok $PASS/$TOTAL next/tests/http_publish_fold.sh passed"
else
  echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
  echo "$ERRORS"
fi
[ $FAIL -eq 0 ]
