#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<'EOF'
Usage: autoreview [options]

Options:
  --mode auto|local|branch|commit
                              Target selection. Default: auto.
  --base REF                 Base ref for branch review. Default: PR base or origin/main.
  --commit REF               Commit ref for commit review. Default: HEAD.
  --reviewer codex|claude|pi|opencode|auto
                              Review engine. Default: auto (Codex, fallback reviewer on error).
  --fallback-reviewer claude|pi|opencode|none
                              Fallback when Codex is unavailable or exits nonzero. Default: claude.
  --codex-bin PATH           Codex binary. Default: codex.
  --claude-bin PATH          Claude binary. Default: claude.
  --pi-bin PATH              Pi binary. Default: pi.
  --opencode-bin PATH        OpenCode binary. Default: opencode.
  --full-access              Keep yolo/full-access mode enabled. Default.
  --no-yolo                  Run nested Codex review with normal sandbox/approval prompts.
  --output FILE              Also save output to file.
  --parallel-tests CMD       Run review and test command concurrently.
  --dry-run                  Print selected commands, do not run.
  -h, --help                 Show help.

Modes:
  local   codex review --uncommitted
  branch  codex review --base <base>
  commit  codex review --commit <commit>
  auto    dirty tree -> local, else PR/current branch -> branch
EOF
}

mode=auto
base_ref=
commit_ref=HEAD
reviewer=${AUTOREVIEW_REVIEWER:-${CODEX_REVIEW_REVIEWER:-auto}}
fallback_reviewer=${AUTOREVIEW_FALLBACK_REVIEWER:-${CODEX_REVIEW_FALLBACK_REVIEWER:-claude}}
codex_bin=${CODEX_BIN:-codex}
claude_bin=${CLAUDE_BIN:-claude}
pi_bin=${PI_BIN:-pi}
opencode_bin=${OPENCODE_BIN:-opencode}
codex_args=()
yolo=${AUTOREVIEW_YOLO:-${CODEX_REVIEW_YOLO:-1}}
output=${AUTOREVIEW_OUTPUT:-${CODEX_REVIEW_OUTPUT:-}}
parallel_tests=
dry_run=false

while [[ $# -gt 0 ]]; do
  case "$1" in
    --mode)
      mode=${2:-}
      shift 2
      ;;
    --base)
      base_ref=${2:-}
      shift 2
      ;;
    --commit)
      commit_ref=${2:-}
      shift 2
      ;;
    --reviewer)
      reviewer=${2:-}
      shift 2
      ;;
    --fallback-reviewer)
      fallback_reviewer=${2:-}
      shift 2
      ;;
    --codex-bin)
      codex_bin=${2:-}
      shift 2
      ;;
    --claude-bin)
      claude_bin=${2:-}
      shift 2
      ;;
    --pi-bin)
      pi_bin=${2:-}
      shift 2
      ;;
    --opencode-bin)
      opencode_bin=${2:-}
      shift 2
      ;;
    --full-access)
      yolo=1
      shift
      ;;
    --no-yolo)
      yolo=0
      shift
      ;;
    --output)
      output=${2:-}
      shift 2
      ;;
    --parallel-tests)
      parallel_tests=${2:-}
      shift 2
      ;;
    --dry-run)
      dry_run=true
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      usage >&2
      exit 2
      ;;
  esac
done

case "$yolo" in
  0|false|False|FALSE|no|No|NO|off|Off|OFF) ;;
  *) codex_args+=(--dangerously-bypass-approvals-and-sandbox) ;;
esac

case "$mode" in
  auto|local|branch|commit) ;;
  *)
    echo "invalid --mode: $mode" >&2
    exit 2
    ;;
esac

case "$reviewer" in
  auto|codex|claude|pi|opencode) ;;
  *)
    echo "invalid --reviewer: $reviewer" >&2
    exit 2
    ;;
esac

case "$fallback_reviewer" in
  claude|pi|opencode|none) ;;
  *)
    echo "invalid --fallback-reviewer: $fallback_reviewer" >&2
    exit 2
    ;;
