name: Nightly releases

on:
  schedule:
    - cron: "17 2 */2 * *"
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: nightly-release
  cancel-in-progress: false

env:
  NIGHTLY_VERSION: nightly-${{ github.run_id }}-${{ github.run_attempt }}

jobs:
  package:
    name: Package release
    uses: ./.github/workflows/package-release.yml
    with:
      version: nightly-${{ github.run_id }}-${{ github.run_attempt }}
      retention_days: 14

  publish:
    name: Publish nightly release
    needs: package
    runs-on: ubuntu-24.04
    timeout-minutes: 15
    permissions:
      actions: read
      contents: write
    steps:
      - name: Check out repository
        uses: actions/checkout@v7

      - name: Configure primary repository access
        env:
          PRIMARY_GIT_SSH_KEY: ${{ secrets.PRIMARY_GIT_SSH_KEY }}
        run: |
          if [[ -z "$PRIMARY_GIT_SSH_KEY" ]]; then
            echo "PRIMARY_GIT_SSH_KEY is not configured" >&2
            exit 1
          fi
          key_file="$RUNNER_TEMP/primary_git_key"
          known_hosts_file="$RUNNER_TEMP/primary_git_known_hosts"
          install -m 600 /dev/null "$key_file"
          printf '%s\n' "$PRIMARY_GIT_SSH_KEY" > "$key_file"
          printf '%s\n' 'git.marzeq.foo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPxWUKlEvL387Pa9OF9bFWiDHSI45QXWxHrejac2QpPe' > "$known_hosts_file"
          echo "GIT_SSH_COMMAND=ssh -i $key_file -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$known_hosts_file" >> "$GITHUB_ENV"
          git remote add primary git@git.marzeq.foo:marzeq/qk.git

      - name: Download release archives
        uses: actions/download-artifact@v7
        with:
          pattern: qk-${{ env.NIGHTLY_VERSION }}-*
          path: dist
          merge-multiple: true

      - name: Publish tag through primary mirror
        run: |
          git tag "$NIGHTLY_VERSION" "$GITHUB_SHA"
          git push primary "refs/tags/$NIGHTLY_VERSION"

      - name: Wait for mirrored GitHub tag
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          for attempt in {1..12}; do
            if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$NIGHTLY_VERSION" >/dev/null 2>&1; then
              exit 0
            fi
            sleep 5
          done
          echo "tag $NIGHTLY_VERSION was not mirrored to GitHub within 60 seconds" >&2
          exit 1

      - name: Publish prerelease
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create "$NIGHTLY_VERSION" dist/*.tar.gz \
            --repo "$GITHUB_REPOSITORY" \
            --title "QK $NIGHTLY_VERSION" \
            --notes "Automated build of $GITHUB_SHA from $(date -u +'%Y-%m-%d %H:%M UTC')." \
            --prerelease \
            --verify-tag

      - name: Remove primary tag after publication failure
        if: ${{ failure() }}
        run: |
          if git remote get-url primary >/dev/null 2>&1 &&
            git ls-remote --exit-code --tags primary "refs/tags/$NIGHTLY_VERSION" >/dev/null 2>&1; then
            git push primary --delete "refs/tags/$NIGHTLY_VERSION"
          fi

      - name: Keep the latest three nightly releases
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          mapfile -t nightly_releases < <(
            gh release list \
              --repo "$GITHUB_REPOSITORY" \
              --limit 100 \
              --json tagName,createdAt,isDraft \
              --jq 'map(select(.isDraft == false and (.tagName | startswith("nightly-")))) | sort_by(.createdAt) | reverse | .[].tagName'
          )
          retained_tags=("${nightly_releases[@]:0:3}")

          is_retained() {
            local candidate=$1
            local retained_tag
            for retained_tag in "${retained_tags[@]}"; do
              if [[ $candidate == "$retained_tag" ]]; then
                return 0
              fi
            done
            return 1
          }

          delete_github_tag() {
            local tag=$1
            local error
            if ! gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" >/dev/null 2>&1; then
              return 0
            fi
            if error=$(gh api --method DELETE "repos/$GITHUB_REPOSITORY/git/refs/tags/$tag" 2>&1); then
              return 0
            fi
            # Deleting the primary tag is mirrored to GitHub asynchronously.
            # Treat a tag that disappeared during the API request as success.
            if ! gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" >/dev/null 2>&1; then
              return 0
            fi
            echo "$error" >&2
            return 1
          }

          for tag in "${nightly_releases[@]:3}"; do
            gh release delete "$tag" --repo "$GITHUB_REPOSITORY" --yes
            if git ls-remote --exit-code --tags primary "refs/tags/$tag" >/dev/null 2>&1; then
              git push primary --delete "refs/tags/$tag"
            fi
            delete_github_tag "$tag"
          done

          mapfile -t primary_tags < <(
            git ls-remote --refs --tags primary 'refs/tags/nightly-*' |
              awk '{ sub("^refs/tags/", "", $2); print $2 }'
          )
          for tag in "${primary_tags[@]}"; do
            if ! is_retained "$tag"; then
              git push primary --delete "refs/tags/$tag"
            fi
          done

          mapfile -t github_tags < <(
            gh api --paginate "repos/$GITHUB_REPOSITORY/git/matching-refs/tags/nightly-" \
              --jq '.[] | .ref | sub("^refs/tags/"; "")'
          )
          for tag in "${github_tags[@]}"; do
            if ! is_retained "$tag"; then
              delete_github_tag "$tag"
            fi
          done