defaults: &defaults
  working_directory: ~/repo

android-img: &android-img
  docker:
    - image: cimg/android:2023.04.1-node

orbs:
  android: circleci/android@2.1.2

macos: &macos
  macos:
    xcode: "14.2.0"
  resource_class: macos.m1.large.gen1

bash-env: &bash-env
  BASH_ENV: "~/.nvm/nvm.sh"

android-env: &android-env
  JAVA_OPTS: '-Xms512m -Xmx2g'
  GRADLE_OPTS: '-Xmx3g -Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx2g -XX:+HeapDumpOnOutOfMemoryError"'
  TERM: dumb

install-npm-modules: &install-npm-modules
  name: Install NPM modules
  command: yarn

restore-npm-cache-linux: &restore-npm-cache-linux
  name: Restore NPM cache
  key: node-modules-{{ checksum "yarn.lock" }}

save-npm-cache-linux: &save-npm-cache-linux
  key: node-modules-{{ checksum "yarn.lock" }}
  name: Save NPM cache
  paths:
    - ./node_modules

restore-npm-cache-mac: &restore-npm-cache-mac
  name: Restore NPM cache
  key: node-v1-mac-{{ checksum "yarn.lock" }}

save-npm-cache-mac: &save-npm-cache-mac
  key: node-v1-mac-{{ checksum "yarn.lock" }}
  name: Save NPM cache
  paths:
    - ./node_modules

restore-gems-cache: &restore-gems-cache
  name: Restore gems cache
  key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}

save-gems-cache: &save-gems-cache
  name: Save gems cache
  key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}
  paths:
    - vendor/bundle

update-fastlane-ios: &update-fastlane-ios
  name: Update Fastlane
  command: |
    bundle install
  working_directory: ios

update-fastlane-android: &update-fastlane-android
  name: Update Fastlane
  command: |
    bundle config set --local deployment 'true'
    bundle install
  working_directory: android

save-gradle-cache: &save-gradle-cache
  name: Save gradle cache
  key: android-{{ checksum "android/build.gradle" }}-{{ checksum  "android/app/build.gradle" }}
  paths:
    - ~/.gradle

restore_cache: &restore-gradle-cache
  name: Restore gradle cache
  key: android-{{ checksum "android/build.gradle" }}-{{ checksum  "android/app/build.gradle" }}

