name: iOS TestFlight

on:
  schedule:
    - cron: '0 5 * * *'  # 9 PM Pacific (UTC-8) / 10 PM PDT (UTC-7)
  workflow_dispatch:
    inputs:
      skip_upload:
        description: 'Skip TestFlight upload (build and archive only)'
        required: false
        default: 'false'
        type: boolean

permissions:
  contents: read
  actions: write

env:
  # Runner-specific build directory to allow parallel builds on multiple runners
  EAGLE0_BUILD_DIR: /tmp/eagle0-${{ github.run_id }}
  # Runner-specific keychain to avoid conflicts when multiple runners sign simultaneously
  KEYCHAIN_NAME: ios-build-${{ github.run_id }}.keychain

jobs:
  check-changes:
    # Skip nightly builds when nothing has changed since the last successful
    # scheduled run. Manual workflow_dispatch always builds.
    runs-on: ubuntu-latest
    outputs:
      should_build: ${{ steps.check.outputs.should_build }}
    steps:
      - name: Check for relevant changes since last successful run
        id: check
        uses: actions/github-script@v9
        with:
          script: |
            if (context.eventName === 'workflow_dispatch') {
              core.setOutput('should_build', 'true');
              console.log('Manual trigger — building');
              return;
            }

            const { data: { workflow_runs: runs } } = await github.rest.actions.listWorkflowRuns({
              owner: context.repo.owner,
              repo: context.repo.repo,
              workflow_id: 'ios_testflight.yml',
              status: 'success',
              event: 'schedule',
              per_page: 1,
            });

            if (runs.length === 0) {
              core.setOutput('should_build', 'true');
              console.log('No previous successful scheduled run — building');
              return;
            }

            const lastSha = runs[0].head_sha;
            console.log(`Last successful scheduled run: ${lastSha}`);
            console.log(`Current SHA: ${context.sha}`);

            if (lastSha === context.sha) {
              core.setOutput('should_build', 'false');
              console.log('No changes since last successful run — skipping');
              return;
            }

            // Paths that can affect the iOS Unity build.
            const relevantPrefixes = [
              'src/main/csharp/',     // Unity project
              'src/main/protobuf/',   // Generates C# code
              'src/main/resources/',  // Game data & maps
              'scripts/',             // Build scripts
              'ci/',                  // CI scripts & workflows
              '.github/workflows/ios_testflight.yml',
              'MODULE.bazel',
              'MODULE.bazel.lock',
              '.bazelrc',
            ];

            const { data: comparison } = await github.rest.repos.compareCommitsWithBasehead({
              owner: context.repo.owner,
              repo: context.repo.repo,
              basehead: `${lastSha}...${context.sha}`,
              per_page: 1,  // We only need the file list, not patches
            });

            // If the diff is too large, build to be safe
            if (comparison.files === undefined) {
              core.setOutput('should_build', 'true');
              console.log('Could not retrieve file list — building to be safe');
              return;
            }

            const relevant = comparison.files.filter(f =>
              relevantPrefixes.some(p => f.filename.startsWith(p))
            );

            if (relevant.length > 0) {
              core.setOutput('should_build', 'true');
              console.log(`${relevant.length} relevant file(s) changed:`);
              relevant.slice(0, 20).forEach(f => console.log(`  ${f.filename}`));
              if (relevant.length > 20) console.log(`  ... and ${relevant.length - 20} more`);
            } else {
              core.setOutput('should_build', 'false');
              console.log(`${comparison.files.length} file(s) changed, none in relevant paths — skipping`);
            }

  build-unity:
    needs: check-changes
    if: needs.check-changes.outputs.should_build == 'true'
    runs-on: [self-hosted, macOS, testflight]

    steps:
      - name: Prune stale PR refs
        run: |
          # Self-hosted runners persist .git between runs. When a PR is updated,
          # old local refs (refs/remotes/pull/*/merge) may point to commits whose
          # objects were never fetched or have been pruned. Remove these stale refs
          # before checkout to prevent "missing object" errors.
          if [ -d ".git" ]; then
            echo "Pruning stale PR refs..."
            git for-each-ref --format='%(refname)' refs/remotes/pull/ 2>/dev/null | \
              xargs -r git update-ref -d 2>/dev/null || true
          fi

      - name: Ensure Git LFS available for checkout
        run: |
          COMMON_PATHS="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
          export PATH="${COMMON_PATHS}:${PATH}"
          for path in /opt/homebrew/bin /usr/local/bin; do
            if [ -d "$path" ]; then
              echo "$path" >> "$GITHUB_PATH"
            fi
          done
          if ! command -v git-lfs >/dev/null 2>&1; then
            brew install git-lfs
          fi
          git-lfs --version

      - uses: actions/checkout@v7
        env:
          GIT_LFS_SKIP_SMUDGE: 1
        with:
          persist-credentials: false
          lfs: false  # Fetch LFS after checkout to avoid stale ref issues
          clean: false  # Library/ persists between runs on self-hosted runners
          fetch-depth: 0

      - name: Clean stale files
        run: |
          git clean -ffd
          rm -rf src/main/csharp/net/eagle0/clients/unity/eagle0/Library/Bee/

      - name: Fetch LFS files
        env:
          GITHUB_TOKEN: ${{ github.token }}
        run: ./ci/github_actions/fetch_lfs.sh

      - name: Ensure Unity version installed
        run: ./ci/github_actions/ensure_unity_installed.sh ios

      - name: Ensure Bazel installed
        uses: ./.github/actions/setup-bazel

      - name: Sync Bazel Xcode config
        run: ./scripts/sync_bazel_xcode.sh

      - name: Build iOS Unity Project
        id: build
        run: |
          ./ci/github_actions/build_unity_ios.sh "$EAGLE0_BUILD_DIR/eagle0iOS"

      - name: Upload Addressables to CDN
        if: success() && github.event.inputs.skip_upload != 'true'
        env:
          ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
          SECRET_KEY: ${{ secrets.SECRET_KEY }}
        run: ./ci/github_actions/upload_addressables.sh iOS

      - name: Purge CDN cache for iOS addressables
        if: success() && github.event.inputs.skip_upload != 'true'
        env:
          DO_CDN_PAT: ${{ secrets.DO_CDN_PAT }}
        run: |
          UNITY_VERSION=$(grep "m_EditorVersion:" src/main/csharp/net/eagle0/clients/unity/eagle0/ProjectSettings/ProjectVersion.txt | head -1 | sed 's/m_EditorVersion: //')
          UNITY_MAJOR_MINOR=$(echo "$UNITY_VERSION" | sed -E 's/^([0-9]+)\.([0-9]+).*/\1.\2/')
          curl -s -X DELETE "https://api.digitalocean.com/v2/cdn/endpoints/8c98df29-6f0c-4704-8e82-ca40a2b81aa8/cache" \
            -H "Authorization: Bearer $DO_CDN_PAT" \
            -H "Content-Type: application/json" \
            -d "{\"files\": [\"addressables/iOS/$UNITY_MAJOR_MINOR/*\"]}" \
            --fail || echo "Warning: CDN purge failed (non-fatal)"

      - name: Archive Build Log
        if: success() || failure()
        uses: actions/upload-artifact@v7
        with:
          name: editor_ios.log
          path: ${{ env.EAGLE0_BUILD_DIR }}/editor_ios.log
          retention-days: 3

      - name: Package generated iOS project
        if: success()
        run: |
          tar -czf "$RUNNER_TEMP/eagle0-ios-project.tar.gz" -C "$EAGLE0_BUILD_DIR" eagle0iOS

      - name: Upload generated iOS project
        if: success()
        uses: actions/upload-artifact@v7
        with:
          name: eagle0-ios-project-${{ github.run_id }}
          path: ${{ runner.temp }}/eagle0-ios-project.tar.gz
          retention-days: 1

      - name: Cleanup build directory
        if: always()
        run: rm -rf "${{ env.EAGLE0_BUILD_DIR }}"

  archive-and-upload:
    needs: build-unity
    runs-on: [self-hosted, macOS, testflight]

    steps:
      - name: Ensure Git LFS available for checkout
        run: |
          COMMON_PATHS="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
          export PATH="${COMMON_PATHS}:${PATH}"
          for path in /opt/homebrew/bin /usr/local/bin; do
            if [ -d "$path" ]; then
              echo "$path" >> "$GITHUB_PATH"
            fi
          done
          if ! command -v git-lfs >/dev/null 2>&1; then
            brew install git-lfs
          fi
          git-lfs --version

      - uses: actions/checkout@v7
        env:
          GIT_LFS_SKIP_SMUDGE: 1
        with:
          persist-credentials: false
          lfs: false

      - name: Clean build directory
        run: rm -rf "${{ env.EAGLE0_BUILD_DIR }}"

      - name: Download generated iOS project
        uses: actions/download-artifact@v8
        with:
          name: eagle0-ios-project-${{ github.run_id }}
          path: ${{ runner.temp }}

      - name: Extract generated iOS project
        run: |
          mkdir -p "$EAGLE0_BUILD_DIR"
          tar -xzf "$RUNNER_TEMP/eagle0-ios-project.tar.gz" -C "$EAGLE0_BUILD_DIR"

      - name: Delete generated iOS project artifact
        uses: actions/github-script@v9
        with:
          script: |
            const artifactName = 'eagle0-ios-project-${{ github.run_id }}';
            const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
              owner: context.repo.owner,
              repo: context.repo.repo,
              run_id: context.runId,
            });

            const artifact = artifacts.find(a => a.name === artifactName);
            if (artifact === undefined) {
              console.log(`Artifact not found: ${artifactName}`);
              return;
            }

            await github.rest.actions.deleteArtifact({
              owner: context.repo.owner,
              repo: context.repo.repo,
              artifact_id: artifact.id,
            });
            console.log(`Deleted artifact: ${artifactName} (${artifact.id})`);

      - name: Install Signing Certificate
        env:
          IOS_CERTIFICATE: ${{ secrets.IOS_CERTIFICATE }}
          IOS_CERTIFICATE_PWD: ${{ secrets.IOS_CERTIFICATE_PWD }}
        run: |
          # Generate random keychain password
          KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
          echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV

          # Decode certificate
          echo "$IOS_CERTIFICATE" | base64 --decode > certificate.p12

          # Delete any existing keychain from previous runs
          security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true

          # Create temporary keychain
          security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
          security default-keychain -s "$KEYCHAIN_NAME"
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
          security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME"

          # Import certificate
          security import certificate.p12 -k "$KEYCHAIN_NAME" -P "$IOS_CERTIFICATE_PWD" -T /usr/bin/codesign -T /usr/bin/security
          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"

          # Add keychain to search list
          security list-keychains -d user -s "$KEYCHAIN_NAME" login.keychain

          rm certificate.p12

      - name: Install Provisioning Profile
        env:
          IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }}
        run: |
          # Decode provisioning profile
          echo "$IOS_PROVISIONING_PROFILE" | base64 --decode > profile.mobileprovision

          # Extract UUID from provisioning profile
          PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print :UUID" /dev/stdin <<< $(security cms -D -i profile.mobileprovision))
          echo "PROFILE_UUID=$PROFILE_UUID" >> $GITHUB_ENV

          # Install to standard location
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE_UUID.mobileprovision

          rm profile.mobileprovision
          echo "Installed provisioning profile: $PROFILE_UUID"

      - name: Archive and Export IPA
        env:
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
          APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
        run: |
          # Write API key to file for xcodebuild
          API_KEY_PATH=$(mktemp)
          echo "$APP_STORE_CONNECT_API_KEY" > "$API_KEY_PATH"
          export APP_STORE_CONNECT_API_KEY_PATH="$API_KEY_PATH"

          chmod +x ./ci/github_actions/archive_ios.sh
          ./ci/github_actions/archive_ios.sh "$EAGLE0_BUILD_DIR/eagle0iOS" "$EAGLE0_BUILD_DIR/archive" "$APPLE_TEAM_ID" "$PROFILE_UUID"

          rm -f "$API_KEY_PATH"

      - name: Upload to TestFlight
        if: ${{ github.event.inputs.skip_upload != 'true' }}
        env:
          # App Store Connect API Key (replaces deprecated altool with Apple ID)
          # Create at: https://appstoreconnect.apple.com/access/api
          APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
          APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
        run: |
          # Write API key to file (xcodebuild needs a file path)
          API_KEY_PATH=$(mktemp)
          echo "$APP_STORE_CONNECT_API_KEY" > "$API_KEY_PATH"
          export APP_STORE_CONNECT_API_KEY_PATH="$API_KEY_PATH"

          chmod +x ./ci/github_actions/upload_testflight.sh
          ./ci/github_actions/upload_testflight.sh "$EAGLE0_BUILD_DIR/archive/eagle0.xcarchive" "$APPLE_TEAM_ID" "$PROFILE_UUID"

          # Cleanup
          rm -f "$API_KEY_PATH"

      - name: Upload IPA artifact
        # Only keep artifact if we skipped TestFlight upload (for debugging)
        if: success() && github.event.inputs.skip_upload == 'true'
        uses: actions/upload-artifact@v7
        with:
          name: eagle0-ios-${{ github.run_id }}
          path: ${{ env.EAGLE0_BUILD_DIR }}/archive/eagle0.ipa
          retention-days: 1

      - name: Cleanup Keychain
        if: always()
        run: |
          security delete-keychain "$KEYCHAIN_NAME" 2>/dev/null || true
      - name: Cleanup build directory
        if: always()
        run: rm -rf "${{ env.EAGLE0_BUILD_DIR }}"
