#!/usr/bin/env bash
# GitReins Tier 1 — the pre-commit gate for this repo.
#
# Three arms, in this order:
#
#   1. the built-in secrets scan. It always runs, and it BLOCKS (exit 1). It is
#      deliberately self-contained: it is the only gate still available on a box
#      where the `gitreins` engine is not installed.
#
#   2. the private-host leak scan (DF-CHIMERA-V2-24). Every staged text file is
#      scanned with the SAME pattern set the INT-CI-005 suite guard uses
#      (tests/private_host_scan.py) and any private/CGNAT address or tailnet
#      hostname BLOCKS (exit 1): a bunker address must be caught at commit
#      time, not on the next full-suite run after the mirror already has it.
#      It skips the same files the secrets scan skips (gitignored +
#      .gitreins/secrets-ignore) and binary-looking files, and it is a loud
#      SKIP — never a block — where no Python can be resolved or the scanner
#      is missing from the repo layout (the suite guard still covers that tree).
#
#   3. the real quality harness — `gitreins guard` (docs/GITREINS.md). Its exit
#      code is a VERDICT, not just an error level, so the hook maps it:
#
#        0  every substantive lane did work and passed           -> PASS, commit
#        1  a lane RAN and FAILED (e.g. a ruff finding)          -> BLOCK, exit 1
#        2  DEGRADED: a substantive lane did NO work (a skip)    -> warn, commit
#        *  engine error, or no engine on PATH at all            -> warn, commit
#
#      Blocking on 2 would make the repo uncommittable on any box whose PATH
#      lacks the lsp lane's tool (`pylsp`) — the run says "a gate never ran",
#      which is not "a gate FAILED". Blocking on 1 is the point of this hook: a
#      graded FAIL must not reach a commit.
#
# There is deliberately NO bypass variable. `git commit --no-verify` remains the
# only escape hatch, and AGENTS.md forbids it for code changes.
set -e

ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"

STAGED=$(git diff --cached --name-only --diff-filter=ACM)
[ -z "$STAGED" ] && exit 0

FAILED=0

# ── 1. Secrets Scan ──────────────────────────────────────
SECRET_PATTERNS='sk-[a-zA-Z0-9_-]{20,}|ghp_[a-zA-Z0-9]{36}|-----BEGIN (RSA |EC |OPENSSH )PRIVATE KEY-----|(OPENROUTER|DEEPSEEK|ZAI|ANTHROPIC)_API_KEY\s*=\s*\S'
# Honor .gitreins/secrets-ignore (same semantics as `gitreins guard`): a
# staged file is skipped when its path equals an entry, or starts with an
# entry ending in '/', or its path-part matches '<path>:<pattern>'.
is_ignored() {
  local file="$1" entry pathpart
  [ -f .gitreins/secrets-ignore ] || return 1
  while IFS= read -r entry; do
    [ -z "$entry" ] && continue
    case "$entry" in \#*) continue ;; esac
    pathpart="${entry%%:*}"
    [ "$file" = "$pathpart" ] && return 0
    case "$pathpart" in */) case "$file" in "$pathpart"*) return 0 ;; esac ;; esac
  done < .gitreins/secrets-ignore
  return 1
}

while IFS= read -r file; do
  [ ! -f "$file" ] && continue
  git check-ignore -q "$file" 2>/dev/null && continue
  if is_ignored "$file"; then
    echo "  (skipped by .gitreins/secrets-ignore: $file)"
    continue
  fi
  if grep -Pn "$SECRET_PATTERNS" "$file" 2>/dev/null; then
    echo "⚠ SECRET DETECTED in $file" >&2
    FAILED=1
  fi
done <<< "$STAGED"

if [ $FAILED -eq 1 ]; then
  echo "" >&2
  echo "COMMIT BLOCKED: secrets found in staged files" >&2
  exit 1
fi
echo "✓ GitReins Tier 1: Secrets scan — PASS"

# ── 2. Private-host leak scan (DF-CHIMERA-V2-24) ─────────
# The INT-CI-005 suite guard catches these only on the next full pytest run;
# by then the public mirror already has the address. Scan every staged text
# file with the SAME pattern set the guard uses and BLOCK on any hit.
LEAK_SCANNER="tests/private_host_scan.py"
LEAK_PY=""
for candidate in "$ROOT/.venv/bin/python" "$(command -v python3 || true)"; do
  if [ -n "$candidate" ] && [ -x "$candidate" ]; then
    LEAK_PY="$candidate"
    break
  fi
done

if [ ! -f "$LEAK_SCANNER" ] || [ -z "$LEAK_PY" ]; then
  REASON=""
  [ ! -f "$LEAK_SCANNER" ] && REASON="$LEAK_SCANNER is missing"
  [ -z "$LEAK_PY" ] && REASON="${REASON:+$REASON; }no python interpreter found"
  echo "⚠ GitReins Tier 1: leak scan SKIPPED — $REASON (the suite guard still covers this tree)" >&2
