#!/bin/sh
# Husky pre-push hook — SDLC guard + fast gates + E2E evidence + skill-invocation sentinel + compliance validator.
#
# Runs five checks before allowing a push:
# 0. SDLC guard — branch-name check for manual execution detection (#231)
# 1. TypeScript check (fast gate)
# 2. E2E evidence check — if UI-facing files changed, verify Playwright ran
# 3. Skill-invocation sentinel — if feat/fix/refactor/perf commits present,
#    verify sdlc-implementer was invoked
# 4. Compliance artifact validation — if feat/fix/refactor/perf commits present,
#    run validate-compliance-artifacts.sh to catch missing evidence files
#
# Bypass with --no-verify (last resort, not a habit).
#
# Install: cp this file to .husky/pre-push && chmod +x .husky/pre-push

set -eu

# ── 0. SDLC guard (devaudit-installer#231) ──────────────────────────
# Fast-fail: if on a tracked branch (feat/fix/refactor/perf) and the
# sentinel is missing, bail before running expensive test suites.
if [ -f scripts/sdlc-guard.sh ]; then
  bash scripts/sdlc-guard.sh
fi

# ── 1. TypeScript check ──────────────────────────────────────────────
echo "Pre-push: running TypeScript check..."
npx tsc --noEmit
if [ $? -ne 0 ]; then
  echo ""
  echo "ERROR: TypeScript check failed. Fix type errors before pushing."
  echo "Run 'npx tsc --noEmit' to see all errors."
  exit 1
fi
echo "Pre-push: TypeScript check passed."

# Capture stdin once — all subsequent checks iterate over the captured refs.
# Previous code had two `while read` loops; the second consumed nothing
# because stdin was already exhausted by the first (devaudit-installer#278).
PUSH_REFS="$(cat)"
PUSH_REFS_FILE=$(mktemp)
trap 'rm -f "$PUSH_REFS_FILE"' EXIT HUP INT TERM
printf '%s\n' "$PUSH_REFS" > "$PUSH_REFS_FILE"

INTEGRATION_BRANCH=$(jq -r '.integration_branch // "develop"' sdlc-config.json 2>/dev/null || echo "develop")

# Compute the commit range for a given (remote_sha, local_sha) pair. For a
# brand-new ref (remote_sha all-zeros), a bare local_sha walks the entire
# history reachable from that commit, not just the commits being pushed —
# use the merge-base against the integration branch instead, falling back
# to the bare SHA only if the merge-base can't be resolved
# (devaudit-installer#741). If origin/$INTEGRATION_BRANCH itself isn't
# resolvable locally (e.g. a fork/mirror that hasn't fetched it, or a
# misconfigured integration_branch name), try origin/HEAD before giving up
# to the bare SHA (devaudit-installer#743).
compute_range() {
  _remote_sha="$1"
  _local_sha="$2"
  if [ "$_remote_sha" = "0000000000000000000000000000000000000000" ]; then
    _base=$(git merge-base "origin/$INTEGRATION_BRANCH" "$_local_sha" 2>/dev/null || true)
    if [ -z "$_base" ]; then
      _base=$(git merge-base origin/HEAD "$_local_sha" 2>/dev/null || true)
    fi
    if [ -n "$_base" ]; then
      echo "${_base}..${_local_sha}"
    else
      echo "$_local_sha"
    fi
  else
    echo "${_remote_sha}..${_local_sha}"
  fi
}

# ── 2. E2E evidence check (devaudit-installer#226) ───────────────────
# Only fires if UI-facing files are in the push. Checks for either
# playwright-report/ directory or .e2e-gate-passed sentinel (written by
# e2e-test-engineer after a successful run).
# Also checks .e2e-evidence-wired sentinel if e2e spec files changed
# (written by e2e-test-engineer Phase 5½ after validating tagTest/evidenceShot).
# Scoped to integration-branch pushes (correct for the normal workflow).

