#!/usr/bin/env bash
# Regression harness for board-roll.sh — rolling oldest LIVE-board entries to LEDGER.
#
# Asserts:
#   1. Under cap → no-op, exit 0, files unchanged.
#   2. Over cap, no roll markers → exit 3, LIVE unchanged (never guesses).
#   3. Over cap, markers present → rolls the fewest oldest entries to get under cap,
#      LIVE ends under cap, pinned preamble/footer + newest entries preserved.
#   4. Rolled blocks land in the LEDGER verbatim, oldest set in original order.
#   5. --dry-run changes nothing and reports a plan.
#   6. Zone emptied but pinned sections alone exceed cap → exit 3.
#   7. --help exits 0 and prints usage; an unknown flag exits nonzero (#701 discipline).
#   8. More than one marker pair → exit 3, unchanged; curated content between the two
#      zones is never relocated to the LEDGER (rev0 #868 regression).

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SUT="$SCRIPT_DIR/board-roll.sh"

fail=0
note() { echo "FAIL: $*" >&2; fail=1; }

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

# builds a LIVE board: pinned preamble + roll zone with N dated entries (newest first),
# each entry padded to be individually large so the cap math is predictable.
make_board() {  # $1 file  $2 n_entries  $3 with_markers(1/0)  $4 pad_bytes
    local f=$1 n=$2 markers=$3 pad=$4 i padtxt
    padtxt=$(head -c "$pad" < /dev/zero | tr '\0' 'x')
    {
        echo "# BOARD — LIVE"
        echo "> pinned protocol blockquote, never rolled."
        echo
        echo "## Curated always-current section (pinned)"
        echo "- this stays no matter what"
        echo
        [[ "$markers" == 1 ]] && echo '<!-- BOARD-ROLL:START -->'
        # newest first (i=n .. 1); oldest (i=1) ends at the bottom
        for (( i=n; i>=1; i-- )); do
            echo "### 2026-07-$(printf '%02d' $i) tick number $i"
            echo "- detail $i $padtxt"
            echo
        done
        [[ "$markers" == 1 ]] && echo '<!-- BOARD-ROLL:END -->'
    } > "$f"
    return 0
}

# ── 1. under cap → no-op ───────────────────────────────────────────────────────
L="$WORK/live1.md"; G="$WORK/ledger1.md"; : > "$G"
make_board "$L" 2 1 10
before=$(cat "$L")
if ! out=$(bash "$SUT" --live "$L" --ledger "$G" --cap 100000 2>&1); then
    note "under-cap should exit 0 (got nonzero): $out"
fi
[[ "$(cat "$L")" == "$before" ]] || note "under-cap modified LIVE"
[[ -s "$G" ]] && note "under-cap wrote to LEDGER"

# ── 2. over cap, no markers → exit 3, unchanged ────────────────────────────────
L="$WORK/live2.md"; G="$WORK/ledger2.md"; : > "$G"
make_board "$L" 6 0 400
before=$(cat "$L")
set +e; bash "$SUT" --live "$L" --ledger "$G" --cap 800 >/dev/null 2>&1; rc=$?; set -e
[[ "$rc" -eq 3 ]] || note "no-markers over-cap should exit 3 (got $rc)"
[[ "$(cat "$L")" == "$before" ]] || note "no-markers run modified LIVE (must never guess)"

# ── 3+4. over cap with markers → rolls oldest, LIVE under cap, LEDGER gets them ─
L="$WORK/live3.md"; G="$WORK/ledger3.md"; echo "# LEDGER" > "$G"
make_board "$L" 6 1 400          # 6 entries, each ~>400B
big=$(wc -c < "$L")
[[ "$big" -ge 2000 ]] || note "fixture too small to test rolling ($big B)"
if ! out=$(bash "$SUT" --live "$L" --ledger "$G" --cap 2000 2>&1); then
    note "marker roll should exit 0 when it can get under cap: $out"
fi
after=$(wc -c < "$L")
[[ "$after" -lt 2000 ]] || note "LIVE still >= cap after roll ($after B)"
# pinned content survives
grep -q "Curated always-current section" "$L" || note "roll dropped pinned section"
grep -q 'BOARD-ROLL:START' "$L" || note "roll dropped START marker"
grep -q 'BOARD-ROLL:END' "$L"   || note "roll dropped END marker"
# newest entry (07-06) stays; oldest (07-01) is the first to leave
grep -q "### 2026-07-06 tick number 6" "$L" || note "roll dropped the newest entry"
grep -q "### 2026-07-01 tick number 1" "$L" && note "oldest entry not rolled out of LIVE"
# oldest went to LEDGER
grep -q "### 2026-07-01 tick number 1" "$G" || note "oldest entry not appended to LEDGER"
grep -q "board-roll:.*rolled from live3.md" "$G" || note "LEDGER missing provenance separator"
# a rolled entry must not be duplicated (present in exactly one of LIVE/LEDGER)
if grep -q "### 2026-07-01 tick number 1" "$L"; then note "rolled entry duplicated in LIVE"; fi
# LEDGER original content preserved
grep -q "^# LEDGER" "$G" || note "roll clobbered existing LEDGER content"

