#!/usr/bin/env bash
#MISE description="Stress-test the release:chronicle slicing logic against 30+ edge cases (boundary mtimes, .jsonl vs .jsonl.br, subagent recursion, hidden files, corrupted brotli, future timestamps, empty files, multi-GB simulation, malformed lines). No network, no upload — pure slicing assertion."
set -uo pipefail

# Locate the chronicle script we're testing.
# Iter-138 relocation: this test moved from .mise/tasks/release/test-chronicle-slicing
# to .mise/tasks/tests/test-chronicle-slicing-*.sh so the iter-50/iter-75
# marketplace-hook-regression-suite auto-discovers it (absorbs what was the
# sequential Check 4d 670ms cost into Check 4e's xargs -P parallel batch where
# it now runs concurrent with the ~1135ms longest test — net wall-clock save
# ~670ms on preflight). The chronicle script remained at its original
# .mise/tasks/release/chronicle canonical home (it's the release-phase chronicle
# producer, not a test artifact), so this test now reaches up one dir + over to
# release/ for the script under test.
CHRONICLE_SCRIPT="$(dirname "$0")/../release/chronicle"
if [[ ! -x "$CHRONICLE_SCRIPT" ]]; then
    echo "✗ chronicle script not found or not executable: $CHRONICLE_SCRIPT"
    exit 1
fi

# Test sandbox.
SANDBOX=$(mktemp -d -t chronicle-stress-XXXXXX)
TEST_HOME="$SANDBOX/home"
TEST_REPO="$SANDBOX/repo"
mkdir -p "$TEST_HOME/.claude/projects"

# The chronicle script encodes $PWD as project dir name, replacing / with -.
# We need to replicate that AND keep the path absolute (Claude Code does the same).
ENCODED_REPO_PATH="$(echo "$TEST_REPO" | tr '/' '-')"
PROJ_DIR="$TEST_HOME/.claude/projects/$ENCODED_REPO_PATH"
mkdir -p "$PROJ_DIR"

# Set up a fake git repo with tags at known dates.
# iter-48 SC2164: cd-without-exit-check defensive consistency. If cd fails
# (TEST_REPO somehow not creatable / not accessible), subsequent git
# commands would run in the WRONG directory and contaminate the parent.
mkdir -p "$TEST_REPO"
cd "$TEST_REPO" || { echo "FATAL: cannot cd into $TEST_REPO" >&2; exit 1; }
git init -q -b main
git config user.email "stress@test"
git config user.name "Stress Test"
echo "v1" > README.md
git add . && git commit -q -m "initial"

# Tag v1.0.0 with a commit dated 2026-04-01 12:00 UTC.
# We trust git's parsing (always UTC) over BSD `date -j` which is locale-flaky.
PREV_TAG_DATE="2026-04-01T12:00:00Z"
GIT_COMMITTER_DATE="$PREV_TAG_DATE" GIT_AUTHOR_DATE="$PREV_TAG_DATE" git commit -q --allow-empty -m "tag commit"
git tag v1.0.0 HEAD
# Read the canonical UTC epoch back from git — same source the chronicle uses.
PREV_TAG_EPOCH=$(git log -1 --format=%ct v1.0.0)

echo "v2" > README.md
git add . && git commit -q -m "post-tag work"

# Helpers ───────────────────────────────────────────────────────────────────

# Create a JSONL file at exact mtime (epoch seconds).
mkjsonl() {
    local path="$1"
    local epoch="$2"
    local content="${3:-{\"sessionId\":\"$(basename "$path" .jsonl)\",\"type\":\"user\"\}}"
    mkdir -p "$(dirname "$path")"
    echo "$content" > "$path"
    # BSD stat -t format
    touch -t "$(date -r "$epoch" +%Y%m%d%H%M.%S 2>/dev/null || date -d "@$epoch" +%Y%m%d%H%M.%S)" "$path"
}

# Create a brotli-compressed .jsonl.br file at exact mtime.
mkbr() {
    local path="$1"  # ends in .jsonl.br
    local epoch="$2"
    local content="${3:-{\"sessionId\":\"compressed\",\"type\":\"user\"\}}"
    local raw="${path%.br}"
    mkdir -p "$(dirname "$path")"
    echo "$content" > "$raw"
    if command -v brotli >/dev/null 2>&1; then
        brotli "$raw" -o "$path"
        rm -f "$raw"
    else
        # Fallback: rename without compression so test still runs structure-wise
        mv "$raw" "$path"
    fi
    touch -t "$(date -r "$epoch" +%Y%m%d%H%M.%S 2>/dev/null || date -d "@$epoch" +%Y%m%d%H%M.%S)" "$path"
}

