#!/bin/bash
# Pre-push hook — version check + post-push PyPI publish
#
# Flow:
#   1. Block direct push to main branch
#   2. Only proceed for main-dev pushes (other branches: skip cleanly)
#   3. Auto-bump version if unchanged in recent commits
#   4. Exit 0 → git push proceeds immediately
#   5. Background job publishes to PyPI after push finishes (if token present)
#
# Version bumping: auto-increments last segment if version unchanged.
# This hook may create one version-bump commit before the push.

# Recursion guard
if [ "${_SAGE_PP_RUNNING:-0}" = "1" ]; then exit 0; fi

# Publish mode: "public" → publish openly; "private" → internal only
PUBLISH_MODE=public

# Colors
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
BLUE='\033[0;34m'
DIM='\033[2m'
NC='\033[0m'

WANT_PUBLISH=false
REPO_DIR="$(pwd)"
REPO_NAME="$(basename "$REPO_DIR")"
PUBLISH_LOG="/tmp/${REPO_NAME}-publish-$$.log"
PUSHING_MAIN_DEV=false
PUSH_LOCAL_SHA=""

# --- Block direct push to main; detect main-dev push ---
while read -r local_ref local_sha remote_ref remote_sha; do
    if [ "$local_ref" = "refs/heads/main" ] || [ "$remote_ref" = "refs/heads/main" ]; then
        echo -e "${RED}✗ Direct push to main is forbidden${NC}"
        echo -e "${YELLOW}  Please push to main-dev first, then merge via PR.${NC}"
        exit 1
    fi
    if [ "$local_ref" = "refs/heads/main-dev" ] || [ "$remote_ref" = "refs/heads/main-dev" ]; then
        PUSHING_MAIN_DEV=true
        PUSH_LOCAL_SHA="$local_sha"
    fi
done

# Version check + publish only applies to main-dev pushes
if [ "$PUSHING_MAIN_DEV" != true ]; then
    exit 0
fi

# Safe read: falls back to default if /dev/tty is unavailable (SSH, IDE, etc.)
safe_read() {
    local varname="$1"
    local default="$2"
    if [ -t 0 ] || [ -c /dev/tty ] 2>/dev/null; then
        read -r "$varname" </dev/tty 2>/dev/null || eval "$varname=\"$default\""
    else
        eval "$varname=\"$default\""
    fi
}

# Find all _version.py files in repo
find_version_files() {
    find . -maxdepth 4 -name '_version.py' \
        -not -path '*/node_modules/*' \
        -not -path '*/.git/*' \
        -not -path '*/dist/*' \
        -not -path '*/.egg-info/*' \
        -not -path '*/build/*' \
        2>/dev/null
}

# Update version in pyproject.toml (static) and/or _version.py (dynamic)
update_version() {
    local old_version="$1"
    local new_version="$2"
    local updated=false

    if grep -q '^version = "' pyproject.toml 2>/dev/null; then
        sed -i "s/version = \"${old_version}\"/version = \"${new_version}\"/" pyproject.toml
        git add pyproject.toml
        updated=true
    fi

    while IFS= read -r VERSION_FILE; do
        if [ -f "$VERSION_FILE" ] && grep -q '__version__ = "' "$VERSION_FILE" 2>/dev/null; then
            sed -i "s/__version__ = \"${old_version}\"/__version__ = \"${new_version}\"/" "$VERSION_FILE"
            git add "$VERSION_FILE"
            updated=true
        fi
    done < <(find_version_files)

    if [ "$updated" = false ]; then
        echo -e "${RED}✗ Failed to update version (no version file found)${NC}"
        return 1
    fi

    return 0
}

