name: Update Entitlements DB

on:
  schedule:
    - cron: "0 19 * * *" # daily at 11:00 PST (19:00 UTC)
  workflow_dispatch:
    inputs:
      version:
        description: "Version to check (e.g. 18.0, leave empty for latest)"
        required: false
        type: string
      force_update:
        description: "Force update even if no new IPSWs detected"
        required: false
        default: false
        type: boolean
      platforms:
        description: "Platforms to check (comma-separated: ios,macos)"
        required: false
        default: "ios,macos"
        type: string

permissions:
  contents: write

concurrency:
  group: update-entitlements-db
  cancel-in-progress: false

jobs:
  update-entitlements-db:
    runs-on: macos-latest
    timeout-minutes: 120
    steps:
      # credentials stay persisted: the last step commits + pushes hack/.ents_cache
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6 # zizmor: ignore[artipacked]
      - name: Set up Go
        uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
        with:
          go-version: "1.26"

      - name: Build ipsw CLI
        run: |
          go build -o ipsw ./cmd/ipsw

      - name: Determine latest IPSW URLs
        id: get-ipsws
        env:
          PLATFORMS: ${{ github.event.inputs.platforms || 'ios,macos' }}
          VERSION: ${{ github.event.inputs.version }}
        run: |
          echo "Checking platforms: $PLATFORMS"
          echo "Requested version: ${VERSION:-latest}"

          # Function to get latest IPSW URL for a platform
          get_ipsw_url() {
            local device="$1"
            local platform_name="$2"
            local get_cmd=(./ipsw dl ipsw)

            if [ "$platform_name" = "macOS" ]; then
              get_cmd+=(--macos)
            else
              get_cmd+=(--device "$device")
            fi

            if [ -n "$VERSION" ]; then
              get_cmd+=(--version "$VERSION")
            else
              get_cmd+=(--latest)
            fi

            get_cmd+=(--urls)

            # Try to get latest IPSW URL, handle errors gracefully
            url=$("${get_cmd[@]}" 2>/dev/null | head -1 || echo "")

            if [ -n "$url" ] && [ "$url" != "null" ]; then
              echo "$url"
            else
              echo ""
            fi
          }

          # Initialize URLs
          IOS_URL=""
          MACOS_URL=""

          # Check each requested platform
          if echo "$PLATFORMS" | grep -q "ios"; then
            IOS_URL=$(get_ipsw_url "iPhone17,1" "iOS")
          fi

          if echo "$PLATFORMS" | grep -q "macos"; then
            MACOS_URL=$(get_ipsw_url "" "macOS")
          fi

          # An empty URL silently skips the platform downstream — surface it
          # so a broken `ipsw dl` lookup doesn't leave the DB quietly stale.
          if echo "$PLATFORMS" | grep -q "ios" && [ -z "$IOS_URL" ]; then
            echo "::warning::iOS IPSW URL lookup returned nothing; iOS will be skipped"
          fi
          if echo "$PLATFORMS" | grep -q "macos" && [ -z "$MACOS_URL" ]; then
            echo "::warning::macOS IPSW URL lookup returned nothing; macOS will be skipped"
          fi

          # Export URLs to environment
          {
            echo "IOS_URL=$IOS_URL"
            echo "MACOS_URL=$MACOS_URL"
          } >> "$GITHUB_ENV"

      - name: Check for new IPSWs
        id: check-ipsws
        env:
          FORCE_UPDATE: ${{ github.event.inputs.force_update }}
        run: |
          # Last successfully ingested URLs live in their own cache file:
          # hack/.watch_cache is round-tripped through `ipsw watch` structs by
          # discord.yml every 30 minutes, which drops any keys it doesn't know
          # about (this is what broke the old .latest_ipsw.{ios,macos}_url keys).
          LAST_IOS=$(jq -r '.ios_url // ""' hack/.ents_cache 2>/dev/null || echo "")
          LAST_MACOS=$(jq -r '.macos_url // ""' hack/.ents_cache 2>/dev/null || echo "")

          echo "=== Cached URLs ==="
          echo "iOS: $LAST_IOS"
          echo "macOS: $LAST_MACOS"

          echo "=== Current URLs ==="
          echo "iOS: $IOS_URL"
          echo "macOS: $MACOS_URL"

          # Check for changes
          SHOULD_UPDATE_IOS="false"
          SHOULD_UPDATE_MACOS="false"
          SHOULD_UPDATE_ANY="false"

          if [ -n "$IOS_URL" ] && [ "$LAST_IOS" != "$IOS_URL" ]; then
            echo "iOS IPSW changed: $LAST_IOS -> $IOS_URL"
            SHOULD_UPDATE_IOS="true"
            SHOULD_UPDATE_ANY="true"
          fi

          if [ -n "$MACOS_URL" ] && [ "$LAST_MACOS" != "$MACOS_URL" ]; then
            echo "macOS IPSW changed: $LAST_MACOS -> $MACOS_URL"
            SHOULD_UPDATE_MACOS="true"
            SHOULD_UPDATE_ANY="true"
          fi

          # Force update if requested
          if [ "$FORCE_UPDATE" = "true" ]; then
            echo "Force update requested"
            SHOULD_UPDATE_ANY="true"
            if [ -n "$IOS_URL" ]; then SHOULD_UPDATE_IOS="true"; fi
            if [ -n "$MACOS_URL" ]; then SHOULD_UPDATE_MACOS="true"; fi
          fi

          # Export update flags
          {
            echo "should_update_ios=$SHOULD_UPDATE_IOS"
            echo "should_update_macos=$SHOULD_UPDATE_MACOS"
            echo "should_update_any=$SHOULD_UPDATE_ANY"
          } >> "$GITHUB_OUTPUT"

          if [ "$SHOULD_UPDATE_ANY" = "true" ]; then
            echo "Will proceed with database updates"
          else
            echo "No new IPSWs found; skipping updates"
          fi

      - name: Free up disk space
        if: steps.check-ipsws.outputs.should_update_any == 'true'
        run: |
          echo "=== Disk space before cleanup ==="
          df -h /System/Volumes/Data
          # This job only needs Go, hdiutil, and /usr/bin/aea — simulator
          # runtimes (~25GB) and non-selected Xcode versions (~15-30GB each)
          # are dead weight, and a single IPSW + its decrypted FileSystem DMG
          # needs ~30GB of scratch space.
          sudo xcrun simctl runtime delete all 2>/dev/null || true
          KEEP="$(dirname "$(dirname "$(xcode-select -p)")")"
          for app in /Applications/Xcode*.app; do
            if [ -d "$app" ] && [ "$app" != "$KEEP" ]; then
              sudo rm -rf "$app" &
            fi
          done
          wait
          echo "=== Disk space after cleanup ==="
          df -h /System/Volumes/Data

      - name: Update entitlements database
        id: process
        if: steps.check-ipsws.outputs.should_update_any == 'true'
        env:
          SHOULD_UPDATE_IOS: ${{ steps.check-ipsws.outputs.should_update_ios }}
          SHOULD_UPDATE_MACOS: ${{ steps.check-ipsws.outputs.should_update_macos }}
        run: |
          echo "Updating Supabase entitlements database with replacement support..."

          # Download and ingest one platform at a time: an IPSW plus its
          # extracted + AEA-decrypted FileSystem DMG needs ~30GB of scratch
          # space, so holding both IPSWs at once blows the runner's disk.
          update_platform() {
            local platform="$1"
            local url="$2"
            local ipsw_file="$3"

            echo "=== Disk space before $platform ==="
            df -h /System/Volumes/Data

            echo "Downloading $platform IPSW: $url"
            # --retry alone skips non-"transient" failures like mid-transfer
            # connection resets (curl exit 18/56) and restarts from byte 0;
            # --retry-all-errors + --continue-at resumes the partial file.
            curl --fail --silent --show-error --location \
              --retry 3 --retry-all-errors --continue-at - \
              "$url" -o "$ipsw_file" || return 1
            ls -lh "$ipsw_file"

            ./ipsw ent --ipsw "$ipsw_file" --replace \
              --pg-host "${{ secrets.SUPABASE_HOST }}" \
              --pg-port 5432 \
              --pg-user "${{ secrets.SUPABASE_USER }}" \
              --pg-password "${{ secrets.SUPABASE_PASSWORD }}" \
              --pg-database postgres \
              --pg-sslmode require \
              --pg-poolmode session || return 1

            echo "$platform database update completed successfully"
          }

          # Remove the IPSW and any DMGs left behind by a failed scan so the
          # next platform starts with a clean disk.
          cleanup_platform() {
            rm -f "$1"
            find . -maxdepth 1 \( -name '*.dmg' -o -name '*.dmg.aea' \) -delete
          }

          FAILED=0

          if [ "$SHOULD_UPDATE_IOS" = "true" ]; then
            if update_platform "iOS" "$IOS_URL" "ios_latest.ipsw"; then
              echo "ios_done=true" >> "$GITHUB_OUTPUT"
            else
              echo "::error::iOS entitlements update failed"
              FAILED=1
            fi
            cleanup_platform "ios_latest.ipsw"
          fi

          if [ "$SHOULD_UPDATE_MACOS" = "true" ]; then
            if update_platform "macOS" "$MACOS_URL" "macos_latest.ipsw"; then
              echo "macos_done=true" >> "$GITHUB_OUTPUT"
            else
              echo "::error::macOS entitlements update failed"
              FAILED=1
            fi
            cleanup_platform "macos_latest.ipsw"
          fi

          exit $FAILED

      - name: Update cache and commit
        # always(): record any platform that succeeded even if the other
        # failed, so the next run doesn't pointlessly re-ingest it.
        if: always() && steps.check-ipsws.outputs.should_update_any == 'true'
        env:
          IOS_DONE: ${{ steps.process.outputs.ios_done }}
          MACOS_DONE: ${{ steps.process.outputs.macos_done }}
        run: |
          NEW_IOS=$(jq -r '.ios_url // ""' hack/.ents_cache 2>/dev/null || echo "")
          NEW_MACOS=$(jq -r '.macos_url // ""' hack/.ents_cache 2>/dev/null || echo "")

          UPDATED_PLATFORMS=""
          if [ "$IOS_DONE" = "true" ]; then
            NEW_IOS="$IOS_URL"
            UPDATED_PLATFORMS="${UPDATED_PLATFORMS}iOS "
          fi
          if [ "$MACOS_DONE" = "true" ]; then
            NEW_MACOS="$MACOS_URL"
            UPDATED_PLATFORMS="${UPDATED_PLATFORMS}macOS "
          fi

          if [ -z "$UPDATED_PLATFORMS" ]; then
            echo "No platforms were ingested; nothing to commit"
            exit 0
          fi

          jq -n --arg ios_url "$NEW_IOS" --arg macos_url "$NEW_MACOS" \
            '{ios_url: $ios_url, macos_url: $macos_url}' > hack/.ents_cache

          # Configure git
          git config --local user.name "github-actions[bot]"
          git config --local user.email "github-actions[bot]@users.noreply.github.com"

          git add hack/.ents_cache
          if ! git commit -m "chore(ents): update entitlements DB for ${UPDATED_PLATFORMS}[skip ci]"; then
            echo "No changes to commit"
            exit 0
          fi

          # Pull latest changes and rebase before pushing to avoid conflicts
          git pull --rebase origin master || {
            echo "Rebase failed, attempting to continue..."
            git rebase --abort
            git pull --no-rebase origin master
          }

          git push
