name: "Publish: Release Uploads"
run-name: "Publish: Release Uploads (Build ${{ inputs.build_number }})"

on:
  workflow_dispatch:
    inputs:
      build_number:
        description: Build number to publish
        required: true
      upload_app_store:
        description: Upload iOS/macOS/visionOS artifacts to App Store Connect
        required: false
        default: "false"
      distribute_testflight:
        description: Distribute an already-uploaded iOS build to TestFlight
        required: false
        default: "false"
      upload_sparkle:
        description: Publish Sparkle artifacts
        required: false
        default: "true"
      upload_microsoft_store:
        description: Upload Windows package to Microsoft Partner Center beta lane
        required: false
        default: "false"
      sparkle_channel:
        description: Sparkle channel, defaults to build-<number>
        required: false
        default: ""
      sparkle_point_main:
        description: Point Sparkle main appcast/default channel to selected channel
        required: false
        default: "true"
      testflight_distribute_external:
        description: Distribute to external TestFlight groups
        required: false
        default: "false"
      testflight_groups:
        description: Optional comma-separated TestFlight groups for external distribution
        required: false
        default: ""
      ios_uses_non_exempt_encryption:
        description: Set true only when the iOS build uses non-exempt encryption
        required: false
        default: "false"

permissions:
  contents: read

concurrency:
  group: burrow-publish-store-uploads-${{ inputs.build_number }}
  cancel-in-progress: true

env:
  BURROW_RELEASE_GCS_BUCKET: ${{ vars.BURROW_RELEASE_GCS_BUCKET || 'burrow-net-releases' }}
  BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
  GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
  BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
  BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
  BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
  BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
  BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
  BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
  BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
  BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
  BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
  BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
  BURROW_APPLE_BUNDLE_ID: ${{ vars.BURROW_APPLE_BUNDLE_ID || 'net.burrow.app' }}
  BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID: ${{ vars.BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID || 'net.burrow.app.network' }}
  BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID: ${{ vars.BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID || '3G42677598' }}
  BURROW_DEVELOPER_ID_CERTIFICATE_ID: ${{ vars.BURROW_DEVELOPER_ID_CERTIFICATE_ID || '9JKN6HXBHC' }}
  BURROW_APPLE_SIGNING_MODE: ${{ vars.BURROW_APPLE_SIGNING_MODE || 'google-kms-staged' }}
  BURROW_NOTARIZE_MACOS: ${{ vars.BURROW_NOTARIZE_MACOS || 'true' }}
  BURROW_SPARKLE_SIGN_WITH_KMS: ${{ vars.BURROW_SPARKLE_SIGN_WITH_KMS || 'true' }}
  BURROW_SPARKLE_KMS_KEY: ${{ vars.BURROW_SPARKLE_KMS_KEY || 'sparkle-ed25519' }}
  BURROW_SPARKLE_KMS_VERSION: ${{ vars.BURROW_SPARKLE_KMS_VERSION || '1' }}
  BURROW_KMS_LOCATION: ${{ vars.BURROW_KMS_LOCATION || 'global' }}
  BURROW_KMS_KEYRING: ${{ vars.BURROW_KMS_KEYRING || 'burrow-identity' }}
  TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS: ${{ vars.TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS || '7200' }}