# COMMANDS
commands:

  manage-pods:
    description: "Restore/Get/Save cache of pods libs"
    steps:
      - restore_cache:
          name: Restore pods
          key: pods-v1-{{ checksum "ios/Podfile.lock" }}
      - run:
          name: Install pods libs
          command: |
            bundle exec pod install --deployment
          working_directory: ios
      - save_cache:
          name: Save pods specs and pods cache
          key: pods-v1-{{ checksum "ios/Podfile.lock" }}
          paths:
            - ~/.pods
            - ios/Pods

  android-build:
    description: "Build Android app"
    steps:
      - checkout

      - restore_cache: *restore-npm-cache-linux

      - run: *install-npm-modules

      - restore_cache: *restore-gradle-cache

      - run:
          name: Configure Gradle
          command: |
            echo -e "" > ./gradle.properties
            echo -e "android.useAndroidX=true" >> ./gradle.properties
            echo -e "android.enableJetifier=true" >> ./gradle.properties
            echo -e "newArchEnabled=false" >> ./gradle.properties
            echo -e "hermesEnabled=true" >> ./gradle.properties
            echo -e "FLIPPER_VERSION=0.125.0" >> ./gradle.properties
            echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties

            if [[ $CIRCLE_JOB == "android-build-official" ]]; then
              echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties
              echo -e "BugsnagAPIKey=$BUGSNAG_KEY_OFFICIAL" >> ./gradle.properties
              echo $KEYSTORE_OFFICIAL_BASE64 | base64 --decode > ./app/$KEYSTORE_OFFICIAL
              echo -e "KEYSTORE=$KEYSTORE_OFFICIAL" >> ./gradle.properties
              echo -e "KEYSTORE_PASSWORD=$KEYSTORE_OFFICIAL_PASSWORD" >> ./gradle.properties
              echo -e "KEY_ALIAS=$KEYSTORE_OFFICIAL_ALIAS" >> ./gradle.properties
              echo -e "KEY_PASSWORD=$KEYSTORE_OFFICIAL_PASSWORD" >> ./gradle.properties
            else
              echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
              echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
              echo $KEYSTORE_EXPERIMENTAL_BASE64 | base64 --decode > ./app/$KEYSTORE_EXPERIMENTAL
              echo -e "KEYSTORE=$KEYSTORE_EXPERIMENTAL" >> ./gradle.properties
              echo -e "KEYSTORE_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
              echo -e "KEY_ALIAS=$KEYSTORE_EXPERIMENTAL_ALIAS" >> ./gradle.properties
              echo -e "KEY_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
            fi
          working_directory: android

      - run:
          name: Set Google Services
          command: |
            if [[ $GOOGLE_SERVICES_ANDROID ]]; then
              echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json
            fi
          working_directory: android/app

      - run:
          name: Build App
          no_output_timeout: 40m
          command: |
            sudo apt install -y libicu-dev
            if [[ $CIRCLE_JOB == "android-build-official" ]]; then
              ./gradlew bundleOfficialPlayRelease
            fi
            if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
              ./gradlew bundleExperimentalPlayRelease
            fi
            if [[ ! $GOOGLE_SERVICES_ANDROID ]]; then
              ./gradlew assembleExperimentalPlayDebug
            fi
          working_directory: android

      - run:
          name: Upload sourcemaps to Bugsnag
          command: |
            if [[ $CIRCLE_JOB == "android-build-official" ]]; then
              npx bugsnag-source-maps upload-react-native \
                --api-key=$BUGSNAG_KEY_OFFICIAL \
                --app-version-code=$CIRCLE_BUILD_NUM \
                --platform android \
                --source-map=android/app/build/generated/sourcemaps/react/officialPlayRelease/index.android.bundle.map \
                --bundle android/app/build/ASSETS/createBundleOfficialPlayReleaseJsAndAssets/index.android.bundle
            fi
            if [[ $CIRCLE_JOB == "android-build-experimental" || "android-automatic-build-experimental" ]]; then
              npx bugsnag-source-maps upload-react-native \
                --api-key=$BUGSNAG_KEY \
                --app-version-code=$CIRCLE_BUILD_NUM \
                --platform android \
                --source-map=android/app/build/generated/sourcemaps/react/experimentalPlayRelease/index.android.bundle.map \
                --bundle android/app/build/ASSETS/createBundleExperimentalPlayReleaseJsAndAssets/index.android.bundle
            fi

      - store_artifacts:
          path: android/app/build/outputs

      - save_cache: *save-npm-cache-linux

      - save_cache: *save-gradle-cache

      - persist_to_workspace:
          root: .
          paths:
            - android/app/build/outputs

  ios-build:
    description: "Build iOS app"
    steps:
      - checkout
      - restore_cache: *restore-gems-cache
      - restore_cache: *restore-npm-cache-mac
      - run: *install-npm-modules
      - run: *update-fastlane-ios
      - manage-pods
      - run:
          name: Set Google Services
          command: |
            if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
              if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
                echo $GOOGLE_SERVICES_IOS | base64 --decode > GoogleService-Info.plist
              else 
                echo $GOOGLE_SERVICES_IOS_EXPERIMENTAL | base64 --decode > GoogleService-Info.plist
              fi
            fi
          working_directory: ios
      - run:
          name: Fastlane Build
          no_output_timeout: 40m
          command: |
            agvtool new-version -all $CIRCLE_BUILD_NUM
            if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
              /usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY_OFFICIAL" ./RocketChatRN/Info.plist
              /usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY_OFFICIAL" ./ShareRocketChatRN/Info.plist
              /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./RocketChatRN/Info.plist
              /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./ShareRocketChatRN/Info.plist
              /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./NotificationService/Info.plist
            else
              /usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./RocketChatRN/Info.plist
              /usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ShareRocketChatRN/Info.plist
              /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./RocketChatRN/Info.plist
              /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./ShareRocketChatRN/Info.plist
              /usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./NotificationService/Info.plist
            fi

            if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
              echo $APP_STORE_CONNECT_API_KEY_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
              if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
                bundle exec fastlane ios build_official
              else
                if [[ $APP_STORE_CONNECT_API_KEY_BASE64 ]]; then
                  bundle exec fastlane ios build_experimental
                else
                  bundle exec fastlane ios build_fork
                fi
              fi
            fi
          working_directory: ios
      - save_cache: *save-npm-cache-mac
      - save_cache: *save-gems-cache
      - store_artifacts:
          path: ios/Rocket.Chat.ipa
      - store_artifacts:
          path: ios/Rocket.Chat.app.dSYM.zip
      - persist_to_workspace:
          root: .
          paths:
            - ios/*.ipa
            - ios/*.zip

  upload-to-google-play-beta:
    description: "Upload to Google Play beta"
    parameters:
      official:
        type: boolean
    steps:
      - checkout
      - attach_workspace:
          at: android
      - run:
          name: Store the google service account key
          command: echo "$FASTLANE_GOOGLE_SERVICE_ACCOUNT" | base64 --decode > service_account.json
          working_directory: android
      - run: *update-fastlane-android
      - run:
          name: Fastlane Play Store Upload
          command: bundle exec fastlane android beta official:<< parameters.official >>
          working_directory: android

  # EXPERIMENTAL ONLY
  upload-to-internal-app-sharing:
    description: "Upload to Internal App Sharing"
    steps:
      - checkout
      - attach_workspace:
          at: android
      - run:
          name: Store the google service account key
          command: echo "$FASTLANE_GOOGLE_SERVICE_ACCOUNT" | base64 --decode > service_account.json
          working_directory: android
      - run: *update-fastlane-android
      - run:
          name: Fastlane Play Store Upload
          command: bundle exec fastlane android internal_app_sharing
          working_directory: android

  # EXPERIMENTAL ONLY
  # No plans to do it for Official
  upload-to-google-play-production:
    description: "Upload to Google Play production"
    steps:
      - checkout
      - attach_workspace:
          at: android
      - run:
          name: Store the google service account key
          command: echo "$FASTLANE_GOOGLE_SERVICE_ACCOUNT" | base64 --decode > service_account.json
          working_directory: android
      - run: *update-fastlane-android
      - run:
          name: Fastlane Play Store Upload
          command: bundle exec fastlane android production
          working_directory: android

  upload-to-testflight:
    description: "Upload to TestFlight"
    parameters:
      official:
        type: boolean
    steps:
      - checkout
      - attach_workspace:
          at: ios
      - restore_cache: *restore-gems-cache
      - restore_cache: *restore-npm-cache-mac
      - run: *update-fastlane-ios
      - manage-pods
      - run:
          name: Fastlane Tesflight Upload
          command: |
            echo $APP_STORE_CONNECT_API_KEY_BASE64 | base64 --decode > ./fastlane/app_store_connect_api_key.p8
            bundle exec fastlane ios beta official:<< parameters.official >>
          working_directory: ios
      - save_cache: *save-gems-cache

  create-e2e-account-file:
    description: "Create e2e account file"
    steps:
      - run:
          command: |
            echo $E2E_ACCOUNT | base64 --decode > ./e2e_account.ts
          working_directory: e2e

version: 2.1

# EXECUTORS
executors:
  mac-env:
    <<: *macos
    environment:
      <<: *bash-env

# JOBS
jobs:
  lint-testunit:
    <<: *defaults
    docker:
      - image: cimg/node:16.14
    resource_class: large
    environment:
      CODECOV_TOKEN: caa771ab-3d45-4756-8e2a-e1f25996fef6

    steps:
      - checkout

      - restore_cache: *restore-npm-cache-linux

      - run: *install-npm-modules

      - run:
          name: Lint
          command: |
            yarn lint

      - run:
          name: Test
          command: |
            yarn test -w 8

      - run:
          name: Codecov
          command: |
            yarn codecov

      - save_cache: *save-npm-cache-linux

  # Android builds
  android-build-experimental:
    <<: *defaults
    <<: *android-img
    environment:
      <<: *android-env
      <<: *bash-env
    resource_class: xlarge
    steps:
      - android-build

    # Android automatic builds
  android-automatic-build-experimental:
    <<: *defaults
    <<: *android-img
    environment:
      <<: *android-env
      <<: *bash-env
    resource_class: xlarge
    steps:
      - android-build

  android-build-official:
    <<: *defaults
    <<: *android-img
    environment:
      <<: *android-env
      <<: *bash-env
    resource_class: xlarge
    steps:
      - android-build

  android-internal-app-sharing-experimental:
    <<: *defaults
    <<: *android-img

    steps:
      - upload-to-internal-app-sharing

  android-google-play-beta-experimental:
    <<: *defaults
    <<: *android-img

    steps:
      - upload-to-google-play-beta:
          official: false

  android-google-play-production-experimental:
    <<: *defaults
    <<: *android-img
    steps:
      - upload-to-google-play-production

  android-google-play-beta-official:
    <<: *defaults
    <<: *android-img

    steps:
      - upload-to-google-play-beta:
          official: true

  e2e-build-android:
    <<: *defaults
    executor:
      name: android/android-machine
      resource-class: xlarge
      tag: 2023.04.1
    environment:
      <<: *android-env
    steps:
      - checkout
      - restore_cache: *restore-npm-cache-linux
      - run: *install-npm-modules
      - save_cache: *save-npm-cache-linux
      - restore_cache: *restore-gradle-cache
      - run:
          name: Configure Gradle
          command: |
            echo -e "" > ./gradle.properties
            # echo -e "android.enableAapt2=false" >> ./gradle.properties
            echo -e "android.useAndroidX=true" >> ./gradle.properties
            echo -e "android.enableJetifier=true" >> ./gradle.properties
            echo -e "newArchEnabled=false" >> ./gradle.properties
            echo -e "hermesEnabled=true" >> ./gradle.properties
            echo -e "FLIPPER_VERSION=0.125.0" >> ./gradle.properties
            echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
            echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
            echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
            echo $KEYSTORE_EXPERIMENTAL_BASE64 | base64 --decode > ./app/$KEYSTORE_EXPERIMENTAL
            echo -e "KEYSTORE=$KEYSTORE_EXPERIMENTAL" >> ./gradle.properties
            echo -e "KEYSTORE_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
            echo -e "KEY_ALIAS=$KEYSTORE_EXPERIMENTAL_ALIAS" >> ./gradle.properties
            echo -e "KEY_PASSWORD=$KEYSTORE_EXPERIMENTAL_PASSWORD" >> ./gradle.properties
          working_directory: android
      - run:
          name: Build Android
          no_output_timeout: 20m
          command: |
            export RUNNING_E2E_TESTS=true
            yarn e2e:android-build
      - save_cache: *save-gradle-cache
      - store_artifacts:
          path: android/app/build/outputs/apk/experimentalPlay/release/app-experimental-play-release.apk
      - store_artifacts:
          path: android/app/build/outputs/apk/androidTest/experimentalPlay/release/app-experimental-play-release-androidTest.apk
      - persist_to_workspace:
          root: /home/circleci/repo
          paths:
            - android/app/build/outputs/apk/

  e2e-test-android:
    <<: *defaults
    executor:
      name: android/android-machine
      resource-class: xlarge
      tag: 2023.04.1
    parallelism: 4
    steps:
      - checkout
      - attach_workspace:
          at: /home/circleci/repo
      - restore_cache: *restore-npm-cache-linux
      - run: *install-npm-modules
      - save_cache: *save-npm-cache-linux
      - run: mkdir ~/junit
      - create-e2e-account-file
      - android/create-avd:
          avd-name: Pixel_API_31_AOSP
          install: true
          system-image: system-images;android-31;default;x86_64
      - run:
          name: Setup emulator
          command: |
            echo "hw.lcd.density = 440" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
            echo "hw.lcd.height = 2280" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
            echo "hw.lcd.width = 1080" >> ~/.android/avd/Pixel_API_31_AOSP.avd/config.ini
      - android/start-emulator:
          avd-name: Pixel_API_31_AOSP
          verbose: true
          post-emulator-launch-assemble-command: ''
      - run:
          name: Run Detox Tests
          command: |
            TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
            yarn e2e:android-test $TEST
      - store_artifacts:
          path: artifacts
      - run:
          command: cp junit.xml ~/junit/
          when: always
      - store_test_results:
          path: ~/junit
      - store_artifacts:
          path: ~/junit

  # iOS builds
  ios-build-experimental:
    executor: mac-env
    steps:
      - ios-build

  ios-build-official:
    executor: mac-env
    steps:
      - ios-build

  ios-testflight-experimental:
    executor: mac-env
    steps:
      - upload-to-testflight:
          official: false

  ios-testflight-official:
    executor: mac-env
    steps:
      - upload-to-testflight:
          official: true

  e2e-build-ios:
    executor: mac-env
    steps:
      - checkout
      - restore_cache: *restore-gems-cache
      - restore_cache: *restore-npm-cache-mac
      - run: *install-npm-modules
      - run: *update-fastlane-ios
      - save_cache: *save-npm-cache-mac
      - save_cache: *save-gems-cache
      - manage-pods
      - run:
          name: Configure Detox
          command: |
            brew tap wix/brew
            brew install applesimutils
      - run:
          name: Build
          no_output_timeout: 20m
          command: |
            /usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/RocketChatRN/Info.plist
            /usr/libexec/PlistBuddy -c "Set :bugsnag:apiKey $BUGSNAG_KEY" ./ios/ShareRocketChatRN/Info.plist
            yarn detox clean-framework-cache && yarn detox build-framework-cache
            export RUNNING_E2E_TESTS=true
            yarn e2e:ios-build
      - persist_to_workspace:
          root: /Users/distiller/project
          paths:
            - ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app
  
  e2e-test-ios:
    executor: mac-env
    parallelism: 5
    steps:
      - checkout
      - attach_workspace:
          at: /Users/distiller/project
      - restore_cache: *restore-npm-cache-mac
      - run: *install-npm-modules
      - save_cache: *save-npm-cache-mac
      - run: mkdir ~/junit
      - run:
          name: Configure Detox
          command: |
            brew tap wix/brew
            brew install applesimutils
            yarn detox clean-framework-cache && yarn detox build-framework-cache
      - create-e2e-account-file
      - run:
          name: Run tests
          command: |
            TEST=$(circleci tests glob "e2e/tests/**/*.ts" | circleci tests split --split-by=timings)
            yarn e2e:ios-test $TEST
      - store_artifacts:
          path: artifacts
      - run:
          command: cp junit.xml ~/junit/
          when: always
      - store_test_results:
          path: ~/junit
      - store_artifacts:
          path: ~/junit