# Edge-case fixtures ────────────────────────────────────────────────────────

# Time landmarks (relative to PREV_TAG_EPOCH = 2026-04-01 12:00 UTC).
T_WAY_BEFORE=$((PREV_TAG_EPOCH - 7 * 86400))    # 1 week before
T_DAY_BEFORE=$((PREV_TAG_EPOCH - 86400))        # 1 day before
T_HOUR_BEFORE=$((PREV_TAG_EPOCH - 3600))        # 1 hour before
T_MIN_BEFORE=$((PREV_TAG_EPOCH - 60))           # 1 minute before
T_BOUNDARY=$PREV_TAG_EPOCH                       # exactly at boundary
T_MIN_AFTER=$((PREV_TAG_EPOCH + 60))            # 1 minute after
T_HOUR_AFTER=$((PREV_TAG_EPOCH + 3600))         # 1 hour after
T_DAY_AFTER=$((PREV_TAG_EPOCH + 86400))         # 1 day after
T_NOW=$(date +%s)
T_FUTURE=$((T_NOW + 86400))                     # 1 day in the future (clock-skew sim)

# 1. Should be EXCLUDED (mtime < SINCE_EPOCH)
mkjsonl "$PROJ_DIR/exclude-week-before.jsonl"  $T_WAY_BEFORE
mkjsonl "$PROJ_DIR/exclude-day-before.jsonl"   $T_DAY_BEFORE
mkjsonl "$PROJ_DIR/exclude-hour-before.jsonl"  $T_HOUR_BEFORE
mkjsonl "$PROJ_DIR/exclude-min-before.jsonl"   $T_MIN_BEFORE

# 2. Boundary case (mtime == SINCE_EPOCH). With `find -newer`, this is excluded
#    (find -newer is strict-greater). We document and assert this behavior.
mkjsonl "$PROJ_DIR/boundary-equal.jsonl"       $T_BOUNDARY

# 3. Should be INCLUDED (mtime > SINCE_EPOCH)
mkjsonl "$PROJ_DIR/include-min-after.jsonl"    $T_MIN_AFTER
mkjsonl "$PROJ_DIR/include-hour-after.jsonl"   $T_HOUR_AFTER
mkjsonl "$PROJ_DIR/include-day-after.jsonl"    $T_DAY_AFTER
mkjsonl "$PROJ_DIR/include-now.jsonl"          $T_NOW
mkjsonl "$PROJ_DIR/include-future.jsonl"       $T_FUTURE

# 4. Subagent recursion (Claude Code stores subagent transcripts in subdirs)
mkjsonl "$PROJ_DIR/include-day-after/subagents/sub-1.jsonl" $T_DAY_AFTER
mkjsonl "$PROJ_DIR/include-day-after/subagents/sub-2-old.jsonl" $T_DAY_BEFORE  # subagent in window-touched dir but its own mtime is before
mkjsonl "$PROJ_DIR/exclude-day-before/subagents/sub-3.jsonl" $T_DAY_BEFORE     # subagent of an excluded session

# 5. .jsonl.br (compressed older sessions)
mkbr "$PROJ_DIR/include-br-after.jsonl.br"     $T_HOUR_AFTER
mkbr "$PROJ_DIR/exclude-br-before.jsonl.br"    $T_DAY_BEFORE

# 6. Empty / zero-byte JSONL — must still be picked if in window
mkjsonl "$PROJ_DIR/include-empty.jsonl"        $T_HOUR_AFTER ""
# iter-48 SC2188: redirection-without-command is intentional but unconventional;
# `: > file` (colon-noop redirect) makes the intent explicit and silences the
# warning. Both forms produce a zero-byte file.
: > "$PROJ_DIR/include-empty.jsonl"  # truly empty (: is no-op)
touch -t "$(date -r $T_HOUR_AFTER +%Y%m%d%H%M.%S 2>/dev/null || date -d "@$T_HOUR_AFTER" +%Y%m%d%H%M.%S)" "$PROJ_DIR/include-empty.jsonl"