jobs:
  sign-apple-kms:
    name: Sign (Apple KMS)
    if: ${{ inputs.upload_app_store == 'true' || inputs.upload_sparkle == 'true' }}
    runs-on: namespace-profile-linux-medium
    steps:
      - name: Checkout
        uses: https://code.forgejo.org/actions/checkout@v4
        with:
          token: ${{ github.token }}
          fetch-depth: 0

      - name: Ensure Nix
        shell: bash
        run: bash Scripts/ci/ensure-nix.sh

      - name: Configure Age
        shell: bash
        env:
          AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
        run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"

      - name: Decrypt Release Secrets
        uses: ./.forgejo/actions/decrypt-release-secrets

      - name: Validate App Store Credentials
        shell: bash
        run: Scripts/ci/check-release-config.sh app-store

      - name: Download Staged Apple Artifacts
        shell: bash
        env:
          BUILD_NUMBER: ${{ inputs.build_number }}
        run: |
          set -euo pipefail
          nix develop .#ci -c Scripts/ci/google-wif-auth.sh
          mkdir -p "dist/builds/${BUILD_NUMBER}/apple"
          nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/apple" "dist/builds/${BUILD_NUMBER}/apple"
          find "dist/builds/${BUILD_NUMBER}/apple" -type f -print | sort

      - name: Sync Apple Provisioning Profiles
        shell: bash
        run: nix develop .#ci -c Scripts/ci/sync-apple-provisioning-profiles.sh all

      - name: Sign Apple Artifacts With KMS
        shell: bash
        env:
          BUILD_NUMBER: ${{ inputs.build_number }}
          SPARKLE_CHANNEL_INPUT: ${{ inputs.sparkle_channel }}
        run: |
          set -euo pipefail
          channel="${SPARKLE_CHANNEL_INPUT:-}"
          if [[ -z "$channel" ]]; then
            channel="build-${BUILD_NUMBER}"
          fi
          SPARKLE_CHANNEL="$channel" nix develop .#ci -c Scripts/apple/sign-release-artifacts-kms.sh all

      - name: Upload Signed Apple Artifacts
        shell: bash
        env:
          BUILD_NUMBER: ${{ inputs.build_number }}
        run: nix develop .#ci -c Scripts/ci/upload-release-storage.sh

  upload-app-store:
    name: Upload (App Store Connect)
    needs: sign-apple-kms
    if: ${{ inputs.upload_app_store == 'true' }}
    runs-on: namespace-profile-macos-large
    steps:
      - name: Checkout
        uses: https://code.forgejo.org/actions/checkout@v4
        with:
          token: ${{ github.token }}
          fetch-depth: 0

      - name: Ensure Nix
        shell: bash
        run: bash Scripts/ci/ensure-nix.sh

      - name: Configure Age
        shell: bash
        env:
          AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
        run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"

      - name: Decrypt Release Secrets
        uses: ./.forgejo/actions/decrypt-release-secrets

      - name: Validate App Store Credentials
        shell: bash
        run: Scripts/ci/check-release-config.sh app-store

      - name: Download Apple Artifacts From GCS
        shell: bash
        env:
          BUILD_NUMBER: ${{ inputs.build_number }}
        run: |
          set -euo pipefail
          nix develop .#ci -c Scripts/ci/google-wif-auth.sh
          mkdir -p publish/apple
          nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/apple" publish/apple
          find publish/apple -type f -print | sort

      - name: Upload Apple Packages
        shell: bash
        run: |
          set -euo pipefail
          Scripts/ci/check-release-config.sh app-store
          key_dir="${RUNNER_TEMP:-/tmp}/burrow-asc"
          altool_key_dir="${HOME}/.appstoreconnect/private_keys"
          mkdir -p "$key_dir" "$altool_key_dir"
          key_path="$key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
          if [[ -n "${ASC_API_KEY_PATH:-}" && -f "${ASC_API_KEY_PATH:-}" ]]; then
            cp "$ASC_API_KEY_PATH" "$key_path"
          else
            printf '%s' "$ASC_API_KEY_BASE64" | base64 --decode > "$key_path"
          fi
          cp "$key_path" "$altool_key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
          chmod 600 "$key_path" "$altool_key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
          shopt -s nullglob
          artifacts=()
          for artifact in publish/apple/*.ipa publish/apple/*.pkg; do
            [[ "$artifact" == *.unsigned.ipa ]] && continue
            artifacts+=("$artifact")
          done
          if (( ${#artifacts[@]} == 0 )); then
            echo "::error ::No Apple upload artifacts found for build ${{ inputs.build_number }}."
            exit 1
          fi
          for artifact in "${artifacts[@]}"; do
            upload_type="ios"
            if [[ "$artifact" == *.pkg ]]; then
              upload_type="macos"
            fi
            xcrun altool --upload-app \
              --type "$upload_type" \
              --file "$artifact" \
              --apiKey "$ASC_API_KEY_ID" \
              --apiIssuer "$ASC_API_ISSUER_ID"
          done

  release-ios-testflight:
    name: Release (TestFlight iOS Testers)
    needs: upload-app-store
    if: ${{ inputs.upload_app_store == 'true' }}
    runs-on: namespace-profile-linux-testflight
    steps:
      - name: Checkout
        uses: https://code.forgejo.org/actions/checkout@v4
        with:
          token: ${{ github.token }}
          fetch-depth: 0

      - name: Ensure Nix
        shell: bash
        run: bash Scripts/ci/ensure-nix.sh

      - name: Configure Age
        shell: bash
        env:
          AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
        run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"

      - name: Decrypt Release Secrets
        uses: ./.forgejo/actions/decrypt-release-secrets

      - name: Validate App Store Credentials
        shell: bash
        run: Scripts/ci/check-release-config.sh app-store

      - name: Distribute iOS Build To TestFlight
        shell: bash
        env:
          BUILD_NUMBER: ${{ inputs.build_number }}
          TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ inputs.testflight_distribute_external || 'false' }}
          TESTFLIGHT_GROUPS: ${{ inputs.testflight_groups || '' }}
          TESTFLIGHT_INTERNAL_GROUP: Internal
          IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ inputs.ios_uses_non_exempt_encryption || 'false' }}
        run: Scripts/ci/distribute-testflight.sh

  distribute-ios-testflight:
    name: Distribute (TestFlight iOS Testers)
    if: ${{ inputs.upload_app_store != 'true' && inputs.distribute_testflight == 'true' }}
    runs-on: namespace-profile-linux-testflight
    steps:
      - name: Checkout
        uses: https://code.forgejo.org/actions/checkout@v4
        with:
          token: ${{ github.token }}
          fetch-depth: 0

      - name: Ensure Nix
        shell: bash
        run: bash Scripts/ci/ensure-nix.sh

      - name: Configure Age
        shell: bash
        env:
          AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
        run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"

      - name: Decrypt Release Secrets
        uses: ./.forgejo/actions/decrypt-release-secrets

      - name: Validate App Store Credentials
        shell: bash
        run: Scripts/ci/check-release-config.sh app-store

      - name: Distribute iOS Build To TestFlight
        shell: bash
        env:
          BUILD_NUMBER: ${{ inputs.build_number }}
          TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ inputs.testflight_distribute_external || 'false' }}
          TESTFLIGHT_GROUPS: ${{ inputs.testflight_groups || '' }}
          TESTFLIGHT_INTERNAL_GROUP: Internal
          IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ inputs.ios_uses_non_exempt_encryption || 'false' }}
        run: Scripts/ci/distribute-testflight.sh

  upload-sparkle:
    name: Upload (Sparkle)
    needs: sign-apple-kms
    if: ${{ inputs.upload_sparkle == 'true' }}
    runs-on: namespace-profile-linux-medium
    steps:
      - name: Checkout
        uses: https://code.forgejo.org/actions/checkout@v4
        with:
          token: ${{ github.token }}
          fetch-depth: 0

      - name: Resolve Sparkle Channel
        id: channel
        shell: bash
        env:
          BUILD_NUMBER: ${{ inputs.build_number }}
        run: |
          set -euo pipefail
          channel="${{ inputs.sparkle_channel }}"
          if [[ -z "$channel" ]]; then
            channel="build-${BUILD_NUMBER}"
          fi
          echo "channel=$channel" >> "$GITHUB_OUTPUT"

      - name: Ensure Nix
        shell: bash
        run: bash Scripts/ci/ensure-nix.sh

      - name: Publish Sparkle Channel
        shell: bash
        env:
          BUILD_NUMBER: ${{ inputs.build_number }}
          CHANNEL: ${{ steps.channel.outputs.channel }}
          SPARKLE_POINT_MAIN: ${{ inputs.sparkle_point_main }}
        run: |
          set -euo pipefail
          nix develop .#ci -c Scripts/ci/google-wif-auth.sh
          mkdir -p "publish/sparkle/${CHANNEL}"
          nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/sparkle/${CHANNEL}" "publish/sparkle/${CHANNEL}" || true
          if [[ ! -f "publish/sparkle/${CHANNEL}/appcast.xml" ]]; then
            echo "::warning ::No Sparkle appcast staged for build ${BUILD_NUMBER}; skipping Sparkle publish."
            find "publish/sparkle/${CHANNEL}" -maxdepth 2 -type f -print || true
            exit 0
          fi
          nix develop .#ci -c gcloud storage rsync --recursive "publish/sparkle/${CHANNEL}" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/${CHANNEL}"
          if [[ "$SPARKLE_POINT_MAIN" == "true" ]]; then
            printf '%s\n' "$CHANNEL" > main-channel.txt
            nix develop .#ci -c gcloud storage cp main-channel.txt "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/main-channel.txt"
            nix develop .#ci -c gcloud storage cp "publish/sparkle/${CHANNEL}/appcast.xml" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/appcast.xml"
            nix develop .#ci -c gcloud storage rsync --recursive "publish/sparkle/${CHANNEL}" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/default"
          fi

  upload-microsoft-store:
    name: Upload (Microsoft Store Beta)
    if: ${{ inputs.upload_microsoft_store == 'true' }}
    runs-on: namespace-profile-windows-large
    steps:
      - name: Checkout
        uses: https://code.forgejo.org/actions/checkout@v4
        with:
          token: ${{ github.token }}
          fetch-depth: 0

      - name: Validate Microsoft Store Credentials
        shell: pwsh
        env:
          MICROSOFT_TENANT_ID: ${{ secrets.MICROSOFT_TENANT_ID }}
          MICROSOFT_CLIENT_ID: ${{ secrets.MICROSOFT_CLIENT_ID }}
          MICROSOFT_CLIENT_SECRET: ${{ secrets.MICROSOFT_CLIENT_SECRET }}
        run: |
          foreach ($name in "MICROSOFT_TENANT_ID","MICROSOFT_CLIENT_ID","MICROSOFT_CLIENT_SECRET") {
            if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) {
              throw "missing required environment variable: $name"
            }
          }

      - name: Stop Until Store Package Exists
        shell: pwsh
        run: |
          Write-Host "::warning ::Burrow only builds a Windows Rust stub today; Microsoft Store package upload is wired for credentials but waits on MSIX packaging."
