#!/usr/bin/env bash
# review-pr.sh — AI-powered PR review using claude CLI
#
# Usage: ./scripts/review-pr.sh <pr-number> [--force]
#
# Calls `claude -p --model sonnet` with context docs + diff.
# No tool access (pure text review), ~$0.02-0.05 per review.
#
# --force: skip the "already reviewed" check
#
# Concurrency: uses a lockfile to ensure only one review runs at a time.
# Status: writes live progress to /tmp/harb-review-status for peeking.
# Logs: /home/debian/harb/logs/review.log (auto-rotated at 100KB)
#
# Peek while running:  cat /tmp/harb-review-status
# Watch log:           tail -f ~/harb/logs/review.log

set -euo pipefail

# --- Environment (cron-safe) ---
export PATH="/home/debian/.nvm/versions/node/v22.20.0/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
export HOME="${HOME:-/home/debian}"

# --- Config ---
PR_NUMBER="${1:?Usage: review-pr.sh <pr-number> [--force]}"
FORCE="${2:-}"
REPO="johba/harb"
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CODEBERG_TOKEN="$(awk '/codeberg.org/{getline;getline;print $2}' ~/.netrc)"
API_BASE="https://codeberg.org/api/v1/repos/${REPO}"
LOCKFILE="/tmp/harb-review.lock"
STATUSFILE="/tmp/harb-review-status"
LOGDIR="/home/debian/harb/logs"
LOGFILE="$LOGDIR/review.log"
MIN_MEM_MB=1500
TMPDIR=$(mktemp -d)

mkdir -p "$LOGDIR"

# --- Logging ---
log() {
  local ts
  ts="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
  echo "[$ts] PR#${PR_NUMBER} $*" | tee -a "$LOGFILE"
}

status() {
  local ts
  ts="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
  printf '[%s] PR #%s: %s\n' "$ts" "$PR_NUMBER" "$*" > "$STATUSFILE"
  log "$*"
}

cleanup() {
  rm -rf "$TMPDIR"
  rm -f "$LOCKFILE" "$STATUSFILE"
}
trap cleanup EXIT

# --- Log rotation (keep ~100KB + 1 archive) ---
if [ -f "$LOGFILE" ]; then
  LOGSIZE=$(stat -c%s "$LOGFILE" 2>/dev/null || echo 0)
  if [ "$LOGSIZE" -gt 102400 ]; then
    mv "$LOGFILE" "$LOGFILE.old"
    log "Log rotated (was ${LOGSIZE} bytes)"
  fi
fi

# --- Memory guard ---
AVAIL_MB=$(awk '/MemAvailable/{printf "%d", $2/1024}' /proc/meminfo)
if [ "$AVAIL_MB" -lt "$MIN_MEM_MB" ]; then
  log "SKIP: only ${AVAIL_MB}MB available (need ${MIN_MEM_MB}MB)"
  exit 0
fi

# --- Concurrency lock ---
if [ -f "$LOCKFILE" ]; then
  LOCK_PID=$(cat "$LOCKFILE" 2>/dev/null || echo "")
  if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
    log "SKIP: another review running (PID ${LOCK_PID})"
    exit 0
  fi
  log "Removing stale lock (PID ${LOCK_PID:-?})"
  rm -f "$LOCKFILE"
fi
echo $$ > "$LOCKFILE"

# --- Fetch PR metadata ---
status "fetching metadata"
PR_JSON=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
  "${API_BASE}/pulls/${PR_NUMBER}")

PR_TITLE=$(echo "$PR_JSON" | jq -r '.title')
PR_BODY=$(echo "$PR_JSON" | jq -r '.body // ""')
PR_HEAD=$(echo "$PR_JSON" | jq -r '.head.ref')
PR_BASE=$(echo "$PR_JSON" | jq -r '.base.ref')
PR_SHA=$(echo "$PR_JSON" | jq -r '.head.sha')
PR_STATE=$(echo "$PR_JSON" | jq -r '.state')

log "${PR_TITLE} (${PR_HEAD}→${PR_BASE} ${PR_SHA:0:7})"

# --- Guards ---
if [ "$PR_STATE" != "open" ]; then
  log "SKIP: state=${PR_STATE}"
  exit 0
fi

status "checking CI"
CI_STATE=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
  "${API_BASE}/commits/${PR_SHA}/status" | jq -r '.state // "unknown"')

if [ "$CI_STATE" != "success" ]; then
  log "SKIP: CI=${CI_STATE}"
  exit 0
fi

if [ "$FORCE" != "--force" ]; then
  status "checking existing reviews"
  EXISTING=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
    "${API_BASE}/issues/${PR_NUMBER}/comments?limit=50" | \
    jq -r --arg sha "$PR_SHA" \
    '[.[] | select(.body | contains("<!-- reviewed:")) | select(.body | contains($sha))] | length')

  if [ "$EXISTING" -gt "0" ]; then
    log "SKIP: already reviewed at ${PR_SHA:0:7}"
    exit 0
  fi
fi

# --- Fetch diff ---
status "fetching diff"
DIFF=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
  "${API_BASE}/pulls/${PR_NUMBER}.diff" | head -c 25000)