# 7. Single-line giant (no trailing newline)
printf '{"sessionId":"giant","prompt":"%s"}' "$(printf 'x%.0s' {1..50000})" > "$PROJ_DIR/include-giant.jsonl"
touch -t "$(date -r $T_HOUR_AFTER +%Y%m%d%H%M.%S 2>/dev/null || date -d "@$T_HOUR_AFTER" +%Y%m%d%H%M.%S)" "$PROJ_DIR/include-giant.jsonl"

# 8. Malformed line mixed with valid (sanitizer must not crash)
cat > "$PROJ_DIR/include-malformed.jsonl" <<'EOF'
{"valid":1}
this is not json
{"valid":2}
EOF
touch -t "$(date -r $T_HOUR_AFTER +%Y%m%d%H%M.%S 2>/dev/null || date -d "@$T_HOUR_AFTER" +%Y%m%d%H%M.%S)" "$PROJ_DIR/include-malformed.jsonl"

# 9. Hidden cache file — must NOT be picked (not .jsonl)
mkjsonl "$PROJ_DIR/.session-chain-cache.json"  $T_HOUR_AFTER

# 10. Backup/swap files — must NOT be picked
mkjsonl "$PROJ_DIR/.swap-file.jsonl.swp"        $T_HOUR_AFTER
mkjsonl "$PROJ_DIR/backup.jsonl.bak"            $T_HOUR_AFTER

# 11. Corrupted .br (decompression should fail gracefully)
echo "not real brotli content" > "$PROJ_DIR/include-corrupt-br.jsonl.br"
touch -t "$(date -r $T_HOUR_AFTER +%Y%m%d%H%M.%S 2>/dev/null || date -d "@$T_HOUR_AFTER" +%Y%m%d%H%M.%S)" "$PROJ_DIR/include-corrupt-br.jsonl.br"

# 12. Symlink — find should follow it (mtime of target controls)
real_target="$PROJ_DIR/_real-target.jsonl"
mkjsonl "$real_target" $T_HOUR_AFTER
ln -s "$real_target" "$PROJ_DIR/include-symlink.jsonl"

# Run the slicing portion ───────────────────────────────────────────────────

# Replicate the chronicle script's find logic in isolation to get a clean
# answer — we want to assert WHAT IS PICKED, not the upload step.
SINCE_REF="$SANDBOX/.since.ref"
touch -t "$(date -r "$PREV_TAG_EPOCH" '+%Y%m%d%H%M.%S')" "$SINCE_REF"

PICKED=$(find "$PROJ_DIR" -type f \( -name '*.jsonl' -o -name '*.jsonl.br' \) -newer "$SINCE_REF" 2>/dev/null | sort)
PICKED_COUNT=$(echo "$PICKED" | grep -c . || echo 0)

# Assertions ────────────────────────────────────────────────────────────────

PASS=0
FAIL=0
fail_list=()
ok()   { PASS=$((PASS + 1)); echo "  ✓ $1"; }
fail() { FAIL=$((FAIL + 1)); echo "  ✗ $1"; fail_list+=("$1"); }

# Helper: assert path is in $PICKED
assert_picked() {
    local pat="$1"
    local desc="$2"
    if echo "$PICKED" | grep -q "$pat"; then ok "$desc"; else fail "$desc :: expected $pat in PICKED"; fi
}
assert_not_picked() {
    local pat="$1"
    local desc="$2"
    if echo "$PICKED" | grep -q "$pat"; then fail "$desc :: $pat should NOT be picked but was"; else ok "$desc"; fi
}

echo
echo "═══════════════════════════════════════════════════════════"
echo "  Chronicle slicing: 30+ edge cases"
echo "═══════════════════════════════════════════════════════════"
echo
echo "Boundary mtime inclusion ────────────────────────────────"
assert_not_picked  "exclude-week-before.jsonl"       "T-7d  EXCLUDED"
assert_not_picked  "exclude-day-before.jsonl"        "T-1d  EXCLUDED"
assert_not_picked  "exclude-hour-before.jsonl"       "T-1h  EXCLUDED"
assert_not_picked  "exclude-min-before.jsonl"        "T-1m  EXCLUDED"
assert_not_picked  "boundary-equal.jsonl"            "T==boundary EXCLUDED (find -newer is strict)"
assert_picked      "include-min-after.jsonl"         "T+1m  INCLUDED"
assert_picked      "include-hour-after.jsonl"        "T+1h  INCLUDED"
assert_picked      "include-day-after.jsonl"         "T+1d  INCLUDED"
assert_picked      "include-now.jsonl"               "T=now INCLUDED"
assert_picked      "include-future.jsonl"            "T=now+1d (future, clock skew) INCLUDED"

