#!/usr/bin/env bash
# Home-server wrapper for the "AI Search" page on the eorganize app.
#
# Invoked over SSH from the VPS app container with the AISearchSession 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, transcribe any voice input via whisper.cpp, then
# runs `claude -p` headless against the operating doc to pick matching
# items from the inventory.
#
# Setup prerequisites are the same as run-identify-local.sh: 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}"
KEYCHAIN_PW_FILE="${EORGANIZE_KEYCHAIN_PW_FILE:-$HOME/.config/eorganize/keychain.password}"
LOGIN_KEYCHAIN="${EORGANIZE_LOGIN_KEYCHAIN:-$HOME/Library/Keychains/login.keychain-db}"
CALLBACK_BASE="${EORGANIZE_CALLBACK_BASE:-https://eorganize.etpics.com}"
CLAUDE_BIN="${CLAUDE_BIN:-claude}"

export PATH="/opt/homebrew/bin:/usr/local/bin:$HOME/.local/bin:$HOME/bin:$PATH"

session_id="${1:-${SSH_ORIGINAL_COMMAND:-}}"
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-ai-search-${session_id}.log"

# Detach so SSH returns immediately.
if [[ -z "${_DETACHED:-}" ]]; then
  _DETACHED=1 nohup "$0" "$session_id" </dev/null >>"$log_file" 2>&1 &
  sleep 0.2
  exit 0
fi

echo "=== eorganize ai-search 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-search-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"
}

trap 'rc=$?; if [[ $rc -ne 0 ]]; then cleanup_failed "wrapper exited $rc"; fi' EXIT

post_status RUNNING

caffeinate_bin="$(command -v caffeinate || true)"

cd "$REPO_DIR" || { cleanup_failed "repo dir $REPO_DIR not found"; exit 1; }
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

# Unlock the login keychain so `claude -p` can read its OAuth token. Over
# SSH we have no GUI session, so the Keychain stays locked by default and
# claude reports "Not logged in" even though we logged in interactively.
if [[ ! -r "$KEYCHAIN_PW_FILE" ]]; then
  cleanup_failed "keychain password file not readable at $KEYCHAIN_PW_FILE"
  exit 1
fi
if ! security unlock-keychain -p "$(cat "$KEYCHAIN_PW_FILE")" "$LOGIN_KEYCHAIN" 2>/dev/null; then
  cleanup_failed "failed to unlock $LOGIN_KEYCHAIN (wrong password in $KEYCHAIN_PW_FILE?)"
  exit 1
fi

if ! "$CLAUDE_BIN" --version >/dev/null 2>&1; then
  cleanup_failed "claude --version failed; re-run 'claude' interactively to refresh auth"
  exit 1
fi

prompt=$(cat <<PROMPT
You are running an AI search pass for the eorganize household inventory app.

The canonical instructions live in docs/OPERATING.md in this repo, under the
section "Phase 3 — AI search (interactive)". Read that section end-to-end
before doing anything else, then execute it for session id:

  $session_id

In short:
  1. Query the production database for AISearchSession with this id. Read
     query, audioPath, audioMime.
  2. If audioPath is set, download it from the public uploads endpoint,
     transcribe via whisper.cpp, and POST the transcript back to the
     callback endpoint as field "transcript".
  3. Combine the typed query and the transcript into a single description
     of what the user wants to find.
  4. Pull the full inventory (items + photos + tags + location + caption +
     description + brand + category) from the DB.
  5. Pick the most likely matching items (up to ~10, ranked by confidence).
  6. POST the final result to the callback endpoint as:
       { "status": "SUCCEEDED",
         "transcript": "...",
         "reasoning": "short why",
         "resultItemIds": ["uuid1", "uuid2", ...] }
     Item ids must be in ranked order, best first.

Do not pause to ask for confirmation — this is a headless run. If something
genuinely blocks progress (audio unreachable, whisper missing, no items
match), include a clear errorMessage and POST FAILED.
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)"

# Claude POSTs its own SUCCEEDED with results; if it exited 0 we trust that
# and just append a log tail. If it failed, we mark the session FAILED.
if [[ $rc -eq 0 ]]; then
  trap - EXIT
  # Best-effort log-tail update — the SUCCEEDED status itself was already
  # posted by Claude with the result payload. We re-post the same status
  # just to attach the tail; the route accepts the same fields again.
  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