DIFF_STAT=$(echo "$DIFF" | grep -E '^\+\+\+ b/' | sed 's|^+++ b/||' | sort)

# --- Which context docs? ---
NEEDS_UX=false
for f in $DIFF_STAT; do
  case "$f" in
    landing/*|web-app/*) NEEDS_UX=true ;;
  esac
done

# --- Build prompt file ---
status "building prompt"
cat > "${TMPDIR}/prompt.md" << PROMPT_EOF
# PR #${PR_NUMBER}: ${PR_TITLE}

## PR Description
${PR_BODY}

## Changed Files
${DIFF_STAT}

## PRODUCT-TRUTH.md (what we can/cannot claim)
$(cat "${REPO_ROOT}/docs/PRODUCT-TRUTH.md")

## ARCHITECTURE.md
$(cat "${REPO_ROOT}/docs/ARCHITECTURE.md")
PROMPT_EOF

if [ "$NEEDS_UX" = true ] && [ -f "${REPO_ROOT}/docs/UX-DECISIONS.md" ]; then
  cat >> "${TMPDIR}/prompt.md" << UX_EOF

## UX-DECISIONS.md
$(cat "${REPO_ROOT}/docs/UX-DECISIONS.md")
UX_EOF
fi

cat >> "${TMPDIR}/prompt.md" << DIFF_EOF

## Diff
\`\`\`diff
${DIFF}
\`\`\`

## Your Task
Produce a structured review:

### 1. Claim Check
Extract every factual claim about the protocol from user-facing text in the diff.
For each, verify against PRODUCT-TRUTH.md:
- ✅ Accurate
- ⚠️ Partially true (explain)
- ❌ False (cite contradiction)
If no claims, say "No user-facing claims in this diff."

### 2. Code Review
Bugs, logic errors, missing edge cases, broken imports.

### 3. Architecture Check
Does this follow patterns in ARCHITECTURE.md?

### 4. UX/Messaging Check
Does copy follow UX-DECISIONS.md?
(Skip if no UX-DECISIONS context provided.)

### 5. Verdict
**APPROVE**, **REQUEST_CHANGES**, or **DISCUSS** — one line reason.

Be direct. No filler.
DIFF_EOF

PROMPT_SIZE=$(stat -c%s "${TMPDIR}/prompt.md")
log "Prompt: ${PROMPT_SIZE} bytes"

# --- Run claude -p ---
status "running claude (sonnet)"
SECONDS=0

REVIEW=$(claude -p \
  --model sonnet \
  --dangerously-skip-permissions \
  --output-format text \
  < "${TMPDIR}/prompt.md" 2>"${TMPDIR}/claude-stderr.log")

ELAPSED=$SECONDS
CLAUDE_EXIT=$?

if [ $CLAUDE_EXIT -ne 0 ]; then
  log "ERROR: claude exited ${CLAUDE_EXIT} after ${ELAPSED}s"
  log "stderr: $(cat "${TMPDIR}/claude-stderr.log" | tail -5)"
  exit 1
fi

if [ -z "$REVIEW" ]; then
  log "ERROR: empty review after ${ELAPSED}s"
  exit 1
fi

REVIEW_SIZE=$(echo "$REVIEW" | wc -c)
log "Review: ${REVIEW_SIZE} bytes in ${ELAPSED}s"

# --- Post to Codeberg ---
status "posting to Codeberg"

COMMENT_BODY="## 🤖 AI Review
<!-- reviewed: ${PR_SHA} -->

${REVIEW}

---
*Reviewed at \`${PR_SHA:0:7}\` · [PRODUCT-TRUTH.md](../docs/PRODUCT-TRUTH.md) · [ARCHITECTURE.md](../docs/ARCHITECTURE.md)*"

POST_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
  -X POST \
  -H "Authorization: token ${CODEBERG_TOKEN}" \
  -H "Content-Type: application/json" \
  "${API_BASE}/issues/${PR_NUMBER}/comments" \
  -d "$(jq -n --arg body "$COMMENT_BODY" '{body: $body}')")

if [ "${POST_CODE}" = "201" ]; then
  log "POSTED to Codeberg"
else
  log "ERROR: Codeberg HTTP ${POST_CODE}"
  echo "$REVIEW" > "${LOGDIR}/review-pr${PR_NUMBER}-${PR_SHA:0:7}.md"
  log "Review saved to ${LOGDIR}/review-pr${PR_NUMBER}-${PR_SHA:0:7}.md"
  exit 1
fi

# --- Notify OpenClaw (best effort) ---
VERDICT=$(echo "$REVIEW" | grep -oP '\*\*(APPROVE|REQUEST_CHANGES|DISCUSS)\*\*' | head -1 | tr -d '*')
if command -v openclaw &>/dev/null; then
  openclaw system event \
    --text "🤖 PR #${PR_NUMBER} reviewed: ${VERDICT:-UNKNOWN} — ${PR_TITLE}" \
    --mode now 2>/dev/null || true
fi

log "DONE: ${VERDICT:-UNKNOWN} (${ELAPSED}s)"
