#!/usr/bin/env bash
# Fail loudly when the testnot.work demo goes thin.
#
# Phase 5 of wiki `testnot-demo-content-plan`. The failure this exists for:
# nine of eleven items went invisible on testnot and nothing said so for weeks.
# The seed's own tests passed the whole time, because tests/workflows/seed_examples.rs
# runs against InMemoryStorage where media always attaches and no project-level
# paywall hides anything. The failure lived exactly in the gap between the test
# and the box, so more unit tests cannot close it. This runs against the live
# site instead.
#
# It checks thinness, not 500s. Every page here can return a cheerful 200 while
# showing nothing, and that is the state it is looking for.
#
# Run it after every reseed (mnw-testnot-seed.sh) and before any capture run
# that photographs this box. The landing carousel will carry screenshots taken
# from here, so a silent reseed that empties the catalog also silently
# invalidates the marketing images.
#
#   ./mnw-testnot-smoke.sh              # check testnot.work
#   BASE=https://testnot.work ./mnw-testnot-smoke.sh
#
# Exit 0 = the demo is worth showing. Exit 1 = it is not; read the FAIL lines.
set -uo pipefail

BASE="${BASE:-https://testnot.work}"

# ── Sealed baselines ────────────────────────────────────────────────────────
#
# Same idea as tests/test_hygiene.rs: freeze what is true today and fail on a
# new violation, rather than demanding a clean sheet nobody will ever deliver.
# A number here may only move in the improving direction, and when it does, edit
# it. Never loosen one to make a run pass.

# Projects the demo must carry. Below this the catalog stops reading as a
# catalog and starts reading as a test fixture.
MIN_PROJECTS=5

# Items a visitor can actually see and click, summed across every storefront
# that is not paywalled. Twenty of twenty-five on 2026-08-07; the other five are
# behind the one deliberate subscription paywall (the Marginalia Reader).
#
# Was nine of eleven until the catalog was widened to five items per project, so
# that three-cards-in-a-five-wide-grid stopped reading as an empty shop.
MIN_VISIBLE_ITEMS=20

# Covers still served from the seed's generated placeholder (a 16x16 grayscale
# PNG, src/seed/media.rs). Zero as of 2026-08-07: src/seed/media-manifest.toml
# now names a real CC0 asset for every cover the seed attaches, so a placeholder
# appearing at all means either the manifest stopped resolving or storage did.
#
# It was sealed at five while the seed had never uploaded real art, on the
# reasoning that a check which is always red is a check nobody reads. That
# reason is spent: zero is now the true value, and the baseline may only move
# in the improving direction.
PLACEHOLDER_COVERS_BASELINE=0

# A cover at or under this is the placeholder or a broken upload, not artwork.
# The placeholder is 72 bytes; real cover art is orders of magnitude larger.
REAL_COVER_MIN_BYTES=2048

# A page under this rendered its chrome and nothing else.
MIN_PAGE_BYTES=3000

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

fails=0
warns=0
pass() { printf '  ok    %s\n' "$*"; }
fail() { printf '  FAIL  %s\n' "$*"; fails=$((fails + 1)); }
warn() { printf '  warn  %s\n' "$*"; warns=$((warns + 1)); }
section() { printf '\n== %s\n' "$*"; }

# Fetch $1 into $TMP/body, echo the status code.
get() {
	curl -sS --max-time 30 -o "$TMP/body" -w '%{http_code}' "$BASE$1" 2>/dev/null || echo 000
}

section "reachable"
code=$(get /)
if [ "$code" != "200" ]; then
	fail "GET / returned $code; nothing else below is meaningful"
	printf '\n%s\n' "smoke: FAILED ($fails)"
	exit 1
fi
pass "GET / -> 200"

# ── The catalog ─────────────────────────────────────────────────────────────
#
# Discover is the index and each storefront is the page it points at. Checking
# them against each other is the whole trick: either alone can look healthy
# while disagreeing with the other, which is how the paywall bug hid.

section "catalog"
code=$(get /discover)
[ "$code" = "200" ] || fail "GET /discover returned $code"

# Discover's project rows carry both the slug and the item count it advertises.
grep -oE '<a href="/p/[a-z0-9-]+" class="table-row project-row">|<span class="row-items">[0-9]+</span>' "$TMP/body" |
	sed -e 's/.*href="\/p\///' -e 's/" class.*//' -e 's/<span class="row-items">//' -e 's/<\/span>//' |
	paste - - >"$TMP/advertised" 2>/dev/null

n_projects=$(wc -l <"$TMP/advertised")
if [ "$n_projects" -lt "$MIN_PROJECTS" ]; then
	fail "discover lists $n_projects projects, floor is $MIN_PROJECTS"
else
	pass "discover lists $n_projects projects"
fi

visible_total=0
: >"$TMP/covers"