else
  LEAK_FAILED=0
  while IFS= read -r file; do
    [ ! -f "$file" ] && continue
    git check-ignore -q "$file" 2>/dev/null && continue
    if is_ignored "$file"; then
      echo "  (skipped by .gitreins/secrets-ignore: $file)"
      continue
    fi
    set +e
    HITS=$("$LEAK_PY" "$LEAK_SCANNER" --scan "$file" 2>/dev/null)
    LEAK_RC=$?
    set -e
    if [ -n "$HITS" ]; then
      echo "$HITS"
      echo "⚠ PRIVATE HOST ADDRESS in $file" >&2
      LEAK_FAILED=1
    elif [ $LEAK_RC -ne 0 ]; then
      echo "⚠ leak scan could not read $file (skipped)" >&2
    fi
  done <<< "$STAGED"

  if [ $LEAK_FAILED -eq 1 ]; then
    echo "" >&2
    echo "COMMIT BLOCKED: private host address found in staged files" >&2
    echo "  Publish the host NAME, never its address (e.g. 'bunker-las-03')." >&2
    exit 1
  fi
  echo "✓ GitReins Tier 1: Private-host leak scan — PASS"
fi

# ── 3. The quality harness (gitreins guard) ──────────────
# The verdict must be VISIBLE — the engine's own output is teed, never
# swallowed — and the rc is handled explicitly, because `set -e` above would
# otherwise turn every non-zero verdict into an indistinguishable abort.
if ! command -v gitreins >/dev/null 2>&1; then
  echo "" >&2
  echo "⚠ GitReins Tier 1: harness did NOT run — engine not found ('gitreins' is not on PATH)." >&2
  echo "  lint / tests / lsp did NOT grade this commit (the secrets scan above did)." >&2
  echo "  Install the engine (pipx install gitreins) and re-commit to be graded." >&2
  exit 0
fi

GUARD_LOG="$(mktemp "${TMPDIR:-/tmp}/gitreins-guard.XXXXXX" 2>/dev/null || true)"

# Repo venv FIRST: GitReins resolves its lsp lane's tool (`pylsp`) from PATH
# only, and a lane that finds no tool is a SKIP (docs/GITREINS.md, "Running it").
set +e
if [ -n "$GUARD_LOG" ]; then
  trap 'rm -f "$GUARD_LOG"' EXIT
  PATH="$ROOT/.venv/bin:$PATH" gitreins guard 2>&1 | tee "$GUARD_LOG"
  RC=${PIPESTATUS[0]}
else
  # An unwritable TMPDIR must not turn the gate into an outage: run the guard
  # anyway (only the per-lane skip detail is lost).
  echo "⚠ GitReins Tier 1: no writable temp file for the guard log — running the guard uncaptured." >&2
  PATH="$ROOT/.venv/bin:$PATH" gitreins guard
  RC=$?
fi
set -e

case "$RC" in
  0)
    echo "✓ GitReins Tier 1: PASS (gitreins guard)"
    exit 0
    ;;
  1)
    echo "" >&2
    echo "COMMIT BLOCKED: gitreins guard reported FAIL" >&2
    echo "  Fix the findings above, re-stage, and commit again." >&2
    echo "  Do NOT reach for --no-verify on a code change (AGENTS.md)." >&2
    exit 1
    ;;
  2)
    SKIPS=""
    if [ -n "$GUARD_LOG" ]; then
      SKIPS="$(grep -E 'skipped' "$GUARD_LOG" 2>/dev/null || true)"
    fi
    echo "" >&2
    echo "⚠ GitReins Tier 1: DEGRADED (gitreins guard exit 2) — the commit PROCEEDS anyway." >&2
    echo "  A DEGRADED run means a substantive lane did NO work, not that a lane failed." >&2
    if [ -n "$SKIPS" ]; then
      echo "  Lanes that graded nothing this run:" >&2
      printf '%s\n' "$SKIPS" | while IFS= read -r line; do
        echo "    $line" >&2
      done
    else
      echo "  (the engine named no individual skip lines — read the output above)" >&2
    fi
    echo "  Repair the skipped lane (pip install -e \".[dev]\" provides pylsp) and re-guard." >&2
    exit 0
    ;;
  *)
    echo "" >&2
    echo "⚠ GitReins Tier 1: harness produced no verdict (gitreins guard exit $RC) — the commit PROCEEDS anyway." >&2
    echo "  lint / tests / lsp did NOT grade this commit (the secrets scan above did)." >&2
    exit 0
    ;;
esac
