#!/bin/sh
# pre-push hook: chain to any pre-existing user-managed pre-push hook
# (e.g., per-repo discretion enforcement at .git/hooks/pre-push), then
# enforce branch freshness against origin/main.
#
# Aborts the push if the branch's merge-base with origin/main is older than
# AELF_PRE_PUSH_FRESHNESS_HOURS (default: 4 hours), so feature branches
# don't drift out of sync with main during long-running parallel sessions.
#
# Skipped on:
#   - pushes to main itself
#   - branch deletion (local sha = 40 zeros)
#   - when ALLOW_STALE_BRANCH_PUSH=1 is set in the environment (logs to stderr)
#
# Chain: enabling .githooks/pre-push via `git config core.hooksPath .githooks`
# displaces any pre-existing .git/hooks/pre-push from git's search path. To
# preserve user-managed hooks (defense in depth), this script first execs
# any executable .git/hooks/pre-push it finds and propagates its exit code,
# then runs the freshness check.
#
# Installed via:  git config core.hooksPath .githooks
# Or run scripts/setup-hooks.sh.
set -eu

remote_name="${1:-origin}"

# Buffer stdin once so it can be replayed to both the chained hook and the
# freshness check below. POSIX sh has neither <(...) process substitution
# nor <<< herestrings, so a temp file is the portable carrier.
stdin_buf="$(mktemp)"
trap 'rm -f "$stdin_buf"' EXIT INT TERM HUP
cat > "$stdin_buf"

# Chain to user-managed pre-push hook, if present and not this same file.
USER_HOOK="$(git rev-parse --git-path hooks/pre-push 2>/dev/null || echo "")"
if [ -n "$USER_HOOK" ] && [ -x "$USER_HOOK" ]; then
    user_hook_dir="$(cd "$(dirname "$USER_HOOK")" 2>/dev/null && pwd || echo "")"
    self_dir="$(cd "$(dirname "$0")" 2>/dev/null && pwd || echo "")"
    user_hook_abs="${user_hook_dir}/$(basename "$USER_HOOK")"
    self_abs="${self_dir}/$(basename "$0")"
    if [ "$user_hook_abs" != "$self_abs" ]; then
        chained_rc=0
        "$USER_HOOK" "$@" < "$stdin_buf" || chained_rc=$?
        if [ "$chained_rc" -ne 0 ]; then
            exit "$chained_rc"
        fi
    fi
fi

freshness_hours="${AELF_PRE_PUSH_FRESHNESS_HOURS:-}"
if [ -z "$freshness_hours" ]; then
    cfg=$(git config --get aelfrice.prepushFreshnessHours 2>/dev/null || echo "")
    freshness_hours="${cfg:-4}"
fi

# Validate as positive integer (POSIX-compatible).
case "$freshness_hours" in
    ''|*[!0-9]*)
        echo "pre-push: invalid AELF_PRE_PUSH_FRESHNESS_HOURS / aelfrice.prepushFreshnessHours: '$freshness_hours' (want positive integer); falling back to 4." >&2
        freshness_hours=4
        ;;
esac

z40="0000000000000000000000000000000000000000"

fetch_attempted=0
ensure_origin_main() {
    if [ "$fetch_attempted" -eq 1 ]; then
        return
    fi
    fetch_attempted=1
    # Best-effort fetch; offline pushes shouldn't block on this. If fetch
    # fails we use whatever origin/main we already have.
    git fetch --quiet "$remote_name" main 2>/dev/null || true
}

fail=0
while read -r local_ref local_sha remote_ref _remote_sha; do
    [ -z "${local_ref:-}" ] && continue

    # Branch deletion: local sha is all zeros — allow.
    [ "$local_sha" = "$z40" ] && continue

    # Skip pushes to main itself.
    case "$remote_ref" in
        refs/heads/main) continue ;;
    esac

    ensure_origin_main

    # Resolve origin/main; if absent (fresh clone offline), skip silently.
    origin_main=$(git rev-parse --verify --quiet "$remote_name/main" || echo "")
    [ -z "$origin_main" ] && continue

    base=$(git merge-base "$local_sha" "$origin_main" 2>/dev/null || echo "")
    if [ -z "$base" ]; then
        # No common ancestor — treat as stale and require a rebase.
        age_hours_int=99999
    else
        base_committer_ts=$(git show -s --format=%ct "$base")
        now_ts=$(date +%s)
        age_seconds=$(( now_ts - base_committer_ts ))
        # Integer hour, rounded down. Subshell-free arithmetic.
        age_hours_int=$(( age_seconds / 3600 ))
    fi

    if [ "$age_hours_int" -ge "$freshness_hours" ]; then
        ref_short=$(printf '%s' "$local_ref" | sed 's|^refs/heads/||')
        echo "" >&2
        echo "pre-push aborted: branch '$ref_short' is ${age_hours_int}h behind ${remote_name}/main (threshold: ${freshness_hours}h)." >&2
        echo "  Run:  git fetch ${remote_name} main && git rebase ${remote_name}/main" >&2
        echo "  Then retry the push." >&2
        echo "  Override (emergencies):  ALLOW_STALE_BRANCH_PUSH=1 git push ..." >&2
        echo "" >&2
        fail=1
    fi
done < "$stdin_buf"

if [ "$fail" -ne 0 ]; then
    if [ "${ALLOW_STALE_BRANCH_PUSH:-0}" = "1" ]; then
        echo "pre-push OVERRIDE: ALLOW_STALE_BRANCH_PUSH=1 — pushing stale branch anyway." >&2
        exit 0
    fi
    exit 1
fi

exit 0