esac

repo_root=$(git rev-parse --show-toplevel)

current_branch=$(git branch --show-current 2>/dev/null || true)
dirty=false
if [[ -n "$(git status --porcelain)" ]]; then
  dirty=true
fi

pr_url=
if [[ -z "$base_ref" && "$mode" != local ]] && command -v gh >/dev/null 2>&1; then
  if pr_lines=$(gh pr view --json baseRefName,url --jq '[.baseRefName, .url] | @tsv' 2>/dev/null); then
    base_name=${pr_lines%%$'\t'*}
    pr_url=${pr_lines#*$'\t'}
    if [[ -n "$base_name" ]]; then
      base_ref="origin/$base_name"
    fi
  fi
fi

if [[ -z "$base_ref" ]]; then
  base_ref=origin/main
fi

review_kind=
if [[ "$mode" == local || ( "$mode" == auto && "$dirty" == true ) ]]; then
  review_kind=local
elif [[ "$mode" == commit ]]; then
  review_kind=commit
elif [[ "$mode" == branch || ( "$mode" == auto && -n "$current_branch" && "$current_branch" != "main" ) ]]; then
  review_kind=branch
else
  echo "no review target: clean main checkout and no forced mode" >&2
  exit 1
fi

if [[ "$review_kind" == local ]]; then
  review_cmd=("$codex_bin" "${codex_args[@]}" review --uncommitted)
elif [[ "$review_kind" == commit ]]; then
  review_cmd=("$codex_bin" "${codex_args[@]}" review --commit "$commit_ref")
else
  review_cmd=("$codex_bin" "${codex_args[@]}" review --base "$base_ref")
fi

printf 'autoreview target: %s\n' "$review_kind"
printf 'branch: %s\n' "${current_branch:-detached}"
if [[ -n "$pr_url" ]]; then
  printf 'pr: %s\n' "$pr_url"
fi
printf 'reviewer: %s\n' "$reviewer"
if [[ "$reviewer" == auto ]]; then
  printf 'fallback-reviewer: %s\n' "$fallback_reviewer"
fi
if [[ "$reviewer" == auto || "$reviewer" == codex ]]; then
  printf 'review:'
  printf ' %q' "${review_cmd[@]}"
  printf '\n'
else
  printf 'review: %s prompt review\n' "$reviewer"
fi
if [[ -n "$parallel_tests" ]]; then
  printf 'tests: %s\n' "$parallel_tests"
fi
if [[ "$review_kind" == branch ]]; then
  printf 'fetch: git fetch origin --quiet\n'
fi
if [[ -n "$output" ]]; then
  printf 'output: %s\n' "$output"
fi

if [[ "$dry_run" == true ]]; then
  exit 0
fi

if [[ "$review_kind" == branch ]]; then
  git fetch origin --quiet || {
    echo "warning: git fetch origin failed; reviewing with existing refs" >&2
  }
fi

review_output=$output
review_output_is_temp=false
if [[ -z "$review_output" ]]; then
  review_output=$(mktemp)
  review_output_is_temp=true
fi
mkdir -p "$(dirname "$review_output")"
: > "$review_output"

cleanup() {
  if [[ "${review_output_is_temp:-false}" == true && -n "${review_output:-}" ]]; then
    rm -f "$review_output"
  fi
  if [[ -n "${prompt_file:-}" ]]; then
    rm -f "$prompt_file"
  fi
}
trap cleanup EXIT

run_review() {
  mkdir -p "$(dirname "$review_output")"
  "${review_cmd[@]}" 2>&1 | tee "$review_output"
}

diff_for_review() {
  case "$review_kind" in
    local)
      git -C "$repo_root" diff --stat
      git -C "$repo_root" diff --cached --stat
      git -C "$repo_root" diff --find-renames
      git -C "$repo_root" diff --cached --find-renames
      while IFS= read -r untracked_file; do
        [[ -n "$untracked_file" ]] || continue
        git -C "$repo_root" diff --no-index -- /dev/null "$untracked_file" || true
      done < <(git -C "$repo_root" ls-files --others --exclude-standard)
      ;;
    commit)
      git -C "$repo_root" show --find-renames --stat --format=fuller "$commit_ref"
      git -C "$repo_root" show --find-renames --format=medium "$commit_ref"
      ;;
    branch)
      git -C "$repo_root" diff --find-renames --stat "$base_ref"...HEAD
      git -C "$repo_root" diff --find-renames "$base_ref"...HEAD
      ;;
  esac
}

