#!/usr/bin/env bash
# witness_sweep_cron.sh — scheduled witness-divergence collection
#
# Runs `make bench-witness-sweep` followed by `make bench-witness-divergence`
# and (optionally) commits any newly-extracted divergence fixtures to the
# arborist repo. Designed to grow the calibration-data corpus over time
# without operator intervention.
#
# Cron usage (recommended every 6 hours; LLM costs scale with frequency):
#
#     0 */6 * * * cd /home/fox/git/arborist && \
#         bench/scripts/witness_sweep_cron.sh \
#             --commit --branch main >> /var/log/arborist-witness.log 2>&1
#
# Invariants:
#   - Never commits unless --commit AND fixture file actually changed
#   - Skips push by default (operator runs `git push` separately)
#   - Records sweep result + extraction count + commit sha in audit log
#   - Fail-closed: pre-commit hook failure → no auto-push, no silent loss

set -euo pipefail

# ---------------------------------------------------------------------------
# Defaults
# ---------------------------------------------------------------------------
COMMIT=0
PUSH=0
BRANCH="main"
LOG_PREFIX="witness-sweep"
SHARDS_DIR="${ARBORIST_SHARDS_DIR:-$HOME/.arborist/shards}"
FIXTURE_PATH="bench/fixtures/5f/falsification-witness-v1.jsonl"
SWEEP_OUT="bench/results/witness-sweep.json"

# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------
while [[ $# -gt 0 ]]; do
    case "$1" in
        --commit) COMMIT=1; shift ;;
        --push)   PUSH=1; shift ;;
        --branch) BRANCH="$2"; shift 2 ;;
        --shards) SHARDS_DIR="$2"; shift 2 ;;
        --help|-h)
            cat <<'EOF'
Usage: witness_sweep_cron.sh [--commit] [--push] [--branch NAME] [--shards DIR]

  --commit       commit any new divergence fixtures (default: dry-run)
  --push         push the commit (requires --commit; default: skip)
  --branch NAME  branch to commit on (default: main)
  --shards DIR   shards-dir override (default: $ARBORIST_SHARDS_DIR or ~/.arborist/shards)

Environment:
  ARBORIST_SHARDS_DIR — fallback if --shards not given.
  ARBORIST_LLM_ENDPOINT / ARBORIST_LLM_MODEL — passthrough to witness sweep.

Output:
  Stdout: human-readable timestamped log per phase.
  Bench artifacts: bench/results/witness-sweep.json (latest sweep);
                   bench/fixtures/5f/falsification-witness-v1.jsonl.
EOF
            exit 0
            ;;
        *) echo "$LOG_PREFIX: unknown arg: $1" >&2; exit 2 ;;
    esac
done

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
log() { printf "[%s %s] %s\n" "$LOG_PREFIX" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*"; }

require_cmd() {
    command -v "$1" >/dev/null 2>&1 || {
        log "missing required command: $1"; exit 2;
    }
}

require_cmd git
require_cmd make

# Must run from repo root (Makefile + .git both expected here)
[[ -f Makefile ]] || { log "must run from arborist repo root"; exit 2; }
[[ -d .git ]] || { log "not a git repo"; exit 2; }

[[ -d "$SHARDS_DIR" ]] || {
    log "shards-dir missing: $SHARDS_DIR"; exit 2;
}

# ---------------------------------------------------------------------------
# Phase 1 — sweep
# ---------------------------------------------------------------------------
log "phase 1 — bench-witness-sweep against $SHARDS_DIR"
ARBORIST_SHARDS_DIR="$SHARDS_DIR" make bench-witness-sweep
SWEEP_DIVERGENCE=$(python3 -c "
import json
d = json.load(open('$SWEEP_OUT'))
print(d['summary']['divergence_count'])
")
log "phase 1 done — sweep recorded $SWEEP_DIVERGENCE divergences this run"

# ---------------------------------------------------------------------------
# Phase 2 — extract divergence as 5F fixtures
# ---------------------------------------------------------------------------
log "phase 2 — extract divergence audit events as 5F fixtures"
make bench-witness-divergence
EXTRACT_COUNT=$(python3 -c "
import json
lines = [l for l in open('$FIXTURE_PATH').read().splitlines() if l.strip()]
# subtract meta line
print(max(0, len(lines) - 1))
")
log "phase 2 done — fixture file holds $EXTRACT_COUNT cumulative divergence rows"

# ---------------------------------------------------------------------------
# Phase 3 — commit if --commit AND something changed
# ---------------------------------------------------------------------------
if [[ "$COMMIT" -ne 1 ]]; then
    log "phase 3 — skipped (no --commit; dry-run)"
    log "DONE"
    exit 0
fi

if git diff --quiet "$FIXTURE_PATH" "$SWEEP_OUT" 2>/dev/null; then
    log "phase 3 — fixture + sweep unchanged; nothing to commit"
    log "DONE"
    exit 0
fi

# Make sure we're on the requested branch.
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "$CURRENT_BRANCH" != "$BRANCH" ]]; then
    log "WARNING: on branch $CURRENT_BRANCH, expected $BRANCH; not committing"
    exit 1
fi

# Stage + commit. Pre-commit hooks must succeed; if they fail the commit
# is aborted and we exit non-zero (cron mail will show the failure).
git add "$FIXTURE_PATH" "$SWEEP_OUT"
git commit -m "witness sweep auto-update: $SWEEP_DIVERGENCE divergence(s) this run

Extracted by witness_sweep_cron.sh — $(date -u +%Y-%m-%dT%H:%M:%SZ)
Divergence count this run: $SWEEP_DIVERGENCE
Total fixture rows: $EXTRACT_COUNT"

NEW_SHA=$(git rev-parse HEAD)
log "phase 3 done — committed $NEW_SHA"

if [[ "$PUSH" -eq 1 ]]; then
    log "phase 4 — pushing to origin/$BRANCH"
    git push origin "$BRANCH"
    log "phase 4 done"
else
    log "phase 4 — skipped (no --push); operator runs git push manually"
fi

log "DONE"
