#!/usr/bin/env bash
# Shared fail-closed Codex review gate for PR/branch merges.
#
# Exit codes:
#   0  Codex explicitly returned VERDICT: APPROVE
#   1  Codex explicitly returned VERDICT: NEEDS-FIXES: <reason>
#   2  Anything ambiguous or operationally broken
set -uo pipefail

usage() {
  echo "usage: codex_review_gate.sh <branch-or-pr-number> [--spec <path>] [--expected-head-oid <sha>]" >&2
}

die_closed() {
  echo "codex_review_gate: $*" >&2
  exit 2
}

TARGET="${1:-}"
if [ -z "$TARGET" ]; then
  usage
  exit 2
fi
shift

SPEC_PATH=""
EXPECTED_HEAD_OID=""
while [ "$#" -gt 0 ]; do
  case "$1" in
    --spec)
      shift
      [ "$#" -gt 0 ] || die_closed "--spec requires a path"
      SPEC_PATH="$1"
      ;;
    --expected-head-oid)
      shift
      [ "$#" -gt 0 ] || die_closed "--expected-head-oid requires a sha"
      EXPECTED_HEAD_OID="$1"
      ;;
    *)
      usage
      exit 2
      ;;
  esac
  shift
done

REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || die_closed "not inside a git repository"
cd "$REPO_ROOT" || die_closed "cannot cd to repo root"

command -v git >/dev/null 2>&1 || die_closed "git not found"

if ! git fetch origin --quiet; then
  die_closed "git fetch origin failed"
fi

BRANCH="$TARGET"
PR_HEAD_OID=""
case "$TARGET" in
  *[!0-9]*|"") ;;
  *)
    command -v gh >/dev/null 2>&1 || die_closed "gh not found for PR number resolution"
    BRANCH="$(gh pr view "$TARGET" --json headRefName -q '.headRefName' 2>/dev/null)" \
      || die_closed "cannot resolve PR #$TARGET head branch"
    [ -n "$BRANCH" ] || die_closed "PR #$TARGET has no head branch"
    PR_HEAD_OID="$(gh pr view "$TARGET" --json headRefOid -q '.headRefOid' 2>/dev/null)" \
      || die_closed "cannot resolve PR #$TARGET head oid"
    [ -n "$PR_HEAD_OID" ] || die_closed "PR #$TARGET has no head oid"
    ;;
esac

if [ -z "$PR_HEAD_OID" ] && command -v gh >/dev/null 2>&1; then
  PR_HEAD_OID="$(gh pr view "$BRANCH" --json headRefOid -q '.headRefOid' 2>/dev/null || true)"
fi

BASE="origin/main"
HEAD_REF="origin/$BRANCH"
git rev-parse --verify --quiet "$BASE" >/dev/null || die_closed "base ref $BASE not found"
HEAD_REF_OID=""
if git rev-parse --verify --quiet "$HEAD_REF" >/dev/null; then
  HEAD_REF_OID="$(git rev-parse "$HEAD_REF" 2>/dev/null)" || die_closed "cannot resolve $HEAD_REF sha"
elif [ -z "$EXPECTED_HEAD_OID" ] && [ -z "$PR_HEAD_OID" ]; then
  die_closed "head ref $HEAD_REF not found"
fi

if [ -n "$EXPECTED_HEAD_OID" ]; then
  if [ -n "$PR_HEAD_OID" ]; then
    if [ "$PR_HEAD_OID" != "$EXPECTED_HEAD_OID" ]; then
      die_closed "PR head moved before review: expected $EXPECTED_HEAD_OID, got $PR_HEAD_OID"
    fi
  elif [ -n "$HEAD_REF_OID" ]; then
    if [ "$HEAD_REF_OID" != "$EXPECTED_HEAD_OID" ]; then
      die_closed "branch head moved before review: expected $EXPECTED_HEAD_OID, got $HEAD_REF_OID"
    fi
  else
    die_closed "cannot prove expected head $EXPECTED_HEAD_OID against PR or $HEAD_REF"
  fi
