#!/usr/bin/env bash
# ============================================================
# Finlet — pre-push hook (path-aware)
# ============================================================
#
# Runs the local CI gate before a push is allowed to leave the
# laptop. Mirrors the GitHub Actions pipeline order so anything
# that would break CI breaks here first:
#
#   1. ruff check finlet/ tests/        (~3s)  — ALWAYS runs (safety net)
#   2. mypy finlet/ --ignore-missing-imports  (~15s) — only if .py/.toml changed
#   3. pytest tests/ -q --tb=short      (~5min) — only if .py/.toml changed
#   4. e2e smoke (6 journeys)           (~90-120s) — only if API or dashboard changed
#
# Path-aware gating added 2026-05-11 — see
# `docs/decisions/pre-push-path-aware-stages.md`. Closes the gap
# where doc-only or workflow-only commits ate 7-10 min of pytest+e2e
# they couldn't possibly regress. Team D lost 10+ min on a HTML-only
# push during the 2026-05-10 iteration; this is the fix.
#
# Step 4 (e2e smoke) was added 2026-05-03 — see
# `docs/decisions/e2e-in-pre-push-gate.md`.
#
# This file is checked into `scripts/git-hooks/` so it stays in
# version control. To activate it locally, run:
#
#     bash scripts/setup-git-hooks.sh
#
# The setup script symlinks this file into `.git/hooks/pre-push`.
# Per-step skip env vars:
#   SKIP_E2E_SMOKE=1 git push           # bypass step 4 only
#   PRE_PUSH_FORCE_ALL=1 git push       # disable path-aware skipping
# ============================================================

set -uo pipefail

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

# Activate the virtualenv
if [ ! -f .venv/bin/activate ]; then
    echo "ERROR: .venv not found at $REPO_ROOT/.venv"
    echo "Create it with: python3 -m venv .venv && pip install -e '.[dev]'"
    exit 1
fi
# shellcheck source=/dev/null
source .venv/bin/activate

# ────────────────────────────────────────────────────────────
# Path-aware change detection
# ────────────────────────────────────────────────────────────
# Compute the set of files changed since this branch diverged from
# origin/main. If we can't determine the merge-base (e.g. brand-new
# repo, detached head, or origin missing), fall back to HEAD~1 and
# ultimately to running ALL stages (safe default).
#
# Honors PRE_PUSH_FORCE_ALL=1 as a kill switch: if set, every stage
# runs regardless of which files changed (useful when a doc change
# secretly touches a code path the heuristic can't see, e.g. a CI
# yaml file that affects test selection).

RANGE_BASE=""
if [ "${PRE_PUSH_FORCE_ALL:-0}" = "1" ]; then
    CHANGED=""
    NEEDS_PYTHON="forced"
    NEEDS_E2E="forced"
    echo "(PRE_PUSH_FORCE_ALL=1 — running all stages)"
else
    if RANGE_BASE=$(git merge-base HEAD origin/main 2>/dev/null); then
        :
    elif RANGE_BASE=$(git rev-parse HEAD~1 2>/dev/null); then
        :
    else
        RANGE_BASE=""
    fi

    if [ -n "$RANGE_BASE" ]; then
        CHANGED=$(git diff --name-only "$RANGE_BASE"..HEAD 2>/dev/null || true)
    else
        # No usable base — run everything to stay safe.
        CHANGED=""
    fi

    if [ -z "$RANGE_BASE" ]; then
        NEEDS_PYTHON="no-base"
        NEEDS_E2E="no-base"
    else
        # Python: anything that affects type-checking or test outcomes.
        NEEDS_PYTHON=$(echo "$CHANGED" | grep -E '\.(py|toml)$' || true)
        # E2E: anything that affects user-facing API or dashboard surface.
        NEEDS_E2E=$(echo "$CHANGED" | grep -E '^(finlet/api/|dashboard/)' || true)
    fi
fi

echo "=== Pre-push checks ==="
if [ -n "$RANGE_BASE" ] && [ "${PRE_PUSH_FORCE_ALL:-0}" != "1" ]; then
    CHANGED_COUNT=$(echo "$CHANGED" | grep -c . || true)
    echo "(diff range: ${RANGE_BASE:0:8}..HEAD — $CHANGED_COUNT files changed)"
fi
echo ""

# ────────────────────────────────────────────────────────────
# 1. Lint (always — 3s safety net)
# ────────────────────────────────────────────────────────────
echo "[1/4] ruff check finlet/ tests/"
if ! ruff check finlet/ tests/; then
    echo ""
    echo "FAILED: ruff found lint errors. Fix them before pushing."
    exit 1
fi
echo "PASSED"
echo ""

# ────────────────────────────────────────────────────────────
# 2. Type check (skipped if no .py/.toml changes)
# ────────────────────────────────────────────────────────────
if [ -z "$NEEDS_PYTHON" ]; then
    echo "[2/4] mypy — SKIPPED (no .py/.toml changes since origin/main)"
    echo ""
else
    echo "[2/4] mypy finlet/ --ignore-missing-imports"
    if ! mypy finlet/ --ignore-missing-imports; then
        echo ""
        echo "FAILED: mypy found type errors. Fix them before pushing."
        exit 1
    fi
    echo "PASSED"
    echo ""
fi

# ────────────────────────────────────────────────────────────
# 3. Tests (skipped if no .py/.toml changes)
# ────────────────────────────────────────────────────────────
if [ -z "$NEEDS_PYTHON" ]; then
    echo "[3/4] pytest — SKIPPED (no .py/.toml changes since origin/main)"
    echo ""
else
    echo "[3/4] pytest tests/ -q --tb=short"
    if ! pytest tests/ -q --tb=short; then
        echo ""
        echo "FAILED: tests did not pass. Fix them before pushing."
        exit 1
    fi
    echo "PASSED"
    echo ""
fi

# ────────────────────────────────────────────────────────────
# 4. E2E smoke (skipped if no API or dashboard changes)
# ────────────────────────────────────────────────────────────
if [ "${SKIP_E2E_SMOKE:-0}" = "1" ]; then
    echo "[4/4] e2e smoke — SKIPPED (SKIP_E2E_SMOKE=1)"
    echo ""
elif [ -z "$NEEDS_E2E" ]; then
    echo "[4/4] e2e smoke — SKIPPED (no finlet/api/ or dashboard/ changes since origin/main)"
    echo ""
else
    echo "[4/4] scripts/e2e-smoke.sh"
    SMOKE_SCRIPT="$REPO_ROOT/scripts/e2e-smoke.sh"
    if [[ ! -x "$SMOKE_SCRIPT" ]]; then
        echo "WARN: $SMOKE_SCRIPT not executable — skipping (run chmod +x to enable)" >&2
    elif ! bash "$SMOKE_SCRIPT"; then
        echo ""
        echo "FAILED: e2e smoke failed. Fix the regression or set SKIP_E2E_SMOKE=1 to bypass."
        exit 1
    fi
    echo ""
fi

echo "=== All pre-push checks passed ==="
