#!/bin/bash
# SPDX-FileCopyrightText: 2026 Daniel J. Mazure
# SPDX-License-Identifier: MIT
#
# pre-push hook — runs heavyweight CI simulation before pushing tags.
#
# On regular pushes: nothing extra (pre-commit already covers it).
# On tag pushes (v*): runs tools/test-ci-local.sh Docker simulation
# to catch CI environment drift before a release hits GitHub Actions.
#
# Install: git config core.hooksPath already points to sdk/infra/hooks/

set -eo pipefail

# ── Context Guard: SDK vs Consumer (RTL-P3.536) ──
# core.hooksPath makes git run THIS hook for consumer projects too. In a
# consumer context, delegate to project-pre-push (which runs the declared
# hooks.pre_push.tests). The tag-push Docker CI below is the SDK repo's own
# release gate, not a consumer concern. Must run BEFORE the stdin refspec
# loop — exec hands stdin (the refspecs) straight to the delegate.
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
IS_CONSUMER=false
if [ -d "vendor/routertl" ]; then
    IS_CONSUMER=true
elif [ ! -d "sdk" ]; then
    # RTL-P2.641: any repo without an sdk/ tree is a consumer. The old test
    # required project.yml AT ROOT, so IP repos (ip.yml at root, project.yml in
    # example/) fell through to the SDK release gate below and bricked their
    # v* tag pushes (REPO_ROOT mis-resolved to a dir with no test-ci-local.sh).
    IS_CONSUMER=true
fi
if [ "$IS_CONSUMER" = true ]; then
    if [ -f "$HOOK_DIR/project-pre-push" ]; then
        exec "$HOOK_DIR/project-pre-push"
    fi
    # Consumer with no project-level pre-push hook: the SDK Docker-CI release
    # gate is the SDK repo's own concern, never a consumer's. Nothing to do.
    exit 0
fi

# ── Detect tag push ──
# Git passes refspec info via stdin: <local ref> <local sha> <remote ref> <remote sha>
TAG_PUSH=false
ZERO_SHA=0000000000000000000000000000000000000000
while read -r local_ref local_sha remote_ref remote_sha; do
    if [[ "$remote_ref" == refs/tags/v* ]]; then
        # A zero local object id means this ref is being deleted. There is no
        # release content to validate, so do not spend a Docker gate cycle.
        if [ "$local_sha" = "$ZERO_SHA" ]; then
            continue
        fi
        TAG_PUSH=true
        TAG_NAME="${remote_ref#refs/tags/}"
        TAG_SHA="$local_sha"
        break
    fi
done

if [ "$TAG_PUSH" = false ]; then
    # Normal branch push — nothing extra needed
    exit 0
fi

echo ""
echo "═══════════════════════════════════════════════════════════"
echo "  🏷️  Tag push detected: $TAG_NAME"
echo "  Running Docker CI simulation before release push..."
echo "═══════════════════════════════════════════════════════════"
echo ""

REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"

# Check Docker availability
if ! command -v docker &>/dev/null || ! docker info &>/dev/null 2>&1; then
    echo "⚠️  Docker not available — skipping CI simulation."
    echo "   Run './tools/test-ci-local.sh' manually before pushing."
    exit 0
fi

# ── Gate the TAGGED tree, not the working tree (RTL-P3.2000) ──
# The gate used to run "$REPO_ROOT/tools/test-ci-local.sh all", which mounts
# whatever the pushing checkout holds. Measured on v5.0.0 (2026-10-07): the
# checkout sat at 2b9c67cde while the tag pointed at 6a0a4d5df, two commits
# later, so the gate certified a tree that was never released; a second push
# from a checkout that had moved AHEAD of the tag ran ~100 min and refused a
# sweep-green tag. Peel the pushed object (an annotated tag is a tag object)
# to its commit, export that commit into a detached scratch worktree, and run
# the tagged tree's own test-ci-local.sh there. The scratch lives under a
# private base, never shared /tmp, and is removed on exit.
TAG_COMMIT=$(git -C "$REPO_ROOT" rev-parse --verify --quiet "${TAG_SHA}^{commit}") || {
    echo "❌ $TAG_NAME ($TAG_SHA) does not resolve to a commit — aborting tag push."
    exit 1
}

PRE_PUSH_TMPBASE="${ROUTERTL_PRE_PUSH_TMPBASE:-${XDG_CACHE_HOME:-$HOME/.cache}/routertl/pre-push}"
mkdir -p "$PRE_PUSH_TMPBASE"
chmod 700 "$PRE_PUSH_TMPBASE"
SCRATCH_DIR="$(mktemp -d "${PRE_PUSH_TMPBASE%/}/tag-gate-XXXXXX")"
TAG_TREE="$SCRATCH_DIR/tree"
cleanup_tag_tree() {
    git -C "$REPO_ROOT" worktree remove --force "$TAG_TREE" >/dev/null 2>&1 || true
    rm -rf -- "$SCRATCH_DIR"
    git -C "$REPO_ROOT" worktree prune >/dev/null 2>&1 || true
}
trap cleanup_tag_tree EXIT

if ! git -C "$REPO_ROOT" worktree add --quiet --detach "$TAG_TREE" "$TAG_COMMIT"; then
    echo "❌ Could not export $TAG_NAME ($TAG_COMMIT) into $TAG_TREE — aborting tag push."
    exit 1
fi
echo "── Gate tree: $TAG_NAME at $TAG_COMMIT (detached worktree $TAG_TREE)"

# RTL-P2.641: the Docker CI gate is SDK-repo-only and needs the CI-sim script.
# If the tagged tree has none (hook reached a non-SDK context), skip gracefully
# — never abort a tag push on a missing internal script.
if [ ! -x "$TAG_TREE/tools/test-ci-local.sh" ]; then
    echo "⚠️  tools/test-ci-local.sh not found in $TAG_NAME — skipping CI simulation for $TAG_NAME."
    exit 0
fi

# Git exports GIT_DIR (and friends) into hook processes. Inherited, they would
# point every git call in the CI script back at the pushing checkout's HEAD,
# so scrub them and let the scratch worktree's own .git decide. TMPDIR and the
# sweep-view base follow the private scratch, never shared /tmp.
mkdir -p "$SCRATCH_DIR/tmp"

# Run the full CI simulation (both tiers)
if (
    unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_OBJECT_DIRECTORY \
          GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_PREFIX GIT_COMMON_DIR
    export TMPDIR="$SCRATCH_DIR/tmp"
    export ROUTERTL_CI_VIEW_TMPBASE="${ROUTERTL_CI_VIEW_TMPBASE:-$SCRATCH_DIR/tmp}"
    cd "$TAG_TREE" && ./tools/test-ci-local.sh all
); then
    echo ""
    echo "✅ Docker CI simulation passed on $TAG_COMMIT — safe to push $TAG_NAME"
else
    echo ""
    echo "❌ Docker CI simulation FAILED — aborting tag push."
    echo "   Fix the issues above before pushing $TAG_NAME."
    echo "   Diagnose the failed stage first; follow the documented release-blocker flow if needed."
    exit 1
fi