build_prompt_file() {
  prompt_file=$(mktemp)
  {
    cat <<EOF
You are performing a closeout code review for the current repository.

Review target: $review_kind
Branch: ${current_branch:-detached}
Base: ${base_ref:-}
Commit: ${commit_ref:-}

Rules:
- Review only the diff below.
- Do not modify files.
- Prioritize correctness bugs, regressions, security issues, and missing tests.
- Ignore speculative edge cases and broad rewrites.
- For each accepted/actionable finding, use exactly this format:
  [P<0-3>] Short title
  File: path:line
  Why: one sentence
  Fix: one sentence
- If no accepted/actionable findings, output exactly:
  autoreview clean: no accepted/actionable findings reported

Diff:
EOF
    diff_for_review
  } > "$prompt_file" || return
}

run_prompt_reviewer() {
  local selected=$1
  local reviewer_output
  local status=0

  if ! build_prompt_file; then
    rm -f "${prompt_file:-}"
    prompt_file=
    return 1
  fi
  reviewer_output=$(mktemp)
  mkdir -p "$(dirname "$review_output")"

  case "$selected" in
    claude)
      if ! command -v "$claude_bin" >/dev/null 2>&1; then
        echo "fallback reviewer unavailable: $claude_bin" >&2
        status=127
      elif printf 'fallback: claude -p\n' | tee -a "$review_output"; then
        "$claude_bin" --tools "" --no-session-persistence -p < "$prompt_file" 2>&1 | tee -a "$review_output" "$reviewer_output"
        status=$?
      else
        status=$?
      fi
      ;;
    pi)
      if ! command -v "$pi_bin" >/dev/null 2>&1; then
        echo "fallback reviewer unavailable: $pi_bin" >&2
        status=127
      elif printf 'fallback: pi -p\n' | tee -a "$review_output"; then
        "$pi_bin" --no-tools --no-session -p < "$prompt_file" 2>&1 | tee -a "$review_output" "$reviewer_output"
        status=$?
      else
        status=$?
      fi
      ;;
    opencode)
      if ! command -v "$opencode_bin" >/dev/null 2>&1; then
        echo "fallback reviewer unavailable: $opencode_bin" >&2
        status=127
      elif printf 'fallback: opencode run\n' | tee -a "$review_output"; then
        "$opencode_bin" run --pure --dir "$(dirname "$prompt_file")" --file "$prompt_file" \
          "Review the attached prompt file. Do not modify files." 2>&1 | tee -a "$review_output" "$reviewer_output"
        status=$?
      else
        status=$?
      fi
      ;;
    *)
      echo "unsupported prompt reviewer: $selected" >&2
      status=2
      ;;
  esac
  if [[ "$status" == 0 ]]; then
    if grep -Eq '\[P[0-3]\]' "$reviewer_output"; then
      status=1
    elif ! grep -q '[^[:space:]]' "$reviewer_output"; then
      status=1
    elif ! grep -Fxq 'autoreview clean: no accepted/actionable findings reported' "$reviewer_output"; then
      status=1
    fi
  fi
  rm -f "$reviewer_output"
  rm -f "$prompt_file"
  prompt_file=
  return "$status"
}

run_selected_review() {
  local selected=$1
  case "$selected" in
    codex)
      if ! command -v "$codex_bin" >/dev/null 2>&1; then
        echo "codex reviewer unavailable: $codex_bin" >&2
        return 127
      fi
      run_review
      ;;
    claude|pi|opencode)
      run_prompt_reviewer "$selected"
      ;;
    *)
      echo "unsupported reviewer: $selected" >&2
      return 2
      ;;
  esac
}