fi

if [ -n "$EXPECTED_HEAD_OID" ]; then
  HEAD_SHA="$EXPECTED_HEAD_OID"
elif [ -n "$PR_HEAD_OID" ]; then
  HEAD_SHA="$PR_HEAD_OID"
else
  [ -n "$HEAD_REF_OID" ] || die_closed "cannot resolve $HEAD_REF sha"
  HEAD_SHA="$HEAD_REF_OID"
fi
git rev-parse --verify --quiet "$HEAD_SHA^{commit}" >/dev/null || die_closed "review head $HEAD_SHA not found"

DIFF_STAT="$(git diff --stat "$BASE...$HEAD_SHA" 2>/dev/null)" || die_closed "git diff failed for $BASE...$HEAD_SHA"
if [ -z "$DIFF_STAT" ]; then
  die_closed "empty diff for $BASE...$HEAD_SHA"
fi

resolve_spec_from_body() {
  command -v gh >/dev/null 2>&1 || return 1
  local body candidate
  body="$(gh pr view "$BRANCH" --json body -q '.body' 2>/dev/null)" || return 1
  candidate="$(printf '%s\n' "$body" \
    | grep -Eo '[^[:space:]`"'"'"'<>]*BUILD_SPEC\.md' \
    | head -n 1)"
  [ -n "$candidate" ] || return 1
  [ -f "$candidate" ] || return 1
  printf '%s\n' "$candidate"
}

resolve_spec_from_branch() {
  local slug candidate
  slug="${BRANCH##*/}"
  [ -n "$slug" ] || return 1
  [ -d consultations ] || return 1
  candidate="$(find consultations -path '*/BUILD_SPEC.md' -type f 2>/dev/null \
    | grep -F "$slug" \
    | head -n 1)"
  [ -n "$candidate" ] || return 1
  printf '%s\n' "$candidate"
}

SPEC_NOTE="No build spec was found; review the diff alone."
SPEC_SOURCE=""
if [ -n "$SPEC_PATH" ]; then
  if [ -f "$SPEC_PATH" ] && [ -r "$SPEC_PATH" ]; then
    SPEC_NOTE="Build spec contract from $SPEC_PATH:"
    SPEC_SOURCE="$SPEC_PATH"
  else
    die_closed "requested build spec path was not readable: $SPEC_PATH"
  fi
else
  RESOLVED_SPEC="$(resolve_spec_from_body || resolve_spec_from_branch || true)"
  if [ -n "$RESOLVED_SPEC" ]; then
    SPEC_NOTE="Build spec contract from $RESOLVED_SPEC:"
    SPEC_SOURCE="$RESOLVED_SPEC"
  fi
fi

SHORT_SHA="$(git rev-parse --short "$HEAD_SHA" 2>/dev/null)" || die_closed "cannot shorten $HEAD_SHA"
SAFE_BRANCH="$(printf '%s' "$BRANCH" | sed 's/[^A-Za-z0-9._-]/_/g; s/\.\./__/g; s/^[.][.]*/_/; s/[.][.]*$/_/')"
[ -n "$SAFE_BRANCH" ] || SAFE_BRANCH="branch"
REVIEW_DIR="$HOME/.recoil/codex-reviews"
mkdir -p "$REVIEW_DIR" || die_closed "cannot create review dir $REVIEW_DIR"
REVIEW_OUT="$REVIEW_DIR/${SAFE_BRANCH}__${SHORT_SHA}.md"
REVIEW_TRACE="$REVIEW_DIR/${SAFE_BRANCH}__${SHORT_SHA}.trace.log"
PROMPT_FILE="$(mktemp "${TMPDIR:-/tmp}/codex-review-prompt.XXXXXX")" || die_closed "cannot create prompt temp file"
cleanup_prompt() {
  rm -f "$PROMPT_FILE" 2>/dev/null || true
}
trap cleanup_prompt EXIT