echo
echo "Subagent recursion ──────────────────────────────────────"
assert_picked      "include-day-after/subagents/sub-1.jsonl"      "subagent in window  INCLUDED (recursion works)"
assert_not_picked  "include-day-after/subagents/sub-2-old.jsonl"  "subagent w/ stale mtime (in newer dir) EXCLUDED"
assert_not_picked  "exclude-day-before/subagents/sub-3.jsonl"     "subagent of excluded session EXCLUDED"

echo
echo "Compressed .jsonl.br ────────────────────────────────────"
assert_picked      "include-br-after.jsonl.br"       "T+1h .br INCLUDED"
assert_not_picked  "exclude-br-before.jsonl.br"      "T-1d .br EXCLUDED"

echo
echo "Content edges (must still be picked if in window) ──────"
assert_picked      "include-empty.jsonl"             "empty 0-byte .jsonl INCLUDED"
assert_picked      "include-giant.jsonl"             "single-line giant INCLUDED"
assert_picked      "include-malformed.jsonl"         "malformed-mixed JSONL INCLUDED"
assert_picked      "include-corrupt-br.jsonl.br"     "corrupted .br INCLUDED (sliced; decompression handled separately)"
# Symlink semantics: `find -type f` correctly skips symlinks themselves and
# matches the underlying regular-file target. So the symlink path is NOT in
# PICKED, but the real target IS. Real-world Claude Code never creates
# symlinks here — this just documents the no-surprise behavior.
assert_not_picked  "include-symlink.jsonl"           "symlink itself NOT picked (-type f filters)"
assert_picked      "_real-target.jsonl"              "symlink target IS picked (real file in window)"

echo
echo "Should NEVER match (extension filter) ──────────────────"
assert_not_picked  ".session-chain-cache.json"       "non-jsonl cache EXCLUDED"
assert_not_picked  ".swap-file.jsonl.swp"            ".jsonl.swp EXCLUDED"
assert_not_picked  "backup.jsonl.bak"                ".jsonl.bak EXCLUDED"

echo
echo "Brotli decompression edge cases ─────────────────────────"
# Now actually run the decompression branch in isolation.
DECOMP_DIR="$SANDBOX/decomp"
mkdir -p "$DECOMP_DIR"

# A real .jsonl.br round-trip
if command -v brotli >/dev/null 2>&1; then
    if brotli -d -c "$PROJ_DIR/include-br-after.jsonl.br" > "$DECOMP_DIR/ok.jsonl" 2>/dev/null; then
        ok "valid .br round-trip decompresses cleanly"
    else
        fail "valid .br failed to decompress"
    fi

    # Corrupted .br must NOT crash chronicle (script's loop ignores brotli errors via `&&`)
    if brotli -d -c "$PROJ_DIR/include-corrupt-br.jsonl.br" > "$DECOMP_DIR/bad.jsonl" 2>/dev/null; then
        fail "corrupted .br unexpectedly decompressed"
    else
        ok "corrupted .br fails brotli (chronicle loop catches with && and skips)"
    fi
else
    fail "brotli command not installed (skipping .br decompression tests)"
fi

echo
echo "Date-window calculation ────────────────────────────────"

# Replicate the chronicle script's tag-resolution logic to verify it picks
# the right SINCE_EPOCH on different git states.

# Case A: HEAD has a tag (release:version flow). describe --exact-match wins.
GIT_COMMITTER_DATE="2026-04-15T10:00:00Z" GIT_AUTHOR_DATE="2026-04-15T10:00:00Z" \
    git -C "$TEST_REPO" commit -q --allow-empty -m "v2.0.0 commit"
git -C "$TEST_REPO" tag v2.0.0 HEAD
RESOLVED_TAG=$(git -C "$TEST_REPO" describe --tags --exact-match HEAD 2>/dev/null)
[[ "$RESOLVED_TAG" == "v2.0.0" ]] && ok "tag at HEAD detected: $RESOLVED_TAG" || fail "tag at HEAD: got '$RESOLVED_TAG'"

# Case B: PREV_TAG resolution skips the new one and takes the next-newest.
PREV=$(git -C "$TEST_REPO" tag --sort=-creatordate --merged HEAD | grep -v "^v2.0.0$" | head -1)
[[ "$PREV" == "v1.0.0" ]] && ok "PREV_TAG resolution skips new tag, picks v1.0.0" || fail "PREV_TAG: got '$PREV'"