# ── 5. --dry-run changes nothing ───────────────────────────────────────────────
L="$WORK/live5.md"; G="$WORK/ledger5.md"; echo "# LEDGER" > "$G"
make_board "$L" 6 1 400
before_l=$(cat "$L"); before_g=$(cat "$G")
out=$(bash "$SUT" --live "$L" --ledger "$G" --cap 2000 --dry-run 2>&1) || note "dry-run exited nonzero: $out"
grep -qi "dry run" <<<"$out" || note "dry-run did not announce itself"
grep -q "would roll" <<<"$out" || note "dry-run did not report a plan"
[[ "$(cat "$L")" == "$before_l" ]] || note "dry-run modified LIVE"
[[ "$(cat "$G")" == "$before_g" ]] || note "dry-run modified LEDGER"

# ── 6. zone emptied, pinned alone over cap → exit 3 ────────────────────────────
# cap 120 is below the pinned preamble+footer size (~180B), so even after rolling
# every zone entry the LIVE file stays over cap → must report the unsatisfiable case.
L="$WORK/live6.md"; G="$WORK/ledger6.md"; echo "# LEDGER" > "$G"
make_board "$L" 3 1 50
set +e; bash "$SUT" --live "$L" --ledger "$G" --cap 120 >/dev/null 2>&1; rc=$?; set -e
[[ "$rc" -eq 3 ]] || note "unsatisfiable cap should exit 3 (got $rc)"

# ── 7. help exits 0, unknown flag exits nonzero (#701) ─────────────────────────
if ! out=$(bash "$SUT" --help 2>&1); then note "--help exited nonzero"; fi
[[ "$out" == Usage:* ]] || note "--help did not print usage"
bash "$SUT" -h >/dev/null 2>&1 || note "-h exited nonzero"
if bash "$SUT" --not-a-real-flag >/dev/null 2>&1; then note "unknown flag was accepted"; fi
if bash "$SUT" --live "$WORK/live3.md" >/dev/null 2>&1; then note "missing --ledger was accepted"; fi

# ── 8. multiple marker pairs → exit 3, unchanged (no cross-zone relocation) ─────
# Two separately-marked zones with a curated pinned section BETWEEN them. A naive
# first-START..last-END span would sweep that curated section (and the intermediate
# markers) into the LEDGER. board-roll must refuse (exit 3) and touch nothing.
L="$WORK/live8.md"; G="$WORK/ledger8.md"; echo "# LEDGER" > "$G"
pad8=$(head -c 300 < /dev/zero | tr '\0' 'x')
{
    echo "# BOARD — LIVE"
    echo "> pinned protocol blockquote"
    echo
    echo '<!-- BOARD-ROLL:START -->'
    echo "### 2026-07-10 zone-A newest"
    echo "- detail A2 $pad8"
    echo "### 2026-07-09 zone-A oldest"
    echo "- detail A1 $pad8"
    echo '<!-- BOARD-ROLL:END -->'
    echo
    echo "## Curated-between-zones (pinned — must never move)"
    echo "- CANARY-BETWEEN keep me"
    echo
    echo '<!-- BOARD-ROLL:START -->'
    echo "### 2026-07-08 zone-B newest"
    echo "- detail B2 $pad8"
    echo "### 2026-07-07 zone-B oldest"
    echo "- detail B1 $pad8"
    echo '<!-- BOARD-ROLL:END -->'
} > "$L"
before8=$(cat "$L")
set +e; bash "$SUT" --live "$L" --ledger "$G" --cap 80 >/dev/null 2>&1; rc=$?; set -e
[[ "$rc" -eq 3 ]] || note "multi-pair board should exit 3 (got $rc)"
[[ "$(cat "$L")" == "$before8" ]] || note "multi-pair run modified LIVE (must never guess across zones)"
grep -q "CANARY-BETWEEN keep me" "$L" || note "multi-pair run relocated curated between-zones content"
grep -q "CANARY-BETWEEN" "$G" && note "curated between-zones content leaked into LEDGER"

if [[ "$fail" -eq 0 ]]; then
    echo "board-roll regression passed (8 groups)"
fi
exit "$fail"