while read -r slug advertised; do
	[ -n "$slug" ] || continue
	code=$(get "/p/$slug")
	if [ "$code" != "200" ]; then
		fail "/p/$slug returned $code"
		continue
	fi

	rendered=$(grep -o 'class="item-card' "$TMP/body" | wc -l)
	grep -oE 'src="https://[^"]*/image/[^"]*"' "$TMP/body" | sed -e 's/src="//' -e 's/"$//' >>"$TMP/covers"

	if grep -q 'class="paywall-box"' "$TMP/body"; then
		# A paywalled storefront shows no items by construction: the handler
		# returns the paywall template before it reaches them. What it must
		# still do is state the count, or it contradicts the discover card that
		# sent the visitor here.
		if [ "$rendered" -ne 0 ]; then
			fail "/p/$slug is paywalled but rendered $rendered item cards"
		elif grep -qE "(^|>)[^<]*\b$advertised items? included" "$TMP/body"; then
			pass "/p/$slug paywalled, states its $advertised items"
		else
			fail "/p/$slug is paywalled and does not state its $advertised items; discover advertises them"
		fi
	else
		visible_total=$((visible_total + rendered))
		if [ "$rendered" -eq "$advertised" ]; then
			pass "/p/$slug renders $rendered items, matching discover"
		else
			fail "/p/$slug renders $rendered items, discover advertises $advertised"
		fi
	fi
done <"$TMP/advertised"

if [ "$visible_total" -lt "$MIN_VISIBLE_ITEMS" ]; then
	fail "$visible_total items visible across open storefronts, floor is $MIN_VISIBLE_ITEMS"
else
	pass "$visible_total items visible across open storefronts"
fi

# ── Covers ──────────────────────────────────────────────────────────────────
#
# A cover URL that 404s still renders as a 200 page with a broken image, which
# no status-code check sees. Fetch each one.

section "covers"
sort -u "$TMP/covers" >"$TMP/covers.uniq"
n_covers=$(wc -l <"$TMP/covers.uniq")
[ "$n_covers" -gt 0 ] || fail "no cover images referenced by any storefront"

placeholders=0
while read -r url; do
	[ -n "$url" ] || continue
	read -r ccode csize ctype <<<"$(curl -sS --max-time 30 -o /dev/null \
		-w '%{http_code} %{size_download} %{content_type}' "$url" 2>/dev/null || echo "000 0 -")"
	short="${url##*/projects/}"
	case "$ctype" in
	image/*) is_image=1 ;;
	*) is_image=0 ;;
	esac
	if [ "$ccode" != "200" ]; then
		fail "cover $short returned $ccode"
	elif [ "$is_image" -eq 0 ]; then
		fail "cover $short served as ${ctype:--}, not an image"
	elif [ "$csize" -lt "$REAL_COVER_MIN_BYTES" ]; then
		placeholders=$((placeholders + 1))
	fi
done <"$TMP/covers.uniq"

if [ "$placeholders" -gt "$PLACEHOLDER_COVERS_BASELINE" ]; then
	fail "$placeholders placeholder covers, sealed baseline is $PLACEHOLDER_COVERS_BASELINE (a real cover reverted)"
elif [ "$placeholders" -eq "$PLACEHOLDER_COVERS_BASELINE" ] && [ "$placeholders" -gt 0 ]; then
	warn "$placeholders of $n_covers covers are still the seed placeholder; at baseline, not a regression"
else
	pass "$placeholders placeholder covers, down from a baseline of $PLACEHOLDER_COVERS_BASELINE. Lower PLACEHOLDER_COVERS_BASELINE to $placeholders"
fi

# ── The surfaces that are not the catalog ───────────────────────────────────
#
# Phase 4's list. Each is a place a curious visitor lands, and each can go
# blank without anything else noticing.

section "other surfaces"
for path in /discover?mode=items /pricing /fan-plus /docs; do
	code=$(get "$path")
	size=$(wc -c <"$TMP/body")
	if [ "$code" != "200" ]; then
		fail "$path returned $code"
	elif [ "$size" -lt "$MIN_PAGE_BYTES" ]; then
		fail "$path returned 200 but only ${size}B, which is chrome and no content"
	else
		pass "$path -> 200, ${size}B"
	fi
done

section "blogs and feeds"
while read -r slug _; do
	[ -n "$slug" ] || continue
	code=$(get "/p/$slug/blog")
	[ "$code" = "200" ] || fail "/p/$slug/blog returned $code"

	# Note the path: the blog feed is feed.xml, not rss. `/p/{slug}/rss` is a
	# different feed, of the project's items.
	code=$(get "/p/$slug/blog/feed.xml")
	posts=$(grep -c '<item>' "$TMP/body" 2>/dev/null || echo 0)
	if [ "$code" != "200" ]; then
		fail "/p/$slug/blog/feed.xml returned $code"
	elif [ "$posts" -lt 1 ]; then
		fail "/p/$slug/blog/feed.xml is an empty feed"
	else
		pass "/p/$slug blog + feed ($posts posts)"
	fi
done <"$TMP/advertised"

# ── Deferred ────────────────────────────────────────────────────────────────

section "deferred"
code=$(get /library)
if [ "$code" = "401" ] || [ "$code" = "302" ] || [ "$code" = "303" ]; then
	warn "/library is $code to anonymous, as expected. Checking its contents needs the demo buyer, which the seed creates when TESTNOT_BUYER_PASSWORD is set (GoingsOn 839a8e5a, option B). The carousel capture is what exercises it"
else
	warn "/library returned $code to an anonymous visitor; expected an auth redirect. Worth a look"
fi

printf '\n'
if [ "$fails" -gt 0 ]; then
	printf 'smoke: FAILED (%d failures, %d warnings)\n' "$fails" "$warns"
	exit 1
fi
printf 'smoke: ok (%d warnings)\n' "$warns"