while read -r local_ref local_sha remote_ref remote_sha; do
  [ -n "${local_ref:-}" ] || continue
  # Check if pushing to the integration branch
  case "$remote_ref" in
    *"refs/heads/$INTEGRATION_BRANCH") ;;
    *) continue ;;
  esac

  # Determine the commit range being pushed
  RANGE=$(compute_range "$remote_sha" "$local_sha")

  # Check for UI-facing files in the push
  UI_FILES=$(git diff --name-only "$RANGE" -- 'app/**/*.tsx' 'src/**/*.tsx' 'pages/**/*.tsx' 'app/**/*.jsx' 'src/**/*.jsx' 'pages/**/*.jsx' 2>/dev/null || true)

  if [ -n "$UI_FILES" ]; then
    E2E_PASSED=false
    SENTINEL_REQS=""
    if [ -f .e2e-gate-passed ]; then
      E2E_PASSED=true
      # New sentinel format (devaudit-installer#578): line 1 is the
      # PASSED/NOT_NEEDED status, subsequent lines are REQ IDs this run
      # covered. An old-format sentinel (status line only) yields an empty
      # list here and the cross-check below degrades gracefully.
      SENTINEL_REQS=$(tail -n +2 .e2e-gate-passed 2>/dev/null | grep -oE '^REQ-[0-9]+' || true)
    elif [ -d playwright-report ] && [ "$(find playwright-report -type f -newer .git/HEAD 2>/dev/null | head -1)" ]; then
      E2E_PASSED=true
    fi

    if [ "$E2E_PASSED" = "false" ]; then
      echo ""
      echo "ERROR: E2E gate was not run before pushing."
      echo "       UI-facing files changed in this push:"
      echo "$UI_FILES" | sed 's/^/         /'
      echo ""
      echo "       Run 'npx playwright test' (or invoke e2e-test-engineer) before pushing."
      echo "       Bypass with --no-verify (last resort, not a habit)."
      exit 1
    fi

    # devaudit-installer#578: a run happened, but that alone doesn't prove it
    # covers THIS push's REQ(s) — a stale, unrelated regression run satisfied
    # this check on wawagardenbar-app REQ-095 while 0% of its planned
    # Playwright coverage existed. Extract in-scope REQs for the push (same
    # Ref: REQ-XXX lookup as check 3) and require each to be tagged by an
    # actual spec file, regardless of report freshness or sentinel presence;
    # additionally flag when the sentinel's own REQ list (if present) omits
    # a REQ that IS tagged elsewhere, since that means the run that produced
    # the sentinel didn't cover it even though a spec exists.
    PUSH_REQS=$(git log "$RANGE" --format='%B' 2>/dev/null \
      | grep -ioE '(\[REQ-[0-9]+\]|Ref:[[:space:]]*REQ-[0-9]+)' \
      | grep -oiE 'REQ-[0-9]+' | tr '[:lower:]' '[:upper:]' | sort -u || true)

    MISSING_REQS=""
    for REQ_ID in $PUSH_REQS; do
      if ! grep -rl "@requirement $REQ_ID" e2e/ --include="*.spec.ts" >/dev/null 2>&1; then
        MISSING_REQS="${MISSING_REQS} ${REQ_ID}(no tagged spec)"
      elif [ -n "$SENTINEL_REQS" ] && ! echo "$SENTINEL_REQS" | grep -qx "$REQ_ID"; then
        MISSING_REQS="${MISSING_REQS} ${REQ_ID}(not in .e2e-gate-passed run)"
      fi
    done

    if [ -n "$MISSING_REQS" ]; then
      echo ""
      echo "ERROR: E2E gate ran, but not for this push's REQ(s):${MISSING_REQS}"
      echo "       UI-facing files changed, and at least one in-scope REQ has no spec"
      echo "       tagged @requirement REQ-XXX, or wasn't covered by the run that wrote"
      echo "       .e2e-gate-passed. A recent/unrelated Playwright run does not satisfy"
      echo "       this (devaudit-installer#578)."
      echo "       Invoke e2e-test-engineer to write and run REQ-scoped coverage."
      echo "       Bypass with --no-verify (last resort, not a habit)."
      exit 1
    fi

    echo "Pre-push: E2E evidence check passed."
  fi

  # Check for E2E spec file changes — require .e2e-evidence-wired sentinel
  SPEC_FILES=$(git diff --name-only "$RANGE" -- 'e2e/**/*.spec.ts' 2>/dev/null || true)

  if [ -n "$SPEC_FILES" ]; then
    if [ ! -f .e2e-evidence-wired ]; then
      echo ""
      echo "ERROR: Evidence wiring validation (Phase 5½) was not run."
      echo "       E2E spec files changed in this push:"
      echo "$SPEC_FILES" | sed 's/^/         /'
      echo ""
      echo "       Invoke e2e-test-engineer to validate tagTest() and evidenceShot() calls."
      echo "       Bypass with --no-verify (last resort, not a habit)."
      exit 1
    fi
    echo "Pre-push: E2E evidence wiring check passed."
  fi
done < "$PUSH_REFS_FILE"

# ── 3. Skill-invocation sentinel (devaudit-installer#226) ────────────
# If feat/fix/refactor/perf commits are in the push, verify the
# sdlc-implementer skill was invoked (writes .sdlc-implementer-invoked).
# Housekeeping types (docs/chore/ci/build/test/revert) are exempt.
# Fires on ANY push containing tracked commits, regardless of target branch
# (devaudit-installer#278: previously only checked integration-branch pushes,
# but feature-branch pushes also need the sentinel).
TRACKED_TYPES='^(feat|fix|refactor|perf)(\(.+\))?!?:'
HAS_TRACKED=false