(
  cat <<PROMPT_EOF
You are an independent, adversarial code reviewer for a private same-repo PR.
Do not edit files. Review only the supplied diff against origin/main and, when
present, the build spec contract.

Required review standard:
- Identify CRITICAL issues that can break correctness, data safety, merge safety,
  security, or the explicit spec contract.
- Identify MAJOR issues that create likely regressions, missing required behavior,
  brittle integration, or inadequate fail-closed behavior.
- Identify MINOR issues only when concrete and actionable.
- Do not invent issues. Ground every finding in the diff or the spec.
- If the diff is acceptable, say why briefly.

$SPEC_NOTE
PROMPT_EOF
  if [ -n "$SPEC_SOURCE" ]; then
    cat "$SPEC_SOURCE" || exit 1
  fi
  cat <<PROMPT_EOF

Diff under review: $BASE...$HEAD_SHA ($BRANCH)
\`\`\`diff
PROMPT_EOF
  git diff "$BASE...$HEAD_SHA" || exit 1
  cat <<PROMPT_EOF
\`\`\`

End your response with EXACTLY one final line, and no text after it:
VERDICT: APPROVE
or
VERDICT: NEEDS-FIXES: <reason>
PROMPT_EOF
) > "$PROMPT_FILE" || die_closed "cannot write Codex review prompt"

if ! command -v codex >/dev/null 2>&1; then
  {
    echo "Codex review gate failed before invocation."
    echo "Reason: codex not found on PATH."
  } > "$REVIEW_OUT"
  echo "$REVIEW_OUT"
  die_closed "codex not found on PATH"
fi

TIMEOUT_BIN="$(command -v gtimeout || command -v timeout || true)"
rm -f "$REVIEW_OUT" "$REVIEW_TRACE" 2>/dev/null || true
if ${TIMEOUT_BIN:+$TIMEOUT_BIN 600} codex exec --sandbox read-only --output-last-message "$REVIEW_OUT" - < "$PROMPT_FILE" > "$REVIEW_TRACE" 2>&1; then
  CODEX_RC=0
else
  CODEX_RC=$?
fi

echo "$REVIEW_OUT"

if [ "$CODEX_RC" -eq 124 ]; then
  echo "codex_review_gate: Codex review timed out" >&2
  echo "codex_review_gate: Codex transcript: $REVIEW_TRACE" >&2
  exit 2
fi
if [ "$CODEX_RC" -ne 0 ]; then
  echo "codex_review_gate: Codex review command failed with exit $CODEX_RC" >&2
  echo "codex_review_gate: Codex transcript: $REVIEW_TRACE" >&2
  exit 2
fi

[ -s "$REVIEW_OUT" ] || {
  echo "codex_review_gate: Codex produced no final review message" >&2
  echo "codex_review_gate: Codex transcript: $REVIEW_TRACE" >&2
  exit 2
}

VERDICT_LINE="$(tail -n 1 "$REVIEW_OUT" 2>/dev/null || true)"
VERDICT_LINE="${VERDICT_LINE%$'\r'}"
VERDICT_COUNT="$(grep -c '^VERDICT:' "$REVIEW_OUT" 2>/dev/null || true)"
[ "$VERDICT_COUNT" = "1" ] || {
  echo "codex_review_gate: ambiguous or missing final VERDICT line" >&2
  exit 2
}
case "$VERDICT_LINE" in
  "VERDICT: APPROVE")
    exit 0
    ;;
  "VERDICT: NEEDS-FIXES: "?*)
    REASON="${VERDICT_LINE#VERDICT: NEEDS-FIXES:}"
    REASON="${REASON# }"
    [ -n "$REASON" ] || {
      echo "codex_review_gate: no parseable final VERDICT line" >&2
      exit 2
    }
    echo "codex_review_gate: NEEDS-FIXES: $REASON" >&2
    exit 1
    ;;
  *)
    echo "codex_review_gate: no parseable final VERDICT line" >&2
    exit 2
    ;;
esac