# Auto-increment the last version component by 1 (X.Y.Z → X.Y.Z+1, X.Y.Z.N → X.Y.Z.N+1)
bump_patch() {
    local v="$1"
    local IFS='.'
    read -ra parts <<< "$v"
    local last_idx=$(( ${#parts[@]} - 1 ))
    parts[$last_idx]=$(( parts[$last_idx] + 1 ))
    echo "${parts[*]}"
}

# Check if PyPI token is available (no interaction needed)
has_pypi_token() {
    if [ -n "${TWINE_PASSWORD:-}" ] || [ -n "${TWINE_TOKEN:-}" ] || [ -n "${UV_PUBLISH_TOKEN:-}" ]; then
        return 0
    fi

    if [ -f "$HOME/.pypirc" ]; then
        if awk '''
            BEGIN { in_pypi = 0; found = 0 }
            /^[[:space:]]*\[pypi\][[:space:]]*$/ { in_pypi = 1; next }
            /^[[:space:]]*\[[^]]+\][[:space:]]*$/ { in_pypi = 0 }
            in_pypi && /^[[:space:]]*(password|token)[[:space:]]*=[[:space:]]*.+$/ { found = 1; exit }
            END { exit(found ? 0 : 1) }
        ''' "$HOME/.pypirc" 2>/dev/null; then
            return 0
        fi
    fi

    return 1
}

# Resolve publisher CLI command (binary first, then python -m fallback)
resolve_publisher_cmd() {
    if command -v sage-pypi-publisher &> /dev/null; then
        PUBLISH_CMD=(sage-pypi-publisher)
        return 0
    fi

    if command -v python3 &> /dev/null; then
        if python3 - <<'PY_HOOK' >/dev/null 2>&1
import importlib.util
import sys
sys.exit(0 if importlib.util.find_spec("pypi_publisher") else 1)
PY_HOOK
        then
            PUBLISH_CMD=(python3 -m pypi_publisher.cli)
            return 0
        fi
    fi

    return 1
}

# Schedule PyPI publish as background job after push completes
schedule_publish() {
    local version="$1"
    local package="$2"

    if ! resolve_publisher_cmd; then
        echo -e "${YELLOW}⚠ sage-pypi-publisher not found, skipping auto-publish${NC}"
        echo -e "${DIM}  Install: python -m pip install isage-pypi-publisher${NC}"
        return
    fi

    echo -e "${GREEN}📦 PyPI publish scheduled (runs after push)${NC}"
    echo -e "${DIM}  Log: tail -f ${PUBLISH_LOG}${NC}"

    (
        GIT_PID="$PPID"
        while kill -0 "$GIT_PID" 2>/dev/null; do
            sleep 1
        done
        sleep 1

        cd "$REPO_DIR" || exit 1

        # Verify the push actually landed before publishing
        remote_sha="$(git ls-remote origin "refs/heads/main-dev" | awk '{print $1}')"
        if [ -n "$PUSH_LOCAL_SHA" ] && [ -n "$remote_sha" ] && [ "$remote_sha" != "$PUSH_LOCAL_SHA" ]; then
            {
                echo "⏭ Skip publish: push SHA mismatch"
                echo "  expected=${PUSH_LOCAL_SHA}"
                echo "  remote=${remote_sha}"
            } >> "$PUBLISH_LOG" 2>&1
            exit 0
        fi

        {
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            echo "📦 Post-push: Building ${package} ${version}..."
            echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

            rm -rf dist/ build/ *.egg-info 2>/dev/null || true

            if "${PUBLISH_CMD[@]}" build . --upload --no-dry-run --mode "${PUBLISH_MODE}"; then
                echo ""
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
                echo "✓ Successfully uploaded ${package} ${version} to PyPI"
                echo "🔗 https://pypi.org/project/${package}/${version}/"
                echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
            else
                _pub_exit=$?
                echo ""
                echo "✗ Failed to upload to PyPI (exit code: ${_pub_exit})"
                echo "  Re-run: sage-pypi-publisher build . --upload --no-dry-run --mode ${PUBLISH_MODE}"
            fi
        } >> "$PUBLISH_LOG" 2>&1

        if grep -q "Successfully uploaded" "$PUBLISH_LOG" 2>/dev/null; then
            echo -e "\n${GREEN}✓ PyPI: ${package} ${version} published${NC}"
        else
            echo -e "\n${RED}✗ PyPI publish failed. See: ${PUBLISH_LOG}${NC}"
        fi
    ) &
    disown
}

# --- Main Logic ---

if [ ! -f pyproject.toml ]; then
    exit 0
fi

PACKAGE_NAME=$(grep -oP '^name = "\K[^"]+' pyproject.toml 2>/dev/null || echo "unknown")

# Get version: prefer _version.py (dynamic), fallback to pyproject.toml (static)
CURRENT_VERSION=""
while IFS= read -r VERSION_FILE; do
    if [ -f "$VERSION_FILE" ]; then
        CURRENT_VERSION=$(grep -oP '__version__ = "\K[^"]+' "$VERSION_FILE" 2>/dev/null || true)
        [ -n "$CURRENT_VERSION" ] && break
    fi
done < <(find_version_files)

if [ -z "$CURRENT_VERSION" ]; then
    CURRENT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml 2>/dev/null || true)
fi

if [ -z "$CURRENT_VERSION" ] || [ "$CURRENT_VERSION" = "unknown" ]; then
    exit 0
fi

# --- Version / Publish ---
# post-commit already bumps the BUILD digit on every commit, so by the time
# we push the version is always current.  The only thing pre-push needs to
# guard against is re-pushing a version that was already published to PyPI
# (e.g. after a failed push/publish on a previous attempt).

# Quick PyPI check (5s timeout, non-blocking)
# Exit codes: 0=version exists on PyPI, 1=not found, 2=network/other error
PYPI_CHECK_RESULT=1
if [ "$PACKAGE_NAME" != "unknown" ] && command -v python3 &>/dev/null; then
    python3 - "$PACKAGE_NAME" "$CURRENT_VERSION" <<'PY' && PYPI_CHECK_RESULT=0 || PYPI_CHECK_RESULT=$?
import json, sys, urllib.request
try:
    with urllib.request.urlopen(f"https://pypi.org/pypi/{sys.argv[1]}/json", timeout=5) as r:
        sys.exit(0 if sys.argv[2] in json.load(r).get("releases", {}) else 1)
except urllib.error.HTTPError as e:
    sys.exit(1 if e.code == 404 else 2)
except (urllib.error.URLError, OSError, TimeoutError):
    sys.exit(2)
except Exception:
    sys.exit(2)
PY
fi

if [ "$PYPI_CHECK_RESULT" -eq 0 ]; then
    new_version=$(bump_patch "$CURRENT_VERSION")
    old_version="$CURRENT_VERSION"
    echo -e "${YELLOW}⚠ ${PACKAGE_NAME} ${CURRENT_VERSION} already on PyPI — auto-bumping: ${old_version} → ${new_version}${NC}"
    if update_version "$CURRENT_VERSION" "$new_version"; then
        git commit -m "chore: bump version to ${new_version}"
        CURRENT_VERSION="$new_version"
        echo -e "${GREEN}✓ Bumped to ${new_version}${NC}"
    else
        exit 1
    fi
elif [ "$PYPI_CHECK_RESULT" -eq 2 ]; then
    echo -e "${DIM}(PyPI check skipped — network/timeout)${NC}"
fi

echo -e "${GREEN}✓ [${REPO_NAME}] Version: ${CURRENT_VERSION}${NC}"

if has_pypi_token; then
    echo -e "${BLUE}📦 Auto-publishing ${CURRENT_VERSION} to PyPI (token found)...${NC}"
    WANT_PUBLISH=true
else
    echo -e "${DIM}  (no PyPI token — skipping publish. Add token to ~/.pypirc or set TWINE_PASSWORD)${NC}"
fi

if [ "$WANT_PUBLISH" = true ]; then
    schedule_publish "$CURRENT_VERSION" "$PACKAGE_NAME"
fi

# Exit 0 → push proceeds immediately, never blocked by build/upload
exit 0
