#!/usr/bin/env bash
# Home-server wrapper for "Start AI pass" button on the eorganize app.
#
# Invoked over SSH from the VPS app container. Receives a session id as its
# single argument (or via SSH_ORIGINAL_COMMAND if the authorized_keys entry
# uses a forced command). Posts back to the VPS callback endpoint to update
# session status, then runs `claude -p` headless against the operating doc.
#
# Setup prerequisites (see docs/OPERATING.md "Triggering AI pass from the
# app" for the full list): repo cloned at $REPO_DIR, `claude` CLI logged in
# once interactively, ffmpeg + whisper-cli + psql in PATH, and a callback
# secret file at $SECRET_FILE.

set -uo pipefail

REPO_DIR="${EORGANIZE_REPO_DIR:-/Users/erictran/Developer/eorganize}"
LOG_DIR="${EORGANIZE_LOG_DIR:-$HOME/Library/Logs}"
SECRET_FILE="${EORGANIZE_CALLBACK_SECRET_FILE:-$HOME/.config/eorganize/callback.secret}"
CALLBACK_BASE="${EORGANIZE_CALLBACK_BASE:-https://eorganize.etpics.com}"
CLAUDE_BIN="${CLAUDE_BIN:-claude}"

# Make sure common Homebrew/asdf locations are on PATH — non-interactive
# SSH sessions inherit a minimal environment.
export PATH="/opt/homebrew/bin:/usr/local/bin:$HOME/.local/bin:$HOME/bin:$PATH"

session_id="${1:-${SSH_ORIGINAL_COMMAND:-}}"
# If invoked via forced-command authorized_keys entry, $SSH_ORIGINAL_COMMAND
# is the whole tail of the remote command — keep only the first whitespace
# token (the session id) to be safe.
session_id="${session_id%% *}"

if ! [[ "$session_id" =~ ^[0-9a-fA-F-]{36}$ ]]; then
  echo "usage: $0 <session-uuid>" >&2
  exit 2
fi

mkdir -p "$LOG_DIR"
log_file="$LOG_DIR/eorganize-identify-${session_id}.log"

# Detach: we want SSH to return immediately. Re-exec ourselves in the
# background with stdio redirected to the log file. The "_DETACHED=1" guard
# prevents an infinite re-exec loop.
if [[ -z "${_DETACHED:-}" ]]; then
  _DETACHED=1 nohup "$0" "$session_id" </dev/null >>"$log_file" 2>&1 &
  # Give the child a moment so any immediate error surfaces in the log.
  sleep 0.2
  exit 0
fi

echo "=== eorganize identify session $session_id started $(date -u +%FT%TZ) ==="

if [[ ! -r "$SECRET_FILE" ]]; then
  echo "FATAL: callback secret not readable at $SECRET_FILE" >&2
  exit 1
fi
callback_secret="$(cat "$SECRET_FILE")"

callback_url="$CALLBACK_BASE/api/ai-review-session/$session_id/status"

post_status() {
  local status="$1"
  local error_message="${2:-}"
  local log_tail="${3:-}"
  local body
  body="$(
    /usr/bin/env python3 - "$status" "$error_message" "$log_tail" <<'PY'
import json, sys
status, err, tail = sys.argv[1], sys.argv[2], sys.argv[3]
out = {"status": status}
if err: out["errorMessage"] = err
if tail: out["logTail"] = tail
print(json.dumps(out))
PY
  )"
  curl -sS -X POST "$callback_url" \
    -H "authorization: Bearer $callback_secret" \
    -H "content-type: application/json" \
    --data-binary "$body" \
    --max-time 15 \
    -o /dev/null -w "callback %{http_code}\n" \
    || echo "callback failed"
}

cleanup_failed() {
  local msg="$1"
  echo "FATAL: $msg" >&2
  local tail
  tail="$(tail -c 4000 "$log_file" 2>/dev/null || true)"
  post_status FAILED "$msg" "$tail"
}

# If we exit non-zero before reaching the explicit SUCCEEDED below, report it.
trap 'rc=$?; if [[ $rc -ne 0 ]]; then cleanup_failed "wrapper exited $rc"; fi' EXIT

post_status RUNNING

# Keep the Mac awake for the duration; -i covers "system idle", -m blocks
# disk sleep, -s blocks system sleep on AC. Without this the run can stall
# mid-identify if the Mac is set to sleep aggressively.
caffeinate_bin="$(command -v caffeinate || true)"

cd "$REPO_DIR" || { cleanup_failed "repo dir $REPO_DIR not found"; exit 1; }

# Stay current with whatever the latest OPERATING.md says.
git pull --ff-only --quiet || echo "warn: git pull failed, continuing with local copy"

if ! command -v "$CLAUDE_BIN" >/dev/null 2>&1; then
  cleanup_failed "claude CLI not found on PATH ($PATH)"
  exit 1
fi

# Sanity-check auth before kicking off the long-running pass.
if ! "$CLAUDE_BIN" --version >/dev/null 2>&1; then
  cleanup_failed "claude --version failed; re-run 'claude' interactively on this machine to refresh auth"
  exit 1
fi

prompt=$(cat <<'PROMPT'
You are running a Phase 2 identify pass for the eorganize household inventory app.

The canonical instructions live in docs/OPERATING.md in this repo, under the
section "Phase 2 — identify (at a desk, via Claude Code)". Read that section
end-to-end before doing anything else, then execute it:

  1. Query the production database for items to review (untitled or
     needsAIReview = true), including their photos, caption, pendingNote,
     voiceNotePath, and current category/brand/tags.
  2. Download thumbnails from the public API and look at each photo.
  3. Transcribe any voice notes via whisper.cpp.
  4. Compose the canonical batch-update SQL exactly as the operating doc
     describes (tag upsert, item update, _ItemTags insert, searchText
     rebuild — all inside a single BEGIN/COMMIT transaction).
  5. Apply the batch via `ssh root@etpics.com 'docker exec -i eorganize-db
     psql -U eorg -d eorgdb' < batch.sql`.
  6. Report a concise table at the end of what you identified.

Do not pause to ask for confirmation — this is a headless run. If something
genuinely blocks progress (e.g., a photo URL returns 404 or whisper-cli is
missing), log it and skip that item rather than aborting the whole pass.
PROMPT
)

run=("$CLAUDE_BIN" -p "$prompt" --dangerously-skip-permissions)
if [[ -n "$caffeinate_bin" ]]; then
  run=("$caffeinate_bin" -dimsu "${run[@]}")
fi

set +e
"${run[@]}" >>"$log_file" 2>&1
rc=$?
set -e

tail_text="$(tail -c 4000 "$log_file" 2>/dev/null || true)"

if [[ $rc -eq 0 ]]; then
  trap - EXIT
  post_status SUCCEEDED "" "$tail_text"
  echo "=== finished SUCCEEDED $(date -u +%FT%TZ) ==="
  exit 0
else
  trap - EXIT
  post_status FAILED "claude exited $rc" "$tail_text"
  echo "=== finished FAILED rc=$rc $(date -u +%FT%TZ) ==="
  exit "$rc"
fi