run_auto_review() {
  run_selected_review codex
  local status=$?
  if [[ "$status" == 0 ]]; then
    return 0
  fi
  if (( status > 128 && status < 192 )); then
    return "$status"
  fi
  if review_output_has_findings; then
    return "$status"
  fi
  if [[ "$fallback_reviewer" == none ]]; then
    return "$status"
  fi
  printf 'autoreview warning: codex exited %s; falling back to %s\n' "$status" "$fallback_reviewer" >&2
  run_selected_review "$fallback_reviewer"
}

elapsed_since() {
  local started_at=$1
  local finished_at
  finished_at=$(date +%s)
  printf '%s\n' "$((finished_at - started_at))"
}

format_elapsed() {
  local seconds=$1
  if (( seconds < 60 )); then
    printf '%ss\n' "$seconds"
  else
    printf '%sm%ss\n' "$((seconds / 60))" "$((seconds % 60))"
  fi
}

review_output_empty() {
  [[ ! -s "$review_output" ]] || ! grep -q '[^[:space:]]' "$review_output"
}

review_findings_text() {
  if grep -Fxq 'codex' "$review_output"; then
    awk '
      $0 == "codex" {
        capture = 1
        output = $0 ORS
        next
      }
      capture {
        output = output $0 ORS
      }
      END {
        printf "%s", output
      }
    ' "$review_output"
    return
  fi
  cat "$review_output"
}

review_output_has_findings() {
  review_findings_text | grep -Eq '\[P[0-3]\]'
}

report_clean_review_or_fail() {
  local elapsed_text
  elapsed_text=$(format_elapsed "${review_elapsed_seconds:-0}")

  if review_output_has_findings; then
    printf 'autoreview complete after %s\n' "$elapsed_text"
    printf 'autoreview findings: accepted/actionable findings reported\n'
    return 1
  fi
  if review_output_empty; then
    printf 'autoreview complete after %s; no output\n' "$elapsed_text"
    return 1
  fi
  printf 'autoreview complete after %s\n' "$elapsed_text"
  printf 'autoreview clean: no accepted/actionable findings reported\n'
}

if [[ -z "$parallel_tests" ]]; then
  review_started_at=$(date +%s)
  set +e
  if [[ "$reviewer" == auto ]]; then
    run_auto_review
  else
    run_selected_review "$reviewer"
  fi
  review_status=$?
  review_elapsed_seconds=$(elapsed_since "$review_started_at")
  set -e
  if [[ "$review_status" == 0 ]]; then
    report_clean_review_or_fail
    exit $?
  fi
  exit "$review_status"
fi

review_status_file=$(mktemp)
review_elapsed_file=$(mktemp)
tests_status_file=$(mktemp)

(
  set +e
  review_started_at=$(date +%s)
  if [[ "$reviewer" == auto ]]; then
    run_auto_review
  else
    run_selected_review "$reviewer"
  fi
  status=$?
  elapsed=$(elapsed_since "$review_started_at")
  printf '%s\n' "$status" > "$review_status_file"
  printf '%s\n' "$elapsed" > "$review_elapsed_file"
) &
review_pid=$!

(
  set +e
  bash -lc "$parallel_tests"
  status=$?
  printf '%s\n' "$status" > "$tests_status_file"
) &
tests_pid=$!

wait "$review_pid" || true
wait "$tests_pid" || true

review_status=$(cat "$review_status_file")
review_elapsed_seconds=$(cat "$review_elapsed_file")
tests_status=$(cat "$tests_status_file")
rm -f "$review_status_file" "$review_elapsed_file" "$tests_status_file"

printf 'autoreview exit: %s\n' "$review_status"
printf 'tests exit: %s\n' "$tests_status"

if [[ "$review_status" != 0 || "$tests_status" != 0 ]]; then
  exit 1
fi

report_clean_review_or_fail