workflows:
  build-and-test:
    jobs:
      - lint-testunit

      # E2E tests
      - e2e-hold:
          type: approval
      - e2e-build-ios:
          requires:
            - e2e-hold
      - e2e-test-ios:
          requires:
            - e2e-build-ios
      - e2e-build-android:
          requires:
            - e2e-hold
      - e2e-test-android:
          requires:
            - e2e-build-android

      # iOS Experimental
      - ios-hold-build-experimental:
          type: approval
          requires:
              - lint-testunit
      - ios-build-experimental:
          requires:
            - ios-hold-build-experimental
      - ios-testflight-experimental:
          requires:
            - ios-build-experimental

      # iOS Official
      - ios-hold-build-official:
          type: approval
          requires:
            - lint-testunit
      - ios-build-official:
          requires:
            - ios-hold-build-official
      - ios-hold-testflight-official:
          type: approval
          requires:
            - ios-build-official
      - ios-testflight-official:
          requires:
            - ios-hold-testflight-official

      # Android Experimental
      - android-hold-build-experimental:
          type: approval
          requires:
              - lint-testunit
          filters:
            branches:
              ignore:
                - develop
      - android-build-experimental:
          requires:
            - android-hold-build-experimental
      - android-internal-app-sharing-experimental:
          requires:
            - android-build-experimental
      - android-hold-google-play-beta-experimental:
          type: approval
          requires:
            - android-build-experimental
      - android-google-play-beta-experimental:
          requires:
            - android-hold-google-play-beta-experimental
      - android-hold-google-play-production-experimental:
          type: approval
          requires:
            - android-build-experimental
      - android-google-play-production-experimental:
          requires:
            - android-hold-google-play-production-experimental

      # Android Official
      - android-hold-build-official:
          type: approval
          requires:
            - lint-testunit
      - android-build-official:
          requires:
            - android-hold-build-official
      - android-hold-google-play-beta-official:
          type: approval
          requires:
            - android-build-official
      - android-google-play-beta-official:
          requires:
            - android-hold-google-play-beta-official

      # Android Automatic Experimental
      - android-automatic-build-experimental:
          filters:
            branches:
              only:
                - develop
          requires:
            - lint-testunit
      - android-google-play-production-experimental:
          requires:
            - android-automatic-build-experimental