# Android client CI (Gitea Actions). Builds the Rust JNI core (clients/android/native) via
# cargo-ndk for all three shipping ABIs and assembles the debug APK (clients/android). Mirrors apple.yml
# but on a Linux runner — the NDK is cross-platform, so no self-hosted host is needed.
#
# Prereq: the runner needs ~6 GB free + internet (it pulls the Android SDK/NDK and the Gradle
# distribution in-job). If android-actions/setup-android is not mirrored on this Gitea instance,
# replace that step with a manual cmdline-tools download, or bake an `android-ci` image like
# ci/rust-ci.Dockerfile. Emulator instrumentation tests are deferred until a KVM-capable runner
# exists (they self-skip otherwise, like apple.yml's RemoteFirstLightTests).
name: android

on:
  push:
    branches: [main]
    # Single project version: a `vX.Y.Z` tag is THE release (uploads to Play's `alpha` closed
    # track for manual promotion + attaches the .aab/.apk to the unified Gitea Release). A main
    # push is canary (Play `internal`).
    tags: ['v*']
  pull_request:
  workflow_dispatch:

jobs:
  android:
    runs-on: ubuntu-24.04
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4

      - name: JDK 21 (AGP 9.2 runs on JDK 17–21, not the host default)
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: "21"

      - name: Rust toolchain + Android targets (self-healing on a fresh runner)
        run: |
          if ! command -v rustup >/dev/null && [ ! -x "$HOME/.cargo/bin/rustup" ]; then
            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
              | sh -s -- -y --no-modify-path --profile minimal
          fi
          RUSTUP="$(command -v rustup || echo "$HOME/.cargo/bin/rustup")"
          dirname "$RUSTUP" >> "$GITHUB_PATH"
          "$RUSTUP" target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android

      - name: Android SDK
        # SHA-pinned: this workflow's release job carries the signing keystore + Play service-account
        # secrets, so a moved tag on a third-party action could exfiltrate them. v3 = 9fc6c4e.
        uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
        with:
          # Only platform-tools — NOT the action's default legacy `tools`, whose dependency chain
          # drags in the ~250 MB emulator nobody here runs (instrumentation tests are deferred).
          # That download was the single flakiest piece of this job: the shared runner fleet drops
          # packets under parallel-job load and sdkmanager's streamed unzip turns a truncated
          # stream into "Error on ZipFile unknown archive" (observed 2026-07-22, twice).
          packages: platform-tools

      - name: NDK r30 + platform 36 + build-tools + CMake (libopus cross-build)
        # cmake;3.22.1 installs cmake + ninja under $ANDROID_SDK/cmake/3.22.1/bin — the exact path
        # kit/build.gradle.kts prepends to PATH for cargo-ndk's audiopus_sys (libopus) CMake build.
        # Note: platforms;android-37 is sometimes missing from standard channels; AGP will
        # auto-download it if needed during the build.
        # retry.sh: sdkmanager is a single-shot multi-hundred-MB fetch, exactly the class the
        # helper exists for (fleet-load packet drops truncate the stream mid-unzip); a failed
        # attempt leaves no partial package behind, so a plain re-invoke is safe.
        run: bash scripts/ci/retry.sh 4 sdkmanager "platform-tools" "platforms;android-36" "build-tools;37.0.0" "ndk;30.0.14904198" "cmake;3.22.1"

      - name: Caches (cargo + gradle)
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            ~/.gradle/caches
            ~/.gradle/wrapper
            target
          key: android-${{ hashFiles('Cargo.lock', 'clients/android/**/*.gradle.kts') }}
          restore-keys: android-

      - name: cargo-ndk
        run: command -v cargo-ndk >/dev/null || cargo install cargo-ndk

      - name: assembleDebug (cargo-ndk → jniLibs → APK)
        working-directory: clients/android
        env:
          VERSION_CODE: ${{ github.run_number }}
        run: ./gradlew :app:assembleDebug --stacktrace

      # Single source of the version name + the Play track for the release steps below. versionCode
      # stays github.run_number (monotonic across both tracks; Play rejects a regressed code).
      - name: Version + channel
        if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
        run: |
          eval "$(bash scripts/ci/pf-version.sh)"   # -> PF_BASE (one minor ahead of the latest stable tag)
          case "$GITHUB_REF" in
            refs/tags/v*) VN="${GITHUB_REF_NAME#v}"; TRACK="alpha" ;;   # alpha = built-in closed testing
            *)            VN="${PF_BASE}-ci${GITHUB_RUN_NUMBER}"; TRACK="internal" ;;
          esac
          echo "VERSION_NAME=$VN" >> "$GITHUB_ENV"
          echo "PLAY_TRACK=$TRACK" >> "$GITHUB_ENV"
          echo "android version $VN -> Play track '$TRACK'"

      - name: Build Release (signed AAB + universal APK)
        if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
        working-directory: clients/android
        env:
          VERSION_CODE: ${{ github.run_number }}   # VERSION_NAME comes from the Version+channel step (GITHUB_ENV)
          RELEASE_KEYSTORE_FILE: "../release.jks"
          RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
          RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
          RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
        run: |
          echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d > release.jks
          # AAB for Play; a universal APK (all ABIs) for direct sideload/testing — same upload key.
          ./gradlew :app:bundleRelease :app:assembleRelease --stacktrace

      # Attach the built APK to the workflow run so it's a one-click sideload download straight from the
      # run page (no need to know the generic-registry URL) — on EVERY trigger, incl. a PR or a
      # workflow_dispatch. The debug APK is built on every run; the signed universal release APK exists
      # only on a main/tag push (its build step above is push-gated), so grab whichever were produced.
      - name: Attach APK(s) to the workflow run
        if: always()
        # v3, not v4: Gitea's artifact backend identifies as GHES, which upload-artifact@v4 refuses
        # (same reason as apple.yml / *-screenshots.yml). Download is a zip of the matched APK(s).
        uses: actions/upload-artifact@v3
        with:
          name: punktfunk-android-apk
          path: |
            clients/android/app/build/outputs/apk/release/*.apk
            clients/android/app/build/outputs/apk/debug/*.apk
          if-no-files-found: warn
          retention-days: 30

      # Publish BEFORE the Play upload so artifacts land even while the Play step is still failing.
      # Generic registry is public for reads — matches windows-msix.yml / deb.yml (REGISTRY_TOKEN, user enricobuehler).
      # main = canary store + `canary/` sideload alias; a `vX.Y.Z` tag = `latest/` alias + attached
      # to the unified Gitea Release.
      - name: Publish to generic registry + attach to Gitea release
        if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
        env:
          REGISTRY: git.unom.io
          OWNER: unom
          PKG: punktfunk-android
          VERSION: ${{ github.run_number }}
          REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
          GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
        run: |
          AAB=clients/android/app/build/outputs/bundle/release/app-release.aab
          APK=clients/android/app/build/outputs/apk/release/app-release.apk
          base="https://$REGISTRY/api/packages/$OWNER/generic/$PKG"
          # 1) immutable, run-number-versioned store (sideload + provenance)
          curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$AAB" "$base/$VERSION/punktfunk-android-r$VERSION.aab"
          curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$APK" "$base/$VERSION/punktfunk-android-r$VERSION.apk"
          echo "published store version $VERSION (versionCode)"
          # 2) channel alias for a predictable sideload URL: stable -> latest/, canary -> canary/
          case "$GITHUB_REF" in refs/tags/v*) ALIAS=latest ;; *) ALIAS=canary ;; esac
          curl -fsS -o /dev/null --user "enricobuehler:$REGISTRY_TOKEN" -X DELETE "$base/$ALIAS/punktfunk-android.apk" || true
          curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$APK" "$base/$ALIAS/punktfunk-android.apk"
          echo "sideload alias: $base/$ALIAS/punktfunk-android.apk"
          # 3) on a real release, attach the .aab + .apk to the unified Gitea Release (X.Y.Z names)
          case "$GITHUB_REF" in
            refs/tags/v*)
              . scripts/ci/gitea-release.sh
              RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
              upsert_asset "$RID" "$AAB" "punktfunk-${VERSION_NAME}.aab"
              upsert_asset "$RID" "$APK" "punktfunk-${VERSION_NAME}.apk"
              ;;
          esac

      # Direct Publishing-API upload instead of r0adkll/upload-google-play — that action hides the
      # real API error behind "Unknown error occurred."; this prints it. stdlib + openssl only (no
      # pip), reuses SERVICE_ACCOUNT_JSON (raw JSON or base64), auto-handles changesNotSentForReview.
      # Track: canary main -> `internal`; a vX.Y.Z release -> `alpha` (closed testing) for manual
      # promotion to production in the Play console.
      - name: Upload to Google Play
        if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
        env:
          SERVICE_ACCOUNT_JSON: ${{ secrets.SERVICE_ACCOUNT_JSON }}
        run: |
          echo "uploading to Play track '$PLAY_TRACK'"
          python3 clients/android/ci/play-upload.py \
            --package io.unom.punktfunk \
            --aab clients/android/app/build/outputs/bundle/release/app-release.aab \
            --track "$PLAY_TRACK" --status completed
