#!/usr/bin/env bash
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0

# Tag and publish a stable Python SDK release from its merged release PR.
# The PR's description is published as the release notes.
#
#   py/bin/create_release <version> <pr-number> [--dry-run]
#
# Steps and checks: py/docs/release_playbook.md
# Exit codes: 0 ok, 1 usage/environment, 2 a check failed (nothing changed).

set -euo pipefail

REPO="genkit-ai/genkit"

usage() {
    cat <<USAGE
Usage: py/bin/create_release <version> <pr-number> [--dry-run]

Tags and publishes a Genkit Python SDK release from its merged release PR.

  <version>     X.Y.Z, as set by py/bin/bump_version
  <pr-number>   Merged release PR titled "chore(py): release Python SDK v<version>".
                Its description becomes the release notes.
  --dry-run     Run the checks without tagging or publishing

For release candidates, use the "Release Python RC" workflow.
USAGE
}

die() {
    echo "error: $1" >&2
    [ -n "${2:-}" ] && echo "  → $2" >&2
    exit 1
}

VERSION=""
PR_NUMBER=""
DRY_RUN=false
for arg in "$@"; do
    case "$arg" in
        -h|--help) usage; exit 0 ;;
        --dry-run) DRY_RUN=true ;;
        -*) die "unknown option '$arg'" "Only --dry-run is supported. Release notes come from the PR description." ;;
        *)
            if [ -z "$VERSION" ]; then VERSION="$arg"
            elif [ -z "$PR_NUMBER" ]; then PR_NUMBER="$arg"
            else die "unexpected argument '$arg'" "Usage: py/bin/create_release <version> <pr-number> [--dry-run]"
            fi
            ;;
    esac
done

[ -n "$VERSION" ] && [ -n "$PR_NUMBER" ] ||
    die "missing arguments" "Usage: py/bin/create_release <version> <pr-number> [--dry-run]"
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
    die "version must be X.Y.Z, got '$VERSION'" "For release candidates, use the \"Release Python RC\" workflow."
[[ "$PR_NUMBER" =~ ^[0-9]+$ ]] ||
    die "PR number must be numeric, got '$PR_NUMBER'"

command -v gh >/dev/null 2>&1 || die "GitHub CLI not found" "Install it from https://cli.github.com"
gh auth status >/dev/null 2>&1 || die "GitHub CLI is not authenticated" "Run: gh auth login"
cd "$(dirname "${BASH_SOURCE[0]}")/../.."

TAG="py/v${VERSION}"
TITLE="chore(py): release Python SDK v${VERSION}"

PR_INFO=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json state,baseRefName,mergeCommit,url,title \
    --jq '[.state, .baseRefName, (.mergeCommit.oid // ""), .url, .title] | join("\u001f")' 2>/dev/null) ||
    die "PR #$PR_NUMBER not found in $REPO"
IFS=$'\x1f' read -r PR_STATE PR_BASE MERGE_SHA PR_URL PR_TITLE <<< "$PR_INFO"
NOTES=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json body --jq .body)

# Run every check so one pass reports everything to fix.
FAILED=0
pass() { echo "  ✓ $1"; }
fail() {
    echo "  ✗ $1"
    echo "    → $2"
    FAILED=$((FAILED + 1))
}

echo "Genkit Python SDK v${VERSION} from #${PR_NUMBER}"
echo ""

if [ "$PR_STATE" != "MERGED" ]; then
    fail "PR is not merged (${PR_STATE})" "Merge $PR_URL, then rerun."
elif [ "$PR_BASE" != "main" ]; then
    fail "PR merged into '$PR_BASE', not main" "Release PRs must merge into main."
else
    pass "PR merged into main"
fi

if [ "$PR_TITLE" = "$TITLE" ]; then
    pass "Title matches"
else
    fail "Title is \"$PR_TITLE\"" "Expected \"$TITLE\". Pass the PR that bumped to $VERSION; rename only if this is it."
fi

if [ -n "$(printf '%s' "$NOTES" | tr -d '[:space:]')" ]; then
    pass "Release notes present"
else
    fail "PR description is empty" "Add the release notes: gh pr edit $PR_NUMBER --repo $REPO --body-file notes.md"
fi

if [ -n "$MERGE_SHA" ]; then
    git fetch --quiet origin main || die "could not fetch origin/main" "Check your network and git remote."
    if git merge-base --is-ancestor "$MERGE_SHA" origin/main 2>/dev/null; then
        WRONG=""
        FOUND=""
        TOTAL=0
        NWRONG=0
        for toml in $(git ls-tree -r --name-only "$MERGE_SHA" -- py/packages | grep -E '^py/packages/[^/]+/pyproject\.toml$'); do
            v=$(git show "$MERGE_SHA:$toml" | sed -n 's/^version *= *"\(.*\)"/\1/p' | head -1)
            TOTAL=$((TOTAL + 1))
            if [ "$v" != "$VERSION" ]; then
                WRONG="$WRONG, $(basename "$(dirname "$toml")")"
                FOUND="${v:-none}"
                NWRONG=$((NWRONG + 1))
            fi
        done
        if [ "$NWRONG" -eq 0 ]; then
            pass "All $TOTAL packages at $VERSION"
        elif [ "$NWRONG" -eq "$TOTAL" ]; then
            fail "Packages are at $FOUND, not $VERSION" "Check the version and PR number."
        else
            fail "$NWRONG of $TOTAL packages not at $VERSION: ${WRONG#, }" "Rerun bump_version $VERSION in a new release PR."
        fi
    else
        fail "Merge commit is not on main" "Release PRs must merge into main."
    fi
fi

if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
    fail "$TAG already exists" "This version has shipped. Release the next version instead."
else
    pass "$TAG is available"
fi

echo ""
if [ "$FAILED" -gt 0 ]; then
    [ "$FAILED" -eq 1 ] && noun="check" || noun="checks"
    echo "Blocked: $FAILED $noun failed. Nothing was tagged or published."
    exit 2
fi

if $DRY_RUN; then
    echo "Ready to tag ${MERGE_SHA:0:12} as $TAG. Dry run, nothing changed."
    exit 0
fi

# Tag the merge commit, not the tip of main, so later merges stay out.
git tag -a "$TAG" "$MERGE_SHA" -m "Genkit Python SDK v${VERSION}" -m "Release PR: $PR_URL"
git push --quiet origin "refs/tags/$TAG"
echo "  ✓ Tagged ${MERGE_SHA:0:12} as $TAG"

NOTES_FILE=$(mktemp)
trap 'rm -f "$NOTES_FILE"' EXIT
printf '%s\n' "$NOTES" > "$NOTES_FILE"
if ! gh release create "$TAG" --repo "$REPO" --verify-tag --title "Genkit Python SDK v${VERSION}" \
    --notes-file "$NOTES_FILE" >/dev/null; then
    echo "error: $TAG is pushed and publishing has started, but creating the GitHub release failed" >&2
    echo "  → Keep the tag. Retry with:" >&2
    echo "    gh pr view $PR_NUMBER --repo $REPO --json body --jq .body | gh release create $TAG --repo $REPO --verify-tag --title \"Genkit Python SDK v${VERSION}\" --notes-file -" >&2
    exit 1
fi
echo "  ✓ Published https://github.com/$REPO/releases/tag/$TAG"

echo ""
echo "Next: approve the PyPI publish at https://github.com/$REPO/actions/workflows/publish_python.yml"
echo "Then verify with: pip index versions genkit"