# Parse captured refs in the current shell so HAS_TRACKED propagates.
while read -r local_ref local_sha remote_ref remote_sha; do
  [ -n "${local_ref:-}" ] || continue
  RANGE=$(compute_range "$remote_sha" "$local_sha")
  COMMITS=$(git log "$RANGE" --format='%s' 2>/dev/null || true)
  if echo "$COMMITS" | grep -qE "$TRACKED_TYPES"; then
    HAS_TRACKED=true
  fi
done < "$PUSH_REFS_FILE"

if [ "$HAS_TRACKED" = "true" ]; then
  if [ ! -f .sdlc-implementer-invoked ]; then
    echo ""
    echo "ERROR: sdlc-implementer skill was not invoked."
    echo "       This push contains feat/fix/refactor/perf commits which require"
    echo "       the SDLC skill flow. Invoke sdlc-implementer to drive the process,"
    echo "       or use --no-verify to bypass (not recommended — CI will also check"
    echo "       RTM provenance via validate-commits.sh)."
    exit 1
  fi
  echo "Pre-push: skill-invocation sentinel check passed."

  # ── 3b. Phase-progression validation (devaudit-installer#278) ──────
  # If the commit touches Phase 3 artifacts (compliance/evidence/REQ-XXX/),
  # the sentinel must contain a phase "3" record. If it touches Phase 5
  # close-out (approved-releases/ or an RTM row flipping to RELEASED),
  # sentinel must have phase "5".
  while read -r local_ref local_sha remote_ref remote_sha; do
    [ -n "${local_ref:-}" ] || continue
    RANGE=$(compute_range "$remote_sha" "$local_sha")

    PHASE3_FILES=$(git diff --name-only "$RANGE" -- 'compliance/evidence/REQ-*/' 2>/dev/null || true)
    PHASE5_APPROVED_DIR=$(git diff --name-only "$RANGE" -- 'compliance/approved-releases/' 2>/dev/null || true)
    # RTM.md alone is not a Phase-5 signal — Phase 1 legitimately adds new
    # DRAFT rows (devaudit-installer#744). Only an *added* line carrying a
    # RELEASED status transition counts as close-out.
    PHASE5_RTM_RELEASED=""
    if git diff "$RANGE" -- 'compliance/RTM.md' 2>/dev/null | grep -qE '^\+.*\bRELEASED\b'; then
      PHASE5_RTM_RELEASED="yes"
    fi
    PHASE5_FILES="${PHASE5_APPROVED_DIR}${PHASE5_RTM_RELEASED}"

    if [ -n "$PHASE3_FILES" ]; then
      PHASES_IN_SENTINEL=$(jq -r '.[].currentPhase // .[].phase // empty' .sdlc-implementer-invoked 2>/dev/null || true)
      if ! echo "$PHASES_IN_SENTINEL" | grep -q '"3"\|3'; then
        echo ""
        echo "ERROR: Commit touches Phase 3 artifacts (compliance/evidence/) but"
        echo "       the sentinel has no phase 3 record. Invoke sdlc-implementer"
        echo "       at Phase 3 before pushing these changes."
        exit 1
      fi
    fi

    if [ -n "$PHASE5_FILES" ]; then
      PHASES_IN_SENTINEL=$(jq -r '.[].currentPhase // .[].phase // empty' .sdlc-implementer-invoked 2>/dev/null || true)
      if ! echo "$PHASES_IN_SENTINEL" | grep -q '"5"\|5'; then
        echo ""
        echo "ERROR: Commit touches Phase 5 close-out artifacts (approved-releases/ or RTM)"
        echo "       but the sentinel has no phase 5 record. Invoke sdlc-implementer"
        echo "       at Phase 5 before pushing these changes."
        exit 1
      fi
    fi
  done < "$PUSH_REFS_FILE"
  echo "Pre-push: phase-progression check passed."
fi

# ── 4. Compliance artifact validation (devaudit-installer#226) ──────
# If feat/fix/refactor/perf commits are in the push, run
# validate-compliance-artifacts.sh to catch missing test-scope.md,
# test-plan.md, implementation-plan.md in compliance/evidence/.
# Reuses HAS_TRACKED from check 3.
if [ "$HAS_TRACKED" = "true" ]; then
  if [ -f scripts/validate-compliance-artifacts.sh ]; then
    echo "Pre-push: running compliance artifact validation..."
    if ! bash scripts/validate-compliance-artifacts.sh "origin/$INTEGRATION_BRANCH"; then
      echo ""
      echo "ERROR: Compliance artifact validation failed."
      echo "       Missing or incomplete artifacts in compliance/evidence/."
      echo "       Run 'bash scripts/validate-compliance-artifacts.sh' locally to see details."
      echo "       Bypass with --no-verify (last resort, not a habit)."
      exit 1
    fi
    echo "Pre-push: compliance artifact validation passed."
  else
    echo "Pre-push: compliance artifact validation skipped (script not found)."
  fi
fi

echo "Pre-push: all checks passed."
