name: Frontend CI

on:
  push:
    branches:
      - main
      - master
      - develop
      - dev
  pull_request:
    branches:
      - main
      - master
  workflow_dispatch:
    inputs:
      publish_ota:
        description: 'Publish Android OTA'
        required: false
        default: 'true'

env:
  REGISTRY: code.littlelan.cn
  IMAGE_NAME: carrot_bbs/frontend-web
  OTA_PLATFORM: android
  OTA_PUBLISH_URL: https://updates.littlelan.cn/admin/publish
  OTA_MANIFEST_URL: https://updates.littlelan.cn/api/manifest

jobs:
  ota-android:
    runs-on: ubuntu-latest
    if: github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch' || github.event.inputs.publish_ota == 'true')
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '25.6.1'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Resolve runtime version
        id: runtime
        run: |
          RUNTIME_VERSION="$(node -p "require('./app.json').expo.version")"
          echo "runtime_version=${RUNTIME_VERSION}" >> "$GITHUB_OUTPUT"
          echo "Resolved runtimeVersion: ${RUNTIME_VERSION}"

      - name: Export Android update bundle
        run: |
          rm -rf dist-android dist-android-update.zip
          npx expo export --platform android --output-dir dist-android
          npx expo config --type public --json > dist-android/expoConfig.json

      - name: Archive Android update bundle
        run: |
          python - <<'PY'
          import os
          import zipfile

          with zipfile.ZipFile('dist-android-update.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
              for root, _, files in os.walk('dist-android'):
                  for name in files:
                      src = os.path.join(root, name)
                      arc = os.path.relpath(src, 'dist-android')
                      zf.write(src, arc)
          PY

      - name: Publish OTA
        env:
          OTA_AUTH_TOKEN: ${{ secrets.OTA_AUTH_TOKEN }}
        run: |
          test -n "${OTA_AUTH_TOKEN}" || (echo "Missing secret OTA_AUTH_TOKEN" && exit 1)
          curl -fSs -X POST "${OTA_PUBLISH_URL}?runtimeVersion=${{ steps.runtime.outputs.runtime_version }}&platform=${OTA_PLATFORM}" \
            -H "Authorization: Bearer ${OTA_AUTH_TOKEN}" \
            -H "Content-Type: application/zip" \
            --data-binary @"dist-android-update.zip"

      - name: Verify OTA manifest
        run: |
          REMOTE="$(curl -sS "${OTA_MANIFEST_URL}" \
            -H "expo-platform: ${OTA_PLATFORM}" \
            -H "expo-runtime-version: ${{ steps.runtime.outputs.runtime_version }}" \
            -H "expo-protocol-version: 1" \
            | python -c "import sys, json, re; s=sys.stdin.read(); m=re.search(r'\\{\\\"id\\\":.*\\\"extra\\\":\\{.*\\}\\}', s); j=json.loads(m.group(0)); print(j['launchAsset']['url'].split('/')[-1].split('&')[0])")"
          LOCAL="$(python -c "import json; m=json.load(open('dist-android/metadata.json')); print(m['fileMetadata']['android']['bundle'].split('/')[-1])")"
          echo "Remote bundle: ${REMOTE}"
          echo "Local bundle:  ${LOCAL}"
          test "${REMOTE}" = "${LOCAL}"

  build-android-apk:
    runs-on: ubuntu-latest
    container:
      image: reactnativecommunity/react-native-android:latest
    env:
      GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=4 -Xmx8g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:ReservedCodeCacheSize=512m"
      _JAVA_OPTIONS: "-Xmx8g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:ReservedCodeCacheSize=512m"
      NODE_OPTIONS: "--max-old-space-size=8192"
      NODE_ENV: "production"
      NDK_NUM_JOBS: "4"
      CMAKE_BUILD_PARALLEL_LEVEL: "4"
    permissions:
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '25.6.1'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Generate Android native project
        run: npx expo prebuild --platform android

      - name: Configure Gradle with Aliyun Maven mirror
        run: |
          cd android
          
          # Update settings.gradle with Aliyun Maven mirror
          cat > settings.gradle << 'SETTINGS_EOF'
          pluginManagement {
            repositories {
              google()
              mavenCentral()
              gradlePluginPortal()
              maven { url 'https://maven.aliyun.com/repository/public' }
              maven { url 'https://maven.aliyun.com/repository/google' }
              maven { url 'https://maven.aliyun.com/repository/central' }
              maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
            }
            def reactNativeGradlePlugin = new File(
              providers.exec {
                workingDir(rootDir)
                commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
              }.standardOutput.asText.get().trim()
            ).getParentFile().absolutePath
            includeBuild(reactNativeGradlePlugin)
            
            def expoPluginsPath = new File(
              providers.exec {
                workingDir(rootDir)
                commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
              }.standardOutput.asText.get().trim(),
              "../android/expo-gradle-plugin"
            ).absolutePath
            includeBuild(expoPluginsPath)
          }
          
          plugins {
            id("com.facebook.react.settings")
            id("expo-autolinking-settings")
          }
          
          extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
            if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
              ex.autolinkLibrariesFromCommand()
            } else {
              ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
            }
          }
          expoAutolinking.useExpoModules()
          
          rootProject.name = '萝卜社区'
          
          expoAutolinking.useExpoVersionCatalog()
          
          include ':app'
          includeBuild(expoAutolinking.reactNativeGradlePlugin)
          SETTINGS_EOF
          
          # Update build.gradle with Aliyun Maven mirror
          cat > build.gradle << 'BUILD_EOF'
          // Top-level build file where you can add configuration options common to all sub-projects/modules.
          
          buildscript {
            repositories {
              google()
              mavenCentral()
              maven { url 'https://maven.aliyun.com/repository/public' }
              maven { url 'https://maven.aliyun.com/repository/google' }
              maven { url 'https://maven.aliyun.com/repository/central' }
              maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
            }
            dependencies {
              classpath('com.android.tools.build:gradle')
              classpath('com.facebook.react:react-native-gradle-plugin')
              classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
            }
          }
          
          allprojects {
            repositories {
              google()
              mavenCentral()
              maven { url 'https://www.jitpack.io' }
              maven { url 'https://maven.aliyun.com/repository/public' }
              maven { url 'https://maven.aliyun.com/repository/google' }
              maven { url 'https://maven.aliyun.com/repository/central' }
            }
          }
          
          apply plugin: "expo-root-project"
          apply plugin: "com.facebook.react.rootproject"
          BUILD_EOF
          
          # Update gradle.properties
          cat > gradle.properties << 'PROPS_EOF'
          org.gradle.daemon=false
          org.gradle.parallel=true
          org.gradle.configureondemand=true
          org.gradle.workers.max=4
          org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
          android.enableJetifier=false
          android.useAndroidX=true
          hermesEnabled=true
          reactNativeArchitectures=arm64-v8a
          newArchEnabled=true
          edgeToEdgeEnabled=true
          expo.gif.enabled=true
          expo.webp.enabled=true
          expo.webp.animated=false
          expo.useLegacyPackaging=false
          PROPS_EOF

      - name: Build Android release APK (arm64 only)
        run: |
          cd android
          chmod +x gradlew
          ./gradlew :app:assembleRelease -PreactNativeArchitectures=arm64-v8a --max-workers=4 --parallel

      - name: Upload APK artifact
        uses: actions/upload-artifact@v3
        with:
          name: carrot-bbs-android-release-apk
          path: android/app/build/outputs/apk/release/app-release.apk

  build-and-push-web:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Log in to Gitea Container Registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.GIT_USERNAME }}
          password: ${{ secrets.GIT_TOKEN }}

      - name: Extract metadata for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=raw,value=latest,enable={{is_default_branch}}
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: Dockerfile.web
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          platforms: linux/amd64
          provenance: false

      - name: Show image tags
        run: |
          echo "Built image tags:"
          echo "${{ steps.meta.outputs.tags }}"
          echo "Digest: ${{ steps.meta.outputs.digest }}"

      - name: Summary
        if: always()
        run: |
          echo "## Frontend CI Summary" >> "$GITHUB_STEP_SUMMARY"
          echo "" >> "$GITHUB_STEP_SUMMARY"
          echo "**Web image:** ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> "$GITHUB_STEP_SUMMARY"
          echo "**Tags:**" >> "$GITHUB_STEP_SUMMARY"
          echo "${{ steps.meta.outputs.tags }}" >> "$GITHUB_STEP_SUMMARY"
          echo "**Digest:** ${{ steps.meta.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY"
