name: iOS CI

on:
  workflow_call:

permissions:
  contents: read
  pull-requests: write
  issues: write

# Single job for efficient execution on shared runner
jobs:
  build-lint-test:
    name: Build, Lint, and Test iOS
    runs-on: [self-hosted, macOS, ARM64]
    timeout-minutes: 30
    env:
      GITHUB_REPO_NAME: ${{ github.repository }}
    
    steps:
    - name: Clean workspace
      run: |
        # Clean workspace for self-hosted runner
        # Clean workspace but preserve .git directory
        find . -maxdepth 1 -name '.*' -not -name '.git' -not -name '.' -not -name '..' -exec rm -rf {} + || true
        rm -rf * || true
        
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Verify Xcode
      run: |
        xcodebuild -version
        swift --version
    
    # Node.js/pnpm not needed for iOS builds
    
    - name: Cache Homebrew packages
      uses: actions/cache@v4
      continue-on-error: true
      with:
        path: |
          ~/Library/Caches/Homebrew
          /opt/homebrew/Cellar/swiftlint
          /opt/homebrew/Cellar/swiftformat
          /opt/homebrew/Cellar/xcbeautify
        key: ${{ runner.os }}-brew-${{ hashFiles('.github/workflows/ios.yml') }}
        restore-keys: |
          ${{ runner.os }}-brew-
    
    - name: Cache Swift packages
      uses: actions/cache@v4
      continue-on-error: true
      with:
        path: |
          ~/Library/Developer/Xcode/DerivedData
          ~/.swiftpm
        key: ${{ runner.os }}-spm-${{ hashFiles('ios/VibeTunnel-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}
        restore-keys: |
          ${{ runner.os }}-spm-
    
    - name: Install all tools
      shell: bash
      run: |
        # Skip Homebrew update for faster CI
        export HOMEBREW_NO_AUTO_UPDATE=1
        
        # Retry logic for brew commands to handle concurrent access
        MAX_ATTEMPTS=5
        WAIT_TIME=5
        
        for attempt in $(seq 1 $MAX_ATTEMPTS); do
          echo "Attempting Homebrew operations (attempt $attempt/$MAX_ATTEMPTS)"
          
          # Check if another brew process is running
          if pgrep -x "brew" > /dev/null; then
            echo "Another brew process detected, waiting ${WAIT_TIME}s..."
            sleep $WAIT_TIME
            WAIT_TIME=$((WAIT_TIME * 2))  # Exponential backoff
            continue
          fi
          
          # Install tools without updating Homebrew
          # brew install automatically upgrades if already installed
          if brew install swiftlint swiftformat xcbeautify; then
            echo "Successfully installed/upgraded all tools"
            break
          else
            if [ $attempt -eq $MAX_ATTEMPTS ]; then
              echo "Failed to install tools after $MAX_ATTEMPTS attempts"
              exit 1
            fi
            echo "Command failed, waiting ${WAIT_TIME}s before retry..."
            sleep $WAIT_TIME
            WAIT_TIME=$((WAIT_TIME * 2))  # Exponential backoff
          fi
        done
        
        # Show versions
        echo "SwiftLint: $(swiftlint --version || echo 'not found')"
        echo "SwiftFormat: $(swiftformat --version || echo 'not found')"
        echo "xcbeautify: $(xcbeautify --version || echo 'not found')"
        echo "PATH: $PATH"
    
    # iOS doesn't need web dependencies - skip pnpm entirely
    
    - name: Resolve Dependencies (once)
      run: |
        cd ios
        echo "Resolving iOS package dependencies..."
        xcodebuild -resolvePackageDependencies -workspace ../VibeTunnel.xcworkspace || echo "Dependency resolution completed"
    
    # BUILD PHASE
    - name: Build iOS app
      run: |
        cd ios
        # Ensure xcbeautify is in PATH
        export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
        
        # Use Release config for faster builds
        set -o pipefail
        xcodebuild build \
          -workspace ../VibeTunnel.xcworkspace \
          -scheme VibeTunnel-iOS \
          -destination "generic/platform=iOS" \
          -configuration Release \
          -showBuildTimingSummary \
          -quiet \
          CODE_SIGNING_ALLOWED=NO \
          CODE_SIGNING_REQUIRED=NO \
          ONLY_ACTIVE_ARCH=NO \
          -derivedDataPath build/DerivedData \
          COMPILER_INDEX_STORE_ENABLE=NO || {
            echo "::error::Build failed"
            exit 1
          }
    
    - name: List build products
      if: always()
      run: |
        echo "Searching for iOS build products..."
        find ios/build -name "*.app" -type d 2>/dev/null || echo "No build products found"
        ls -la ios/build/DerivedData/Build/Products/ 2>/dev/null || echo "Build products directory not found"
    
    # LINT PHASE
    - name: Run SwiftFormat (check mode)
      id: swiftformat
      continue-on-error: true
      run: |
        cd ios
        swiftformat . --lint 2>&1 | tee ../swiftformat-output.txt
        echo "result=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
    
    - name: Run SwiftLint
      id: swiftlint
      continue-on-error: true
      run: |
        cd ios
        swiftlint 2>&1 | tee ../swiftlint-output.txt
        echo "result=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
    
    # TEST PHASE
    - name: Create and boot simulator
      id: simulator
      run: |
        echo "Creating iOS simulator for tests..."
        
        # Generate unique simulator name to avoid conflicts
        SIMULATOR_NAME="VibeTunnel-iOS-${GITHUB_RUN_ID}-${GITHUB_JOB}-${RANDOM}"
        echo "Simulator name: $SIMULATOR_NAME"
        
        # Cleanup function
        cleanup_simulator() {
          local sim_id="$1"
          if [ -n "$sim_id" ]; then
            echo "Cleaning up simulator $sim_id..."
            xcrun simctl shutdown "$sim_id" 2>/dev/null || true
            xcrun simctl delete "$sim_id" 2>/dev/null || true
          fi
        }
        
        # Pre-cleanup: Remove old VibeTunnel test simulators from previous runs
        echo "Cleaning up old test simulators..."
        xcrun simctl list devices | grep "VibeTunnel-iOS-" | grep -E "\(.*\)" | \
          sed -n 's/.*(\(.*\)).*/\1/p' | while read -r old_sim_id; do
          cleanup_simulator "$old_sim_id"
        done
        
        # Get the latest iOS runtime
        RUNTIME=$(xcrun simctl list runtimes | grep "iOS" | tail -1 | awk '{print $NF}')
        echo "Using runtime: $RUNTIME"
        
        # Create a new simulator with retry logic
        SIMULATOR_ID=""
        for attempt in 1 2 3; do
          echo "Creating simulator (attempt $attempt)..."
          SIMULATOR_ID=$(xcrun simctl create "$SIMULATOR_NAME" "iPhone 15" "$RUNTIME" 2>/dev/null || \
                        xcrun simctl create "$SIMULATOR_NAME" "com.apple.CoreSimulator.SimDeviceType.iPhone-15" "$RUNTIME" 2>/dev/null) && break
          
          echo "Creation failed, waiting before retry..."
          sleep $((attempt * 2))
        done
        
        if [ -z "$SIMULATOR_ID" ]; then
          echo "::error::Failed to create simulator after 3 attempts"
          exit 1
        fi
        
        echo "Created simulator: $SIMULATOR_ID"
        echo "SIMULATOR_ID=$SIMULATOR_ID" >> $GITHUB_ENV
        echo "simulator_id=$SIMULATOR_ID" >> $GITHUB_OUTPUT
        
        # Boot the simulator with retry logic
        echo "Booting simulator..."
        for attempt in 1 2 3; do
          if xcrun simctl boot "$SIMULATOR_ID" 2>/dev/null; then
            echo "Simulator booted successfully"
            break
          fi
          
          # Check if already booted
          if xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -q "Booted"; then
            echo "Simulator already booted"
            break
          fi
          
          echo "Boot attempt $attempt failed, waiting..."
          sleep $((attempt * 3))
        done
        
        # Wait for simulator to be ready
        echo "Waiting for simulator to be ready..."
        for i in {1..30}; do
          if xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -q "Booted"; then
            echo "Simulator is ready"
            break
          fi
          sleep 1
        done
    
    - name: Run iOS tests
      run: |
        cd ios
        # Ensure xcbeautify is in PATH
        export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
        
        # Set up cleanup trap
        cleanup_and_exit() {
          local exit_code=$?
          echo "Test execution finished with exit code: $exit_code"
          
          # Attempt to shutdown simulator gracefully
          if [ -n "$SIMULATOR_ID" ]; then
            echo "Shutting down simulator..."
            xcrun simctl shutdown "$SIMULATOR_ID" 2>/dev/null || true
            
            # Give it a moment to shutdown
            sleep 2
            
            # Force terminate if still running
            if xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -q "Booted"; then
              echo "Force terminating simulator..."
              xcrun simctl terminate "$SIMULATOR_ID" com.apple.springboard 2>/dev/null || true
            fi
          fi
          
          exit $exit_code
        }
        trap cleanup_and_exit EXIT
        
        echo "Running iOS tests using Swift Testing framework..."
        echo "Simulator ID: $SIMULATOR_ID"
        
        # Verify simulator is still booted
        if ! xcrun simctl list devices | grep "$SIMULATOR_ID" | grep -q "Booted"; then
          echo "::error::Simulator is not in booted state"
          exit 1
        fi
        
        # Only enable coverage on main branch
        if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
          ENABLE_COVERAGE="YES"
        else
          ENABLE_COVERAGE="NO"
        fi
        
        set -o pipefail
        xcodebuild test \
          -workspace ../VibeTunnel.xcworkspace \
          -scheme VibeTunnel-iOS \
          -destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
          -resultBundlePath TestResults.xcresult \
          -enableCodeCoverage $ENABLE_COVERAGE \
          CODE_SIGN_IDENTITY="" \
          CODE_SIGNING_REQUIRED=NO \
          CODE_SIGNING_ALLOWED=NO \
          COMPILER_INDEX_STORE_ENABLE=NO \
          -quiet \
          2>&1 || {
            echo "::error::iOS tests failed"
            exit 1
          }
        
        echo "Tests completed successfully"

    # Add cleanup step that always runs
    - name: Cleanup simulator
      if: always() && steps.simulator.outputs.simulator_id != ''
      run: |
        SIMULATOR_ID="${{ steps.simulator.outputs.simulator_id }}"
        echo "Cleaning up simulator $SIMULATOR_ID..."
        
        # Shutdown simulator
        xcrun simctl shutdown "$SIMULATOR_ID" 2>/dev/null || true
        
        # Wait a bit for shutdown
        sleep 2
        
        # Delete simulator
        xcrun simctl delete "$SIMULATOR_ID" 2>/dev/null || true
        
        echo "Simulator cleanup completed"
    
    # COVERAGE EXTRACTION
    - name: Debug coverage files
      if: always()
      run: |
        cd ios
        echo "=== Checking TestResults.xcresult ==="
        if [ -f TestResults.xcresult ]; then
          echo "TestResults.xcresult exists"
          ls -la TestResults.xcresult
          
          echo "\n=== Checking for coverage data in xcresult ==="
          xcrun xcresulttool get --path TestResults.xcresult --format json 2>/dev/null | jq '.actions._values[].actionResult.coverage' 2>/dev/null | head -20 || echo "No coverage data found in xcresult"
          
          echo "\n=== Attempting direct coverage view ==="
          xcrun xccov view --report TestResults.xcresult 2>&1 | head -20 || echo "Direct coverage view failed"
        else
          echo "TestResults.xcresult not found"
          ls -la
        fi
    
    - name: Extract coverage summary
      if: always()
      id: coverage
      run: |
        cd ios
        if [ -f TestResults.xcresult ]; then
          # Try multiple extraction methods
          echo "=== Method 1: Standard extraction ==="
          COVERAGE_PCT=$(xcrun xccov view --report TestResults.xcresult --json 2>/dev/null | jq -r '.lineCoverage // 0' | awk '{printf "%.1f", $1 * 100}') || COVERAGE_PCT="0"
          
          if [ "$COVERAGE_PCT" = "0" ] || [ -z "$COVERAGE_PCT" ]; then
            echo "Method 1 failed, trying alternative methods"
            
            echo "\n=== Method 2: Without --json flag ==="
            COVERAGE_LINE=$(xcrun xccov view --report TestResults.xcresult 2>&1 | grep -E "^[0-9]+\.[0-9]+%" | head -1) || true
            if [ -n "$COVERAGE_LINE" ]; then
              COVERAGE_PCT=$(echo "$COVERAGE_LINE" | sed 's/%.*//g')
              echo "Extracted coverage from text output: $COVERAGE_PCT%"
            fi
          fi
          
          if [ "$COVERAGE_PCT" = "0" ] || [ -z "$COVERAGE_PCT" ]; then
            echo "\n=== Method 3: Check if tests ran ==="
            if xcrun xcresulttool get --path TestResults.xcresult --format json 2>/dev/null | grep -q '"testsCount"' && \
               xcrun xcresulttool get --path TestResults.xcresult --format json 2>/dev/null | jq -e '.metrics.testsCount > 0' >/dev/null 2>&1; then
              echo "Tests ran but coverage extraction failed"
              # Set to 0.1% to indicate tests ran but coverage couldn't be extracted
              COVERAGE_PCT="0.1"
            else
              echo "No tests were found or run"
              COVERAGE_PCT="0"
            fi
          fi
          
          # Create minimal summary JSON
          echo "{\"coverage\": \"$COVERAGE_PCT\"}" > coverage-summary.json
          
          echo "Final Coverage: ${COVERAGE_PCT}%"
          
          # Any coverage above 0% is acceptable for now
          if (( $(echo "$COVERAGE_PCT > 0" | bc -l) )); then
            echo "coverage_result=success" >> $GITHUB_OUTPUT
          else
            echo "coverage_result=failure" >> $GITHUB_OUTPUT
          fi
        else
          echo '{"error": "No test results bundle found"}' > coverage-summary.json
          echo "coverage_result=failure" >> $GITHUB_OUTPUT
        fi
    
    # ARTIFACT UPLOADS
    # Skip build artifact upload for PR builds to save time
    - name: Upload build artifacts
      uses: actions/upload-artifact@v4
      if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main'
      with:
        name: ios-build-artifacts
        path: ios/build/DerivedData/Build/Products/Release-iphoneos/
        retention-days: 3
    
    - name: Upload coverage artifacts
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: ios-coverage
        path: |
          ios/coverage-summary.json
          ios/TestResults.xcresult
        retention-days: 1
    
    # LINT REPORTING
    - name: Read SwiftFormat Output
      if: always()
      id: swiftformat-output
      run: |
        if [ -f swiftformat-output.txt ]; then
          echo 'content<<EOF' >> $GITHUB_OUTPUT
          cat swiftformat-output.txt >> $GITHUB_OUTPUT
          echo 'EOF' >> $GITHUB_OUTPUT
        else
          echo "content=No output" >> $GITHUB_OUTPUT
        fi
    
    - name: Read SwiftLint Output
      if: always()
      id: swiftlint-output
      run: |
        if [ -f swiftlint-output.txt ]; then
          echo 'content<<EOF' >> $GITHUB_OUTPUT
          cat swiftlint-output.txt >> $GITHUB_OUTPUT
          echo 'EOF' >> $GITHUB_OUTPUT
        else
          echo "content=No output" >> $GITHUB_OUTPUT
        fi
    
    - name: Report SwiftFormat Results
      if: always()
      uses: ./.github/actions/lint-reporter
      with:
        title: 'iOS Formatting (SwiftFormat)'
        lint-result: ${{ steps.swiftformat.outputs.result == '0' && 'success' || 'failure' }}
        lint-output: ${{ steps.swiftformat-output.outputs.content }}
        github-token: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Report SwiftLint Results
      if: always()
      uses: ./.github/actions/lint-reporter
      with:
        title: 'iOS Linting (SwiftLint)'
        lint-result: ${{ steps.swiftlint.outputs.result == '0' && 'success' || 'failure' }}
        lint-output: ${{ steps.swiftlint-output.outputs.content }}
        github-token: ${{ secrets.GITHUB_TOKEN }}

  report-coverage:
    name: Report iOS Coverage
    runs-on: blacksmith-8vcpu-ubuntu-2404-arm
    needs: [build-lint-test]
    # Only run coverage reporting on main branch where we actually collect coverage
    if: always() && github.event_name == 'push' && github.ref == 'refs/heads/main'

    steps:
    - name: Clean workspace
      run: |
        # Clean workspace for self-hosted runner
        # Clean workspace but preserve .git directory
        find . -maxdepth 1 -name '.*' -not -name '.git' -not -name '.' -not -name '..' -exec rm -rf {} + || true
        rm -rf * || true
        
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Download coverage artifacts
      uses: actions/download-artifact@v4
      with:
        name: ios-coverage
        path: ios-coverage-artifacts

    - name: Read coverage summary
      id: coverage
      run: |
        if [ -f ios-coverage-artifacts/coverage-summary.json ]; then
          # Read the coverage summary
          COVERAGE_JSON=$(cat ios-coverage-artifacts/coverage-summary.json)
          echo "summary<<EOF" >> $GITHUB_OUTPUT
          echo "$COVERAGE_JSON" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
          
          # Extract coverage percentage
          COVERAGE_PCT=$(echo "$COVERAGE_JSON" | jq -r '.coverage // 0')
          
          # Any coverage above 0% is acceptable for now
          if (( $(echo "$COVERAGE_PCT > 0" | bc -l) )); then
            echo "result=success" >> $GITHUB_OUTPUT
          else
            echo "result=failure" >> $GITHUB_OUTPUT
          fi
          
          # Format output - just show the percentage
          echo "output=• Coverage: ${COVERAGE_PCT}%" >> $GITHUB_OUTPUT
        else
          echo "summary={\"error\": \"No coverage data found\"}" >> $GITHUB_OUTPUT
          echo "result=failure" >> $GITHUB_OUTPUT
          echo "output=Coverage data not found" >> $GITHUB_OUTPUT
        fi

    - name: Report Coverage Results
      uses: ./.github/actions/lint-reporter
      with:
        title: 'iOS Test Coverage'
        lint-result: ${{ steps.coverage.outputs.result }}
        lint-output: ${{ steps.coverage.outputs.output }}
        github-token: ${{ secrets.GITHUB_TOKEN }}