#!/bin/bash
# gh-api-push.sh: Atomic multi-file push via GitHub Git Data API
# Fallback when git push fails (e.g., Stash fake-ip / LibreSSL TLS issues)
# Uses /git/blobs, /git/trees, /git/commits, /git/refs for atomic single-commit
# or multi-commit push. Auto-infers owner/repo from git remote.
#
# Usage: gh-api-push.sh [owner/repo] [branch]

set -e

REPO_SLUG="${1:-}"
BRANCH="${2:-$(git rev-parse --abbrev-ref HEAD)}"

cd "$(git rev-parse --show-toplevel)" 2>/dev/null || { echo "❌ Not a git repo"; exit 1; }

# Infer repo slug from remote if not provided
if [ -z "$REPO_SLUG" ]; then
    REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "")
    if [[ "$REMOTE_URL" =~ github\.com[^:/]*[:/]([^/]+)/([^/]+)(\.git)?$ ]]; then
        REPO_SLUG="${BASH_REMATCH[1]}/${BASH_REMATCH[2]%.git}"
    else
        echo "❌ Cannot infer repo slug from remote: $REMOTE_URL"
        exit 1
    fi
fi

if ! gh auth status &>/dev/null; then
    echo "❌ gh CLI not authenticated"
    exit 1
fi

REMOTE_BRANCH="origin/${BRANCH}"
COMMITS=$(git rev-list --reverse "${REMOTE_BRANCH}..HEAD" 2>/dev/null || true)

if [ -z "$COMMITS" ]; then
    echo "⏭️  No unpushed commits"
    exit 0
fi

COMMIT_COUNT=$(echo "$COMMITS" | wc -l | awk '{print $1}')
echo "🔄 git push failed, using Git Data API fallback for ${REPO_SLUG}@${BRANCH}..."
echo "   Unpushed commits: ${COMMIT_COUNT}"

# Get remote latest commit SHA (parent of first unpushed commit)
PARENT_SHA=$(gh api "repos/${REPO_SLUG}/git/ref/heads/${BRANCH}" --jq '.object.sha' 2>/dev/null || echo "")
if [ -z "$PARENT_SHA" ]; then
    echo "❌ Failed to get remote ref for ${BRANCH}"
    exit 1
fi

CURRENT_PARENT="$PARENT_SHA"

# Helper: build tree JSON and create tree on GitHub
create_tree_for_commit() {
    local commit_sha="$1"
    local base_tree="$2"
    local repo="$3"

    local changed_files
    changed_files=$(git diff-tree --no-commit-id --name-status -r "$commit_sha")

    if [ -z "$changed_files" ]; then
        echo "$base_tree"
        return
    fi

    local tmpdir
    tmpdir=$(mktemp -d)
    # trap cleanup in caller to avoid nested trap issues

    while IFS= read -r line; do
        [ -z "$line" ] && continue
        local status file
        status=$(echo "$line" | cut -f1)
        file=$(echo "$line" | cut -f2-)

        case "$status" in
            A|M)
                if [ -f "$file" ]; then
                    local content blob_sha
                    content=$(base64 -i "$file" | tr -d '\n')
                    blob_sha=$(gh api "repos/${repo}/git/blobs" --method POST \
                        -f content="$content" -f encoding="base64" --jq '.sha')
                    printf '%s\t%s\n' "$file" "$blob_sha" >> "$tmpdir/blobs.map"
                fi
                ;;
            D)
                printf '%s\tDELETE\n' "$file" >> "$tmpdir/blobs.map"
                ;;
        esac
    done <<< "$changed_files"

    local tree_json
    tree_json=$(python3 -c "
import json, sys
base = sys.argv[1]
entries = []
with open(sys.argv[2], 'r') as f:
    for line in f:
        line = line.rstrip('\n')
        if '\t' not in line:
            continue
        path, sha = line.split('\t', 1)
        if sha == 'DELETE':
            entries.append({'path': path, 'mode': '100644', 'type': 'blob', 'sha': None})
        else:
            entries.append({'path': path, 'mode': '100644', 'type': 'blob', 'sha': sha})
print(json.dumps({'tree': entries, 'base_tree': base}))
" "$base_tree" "$tmpdir/blobs.map")

    gh api "repos/${repo}/git/trees" --method POST --input - --jq '.sha' <<< "$tree_json"
    rm -rf "$tmpdir"
}

# Helper: create commit with preserved authorship
create_commit() {
    local msg="$1"
    local tree_sha="$2"
    local parent="$3"
    local repo="$4"
    local commit_sha="$5"

    local author_name author_email author_date committer_name committer_email committer_date
    author_name=$(git log -1 --pretty=format:%an "$commit_sha")
    author_email=$(git log -1 --pretty=format:%ae "$commit_sha")
    author_date=$(git log -1 --pretty=format:%aI "$commit_sha")
    committer_name=$(git log -1 --pretty=format:%cn "$commit_sha")
    committer_email=$(git log -1 --pretty=format:%ce "$commit_sha")
    committer_date=$(git log -1 --pretty=format:%cI "$commit_sha")

    local commit_json
    commit_json=$(python3 -c "
import json, sys
print(json.dumps({
    'message': sys.argv[1],
    'tree': sys.argv[2],
    'parents': [sys.argv[3]],
    'author': {'name': sys.argv[4], 'email': sys.argv[5], 'date': sys.argv[6]},
    'committer': {'name': sys.argv[7], 'email': sys.argv[8], 'date': sys.argv[9]}
}))
" "$msg" "$tree_sha" "$parent" "$author_name" "$author_email" "$author_date" \
       "$committer_name" "$committer_email" "$committer_date")

    gh api "repos/${repo}/git/commits" --method POST --input - --jq '.sha' <<< "$commit_json"
}

# Process each unpushed commit in order
while IFS= read -r COMMIT_SHA; do
    [ -z "$COMMIT_SHA" ] && continue

    commit_msg=$(git log -1 --pretty=format:%B "$COMMIT_SHA")
    base_tree=""
    tree_sha=""
    new_commit_sha=""

    # Determine base tree: for first commit use remote parent's tree,
    # for subsequent commits use the previously created commit's tree.
    if [ "$CURRENT_PARENT" = "$PARENT_SHA" ]; then
        base_tree=$(gh api "repos/${REPO_SLUG}/git/commits/${PARENT_SHA}" --jq '.tree.sha')
    else
        base_tree=$(gh api "repos/${REPO_SLUG}/git/commits/${CURRENT_PARENT}" --jq '.tree.sha')
    fi

    tree_sha=$(create_tree_for_commit "$COMMIT_SHA" "$base_tree" "$REPO_SLUG")
    new_commit_sha=$(create_commit "$commit_msg" "$tree_sha" "$CURRENT_PARENT" "$REPO_SLUG" "$COMMIT_SHA")

    CURRENT_PARENT="$new_commit_sha"
    echo "   ✅ Created commit ${new_commit_sha:0:7}: $(echo "$commit_msg" | head -1)"
done <<< "$COMMITS"

# Update branch reference
gh api "repos/${REPO_SLUG}/git/refs/heads/${BRANCH}" --method PATCH -f sha="$CURRENT_PARENT" >/dev/null

echo "🎉 Atomic fallback push complete (${COMMIT_COUNT} commit(s))"
