#!/usr/bin/env bash
# Webpath conformance — L1 Core starter kit.
# Black-box checks over plain HTTP. Joins a throwaway space, runs the cases,
# and deletes the space on exit. Full suite (L2/L3) coming soon.
#
#   WEBPATH_HOST=https://knower.cc ./conformance.sh
#
# Requires: bash, curl, python3 (for JSON). No host internals needed.
set -uo pipefail

HOST="${WEBPATH_HOST:-https://knower.cc}"
PASS=0; FAIL=0
jget(){ python3 -c "import sys,json;d=json.load(sys.stdin);print(d$1)" 2>/dev/null; }
ok(){ PASS=$((PASS+1)); printf '  \033[32m✓\033[0m %s\n' "$1"; }
no(){ FAIL=$((FAIL+1)); printf '  \033[31m✗\033[0m %s\n' "$1"; [ -n "${2:-}" ] && printf '      %s\n' "$2"; }
# code URL [curl-args...] -> echoes HTTP status
code(){ local u="$1"; shift; curl -s -o /dev/null -w '%{http_code}' "$@" "$u"; }

echo "Webpath conformance (L1 Core) against $HOST"
echo "------------------------------------------------------------"

# --- bootstrap: join a throwaway space (WP-S1) ---
NAME="wpconf$(date +%s)"
JOIN="$(curl -s -X POST "$HOST/join" -d "{\"name\":\"$NAME\"}")"
SPACE="$(echo "$JOIN" | jget "['space']")"        # https://<name>.<host>/
TOKEN="$(echo "$JOIN" | jget "['token']")"
SPACE="${SPACE%/}"                                 # strip trailing slash
SHOST="${SPACE#https://}"                          # space hostname (for ?confirm)
if [ -z "$SPACE" ] || [ -z "$TOKEN" ]; then
  echo "FATAL: /join did not return space+token. Got: $JOIN"; exit 2
fi
trap 'curl -s -o /dev/null -X DELETE -H "Authorization: Bearer $TOKEN" "$SPACE/?confirm=$SHOST"' EXIT
ok "WP-S1  /join minted $SPACE with a key"

A=(-H "Authorization: Bearer $TOKEN")

# WP-S4  new space starts empty
n="$(curl -s "${A[@]}" "$SPACE/?list" | jget "['entries'].__len__()")"
[ "$n" = "0" ] && ok "WP-S4  new space starts empty" || no "WP-S4  new space starts empty" "entries=$n"

# WP-D1/D2  write -> read round-trip, ETag, inferred Content-Type
put="$(curl -s -D - -o /dev/null "${A[@]}" -X PUT --data-binary '# hello' "$SPACE/notes/t.md?visibility=public")"
echo "$put" | grep -qi '^etag:' && ok "WP-D1  ETag present on write" || no "WP-D1  ETag present on write"
body="$(curl -s "$SPACE/notes/t.md")"
[ "$body" = "# hello" ] && ok "WP-D1  bytes round-trip verbatim" || no "WP-D1  bytes round-trip verbatim" "got: $body"
ct="$(curl -s -D - -o /dev/null "$SPACE/notes/t.md" | grep -i '^content-type:' )"
echo "$ct" | grep -qi 'text/markdown' && ok "WP-D2  Content-Type inferred from extension" || no "WP-D2  Content-Type inferred" "$ct"

# WP-R1/R2  clean URL renders, extension is source
rend="$(curl -s -D - -o /dev/null "$SPACE/notes/t")"
echo "$rend" | grep -qi 'content-type: text/html' && ok "WP-R1  clean URL renders to HTML" || no "WP-R1  clean URL renders to HTML"
echo "$rend" | grep -qi 'content-location:' && ok "WP-R1  Content-Location names the source" || no "WP-R1  Content-Location set"

# WP-C2/C3  conditional write (stale If-Match -> 412)
etag="$(curl -s -D - -o /dev/null "$SPACE/notes/t.md" | grep -i '^etag:' | tr -d '\r' | awk '{print $2}')"
c="$(code "$SPACE/notes/t.md" "${A[@]}" -X PUT --data-binary 'x' -H 'If-Match: "stale-nope"')"
[ "$c" = "412" ] && ok "WP-C2  stale If-Match → 412" || no "WP-C2  stale If-Match → 412" "got $c"
c="$(code "$SPACE/notes/t.md" "${A[@]}" -X PUT --data-binary 'x' -H "If-Match: $etag")"
[ "$c" = "200" ] && ok "WP-C3  fresh If-Match → 200" || no "WP-C3  fresh If-Match → 200" "got $c"

# WP-V1/V2  default private + no existence leak
curl -s -o /dev/null "${A[@]}" -X PUT --data-binary 'secret' "$SPACE/p.md"   # no ?visibility
vis="$(curl -s "${A[@]}" "$SPACE/p.md?meta" | jget "['visibility']")"
[ "$vis" = "private" ] && ok "WP-V1  new files default to private" || no "WP-V1  default private" "got $vis"
priv="$(code "$SPACE/p.md")"; miss="$(code "$SPACE/does-not-exist-xyz.md")"
[ "$priv" = "404" ] && [ "$miss" = "404" ] && ok "WP-V2  private file 404s like a missing one (no leak)" \
  || no "WP-V2  private==missing (no leak)" "private=$priv missing=$miss"

# WP-X1/X2  llms.txt synthesized, lean, read-only
lt="$(curl -s "$SPACE/llms.txt")"; bytes="$(printf %s "$lt" | wc -c | tr -d ' ')"
{ [ "$bytes" -gt 0 ] && [ "$bytes" -le 1500 ] && echo "$lt" | grep -q "$SHOST"; } \
  && ok "WP-X1  llms.txt ≤1500B, names the host ($bytes B)" || no "WP-X1  llms.txt lean+host" "$bytes B"
c="$(code "$SPACE/llms.txt" "${A[@]}" -X PUT --data-binary 'x')"
[ "$c" = "405" ] && ok "WP-X2  llms.txt is read-only (405)" || no "WP-X2  llms.txt read-only" "got $c"

# WP-E1  errors are problem+json
ec="$(curl -s -D - -o /dev/null "$SPACE/nope-404.md")"
echo "$ec" | grep -qi 'content-type: application/problem+json' && ok "WP-E1  errors are application/problem+json" || no "WP-E1  problem+json"

# WP-T1  ?token= responses are no-store
ns="$(curl -s -D - -o /dev/null "$SPACE/p.md?token=$TOKEN" | grep -i '^cache-control:')"
echo "$ns" | grep -qi 'no-store' && ok "WP-T1  ?token= response is private, no-store" || no "WP-T1  no-store" "$ns"

echo "------------------------------------------------------------"
printf 'L1 Core: \033[32m%d passed\033[0m, \033[31m%d failed\033[0m  (space torn down on exit)\n' "$PASS" "$FAIL"
[ "$FAIL" -eq 0 ]