# Case C: SINCE_EPOCH is taken from PREV tag's COMMIT date, not tag-create date.
SINCE=$(git -C "$TEST_REPO" log -1 --format=%ct v1.0.0)
[[ "$SINCE" == "$PREV_TAG_EPOCH" ]] && ok "SINCE_EPOCH = v1.0.0 commit date ($SINCE)" || fail "SINCE_EPOCH wrong: got $SINCE expected $PREV_TAG_EPOCH"

# Case D: First-release fallback (no prior tag).
FRESH_REPO="$SANDBOX/fresh"
# iter-48 SC2164: cd-without-exit-check defensive consistency
mkdir -p "$FRESH_REPO" && cd "$FRESH_REPO" || { echo "FATAL: cannot cd into $FRESH_REPO" >&2; exit 1; }
git init -q -b main
git -c user.email=x -c user.name=x commit -q --allow-empty -m "first commit"
git tag v1.0.0 HEAD
NO_PREV=$(git tag --sort=-creatordate --merged HEAD | grep -v "^v1.0.0$" | head -1 || echo "")
if [[ -z "$NO_PREV" ]]; then
    ok "first-release: no prior tag → fallback to 7-day window kicks in"
else
    fail "first-release: unexpectedly found prior tag '$NO_PREV'"
fi

# iter-48 SC2164: cd-without-exit-check defensive consistency.
# Returning to TEST_REPO for the next test case.
cd "$TEST_REPO" || { echo "FATAL: cannot cd back into $TEST_REPO" >&2; exit 1; }

echo
echo "Visibility gate (parser only — no live API) ────────────"

# The chronicle script parses ssh-aliased and standard remote URLs.
# Re-implement the parser locally and assert against known forms.
parse() {
    local url="${1%.git}"
    echo "$url" | awk -F'[:/]' '{print $(NF-1)"/"$NF}'
}
[[ "$(parse 'git@github.com:owner/repo.git')"            == "owner/repo" ]]            && ok "standard SSH URL parses"          || fail "standard SSH URL parser broken"
[[ "$(parse 'git@github.com-alias:owner/repo.git')"      == "owner/repo" ]]            && ok "ssh-aliased host parses"          || fail "ssh-aliased host parser broken"
[[ "$(parse 'https://github.com/owner/repo.git')"        == "owner/repo" ]]            && ok "https URL parses"                 || fail "https URL parser broken"
[[ "$(parse 'git@github.com:Eon-Labs/data-provider.git')" == "Eon-Labs/data-provider" ]] && ok "Eon-Labs org URL parses"        || fail "Eon-Labs URL parser broken"
[[ "$(parse 'https://github.com/owner/repo')"            == "owner/repo" ]]            && ok "URL without .git suffix parses"   || fail "no-.git suffix parser broken"

echo
echo "Find strategy assertions ───────────────────────────────"
# Total picked count: 5 explicit includes + 1 subagent + 1 .br + 4 content-edge + 1 symlink target + 1 symlink itself + 1 corrupt-br = up to 14
# (The exact number depends on whether find counts the symlink as one or two entries.)
# We assert ≥ 11 picked (the firm includes), ≤ 16 (upper bound w/ symlink dup).
if [[ $PICKED_COUNT -ge 11 ]]; then
    ok "PICKED count ≥ 11 (got $PICKED_COUNT)"
else
    fail "PICKED count too low: $PICKED_COUNT"
fi

# No file from the EXCLUDED region should be in the slice.
EXCL_LEAK=$(echo "$PICKED" | grep -E 'exclude-(week|day|hour|min)-before|exclude-br-before|sub-2-old|sub-3' | wc -l | tr -d ' ')
if [[ $EXCL_LEAK -eq 0 ]]; then
    ok "zero leaks from EXCLUDED region"
else
    fail "$EXCL_LEAK file(s) from EXCLUDED region leaked into PICKED"
fi

# Summary ──────────────────────────────────────────────────────────────────

echo
echo "═══════════════════════════════════════════════════════════"
echo "  Summary: $PASS passed, $FAIL failed"
echo "═══════════════════════════════════════════════════════════"
echo

if [[ $FAIL -gt 0 ]]; then
    echo "Failures:"
    for f in "${fail_list[@]}"; do echo "  - $f"; done
    echo
    echo "PICKED set was:"
    echo "$PICKED" | sed 's|^|    |'
    rm -rf "$SANDBOX"
    exit 1
fi

rm -rf "$SANDBOX"
exit 0
