# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#
# Fastlane is updated quite frequently with security patches
# update_fastlane

# There are a few env variables defined in the .env file in
# this directory (fastlane/.env)

def curl_upload(url, file, auth, conn_timeout = 5, timeout = 60, retries = 3)
  rval = sh(
    'curl',
    '--silent',
    '--user', auth,
    '--write-out', "\nHTTP_CODE:%{http_code}",
    '--request', 'POST',
    '--header', 'Content-Type: application/octet-stream',
    # we retry few times if upload doesn't succeed in sensible time
    '--retry-connrefused',                  # consider ECONNREFUSED as error too retry
    '--data-binary', "@../#{file}",         # `fastlane` is the cwd so we go one folder up
    '--connect-timeout', conn_timeout.to_s, # max time in sec. for establishing connection
    '--max-time', timeout.to_s,             # max time in sec. for whole transfer to take
    '--retry', retries.to_s,                # number of retries to attempt
    '--retry-max-time', timeout.to_s,       # same as --max-time but for retries
    '--retry-delay', '0',                   # an exponential backoff algorithm in sec.
    url
  )
  # We're not using --fail because it suppresses server response
  raise "Error:\n#{rval}" unless rval.include? 'HTTP_CODE:200'

  rval
end

def retry_curl_upload(url, file, auth, conn_timeout = 5, timeout = 60, retries = 3)
  # since curl doesn't retry on connection and operation timeouts we roll our own
  (1..retries).each do |try|
    begin
      return curl_upload(url, file, auth, conn_timeout, timeout, retries)
    rescue StandardError => e
      if try == retries
        UI.error "Error:\n#{e}"
        raise
      end
      UI.important "Warning: Retrying cURL upload! (attempt #{try}/#{retries})"
    end
  end
end

# Creates and unlocks a keychain into which Fastlane match imports signing keys and certs.
class Keychain
  attr_accessor :name, :pass

  def initialize(name)
    # Local devs will not have KEYCHAIN_PASSWORD set, and will be prompted for password.
    return "login.keychain-db" unless ENV['KEYCHAIN_PASSWORD']
    # We user the same keychain every time because we need to set a default.
    @name = "#{name}.keychain-db"
    @pass = ENV['KEYCHAIN_PASSWORD']
    Fastlane::Actions::CreateKeychainAction.run(
      name: @name,
      password: @pass,
      unlock: true,
      # Fastlane can't find the signing cert without setting a default.
      default_keychain: true,
      # Deleting the keychain would cause race condition for parallel jobs.
      require_create: false,
      # Lock it up after 25 minutes just in case we don't delete it.
      lock_when_sleeps: true,
      lock_after_timeout: true,
      timeout: 1500
    )
  end
end

# App Store Connect API is an official public API used to manage Apps.
# This includes metadata, pricing and availability, provisioning, and more.
# It provides a JSON API and auth using API Keys to generate a JSON Web Token (JWT).
def asc_api_key()
  app_store_connect_api_key(
    key_id: ENV['FASTLANE_ASC_API_KEY_ID'],
    issuer_id: ENV['FASTLANE_ASC_API_ISSUER_ID'],
    key_filepath: ENV['FASTLANE_ASC_API_KEY_FILE_PATH'],
    duration: 1200, # seconds, session length
    in_house: false,
  )
end

# builds an ios app with ad-hoc configuration and put it
# to "status-ios" output folder
# `readonly`:
#    if true  - only fetch existing certificates and profiles, don't upgrade from AppStoreConnect
#    if false - read list of devices from AppStoreConnect, and upgrade the provisioning profiles from it
# `pr_build`:
#    if true  - uses StatusImPR scheme and postfixed app id with `.pr` to build an app, which can be used in parallel with release
#    if false - uses StatusIm scheme to build the release app 

def build_ios_adhoc(readonly: false, pr_build: false)
  # PR builds should appear as a separate App on iOS
  scheme = pr_build ? 'StatusImPR' : 'StatusIm'
  app_id = pr_build ? 'im.status.ethereum.pr' : 'im.status.ethereum'

  kc = Keychain.new('fastlane')

  match(
    type: 'adhoc',
    readonly: readonly,
    api_key: asc_api_key(),
    app_identifier: app_id,
    force_for_new_devices: true,
    keychain_name: kc.name,
    keychain_password: kc.pass
  )

  build_ios_app(
    scheme: scheme,
    workspace: 'ios/StatusIm.xcworkspace',
    configuration: 'Release',
    clean: true,
    export_method: 'ad-hoc',
    output_name: 'StatusIm',
    output_directory: 'status-ios',
    buildlog_path: 'ios/logs',
    export_options: {
      signingStyle: 'manual',
      provisioningProfiles: {
        "im.status.ethereum": "match AdHoc im.status.ethereum",
        "im.status.ethereum.pr": "match AdHoc im.status.ethereum.pr"
      }
    }
  )
end

# builds an ios app with e2e configuration and put it
# to "status-ios" output folder
def build_ios_e2e
  # determine a simulator SDK installed
  showsdks_output = sh('xcodebuild', '-showsdks')
  simulator_sdk = showsdks_output.scan(/iphonesimulator\d\d?\.\d\d?/).first

  kc = Keychain.new('fastlane')

  match(
    type: 'adhoc',
    readonly: true,
    api_key: asc_api_key(),
    force_for_new_devices: true,
    keychain_name: kc.name,
    keychain_password: kc.pass
  )

  build_ios_app(
    # Creating a build for the iOS Simulator
    # 1. https://medium.com/rocket-fuel/fastlane-to-the-simulator-87549b2601b9
    sdk: simulator_sdk,
    destination: 'generic/platform=iOS Simulator',
    # 2. fixing compilations issues as stated in https://stackoverflow.com/a/20505258
    #    it looks like i386 isn't supported by React Native
    xcargs: 'ARCHS="x86_64" ONLY_ACTIVE_ARCH=NO',
    # 3. directory where to up StatusIm.app
    derived_data_path: 'status-ios',
    output_name: 'StatusIm.app',
    buildlog_path: 'ios/logs',
    # -------------------------------------
    # Normal stuff
    scheme: 'StatusIm',
    workspace: 'ios/StatusIm.xcworkspace',
    configuration: 'Release',
    # Simulator apps can't be archived...
    skip_archive: true,
    # ...and we don't need an .ipa file for them, because we use .app directly
    skip_package_ipa: true
  )

  zip(
    path: 'status-ios/Build/Products/Release-iphonesimulator/StatusIm.app',
    output_path: 'status-ios/StatusIm.app.zip',
    verbose: false
  )
end

def upload_to_diawi(source)
  diawi(
    file: source,
    timeout: 120,
    check_status_delay: 5,
    token: ENV['DIAWI_TOKEN']
  )
  # save the URL to a file for use in CI
  File.write('diawi.out', lane_context[SharedValues::UPLOADED_FILE_LINK_TO_DIAWI])
end

platform :ios do
  desc '`fastlane ios adhoc` - ad-hoc lane for iOS.'
  desc 'This lane is used for PRs, Releases, etc.'
  desc 'It creates an .ipa that can be used by a list of devices, registeded in the App Store Connect.'
  desc 'This .ipa is ready to be distibuted through diawi.com'
  lane :adhoc do
    build_ios_adhoc(readonly: true)
  end

  desc '`fastlane ios e2e` - e2e lane for iOS.'
  desc 'This lane is used for SauceLabs end-to-end testing.'
  desc 'It creates an .app that can be used inside of a iPhone simulator.'
  lane :e2e do
    build_ios_e2e
  end

  desc '`fastlane ios pr` - makes a new pr build'
  desc 'This lane builds a new adhoc build and leaves an .ipa that is ad-hoc signed (can be uploaded to diawi)'
  lane :pr do
    build_ios_adhoc(pr_build: true)
  end

  desc '`fastlane ios nightly` - makes a new nightly'
  desc 'This lane builds a new nightly and leaves an .ipa that is ad-hoc signed (can be uploaded to diawi)'
  lane :nightly do
    build_ios_adhoc()
  end

  desc '`fastlane ios release` builds a release & uploads it to TestFlight'
  lane :release do
    kc = Keychain.new('fastlane')

    match(
      type: 'appstore',
      readonly: true,
      api_key: asc_api_key(),
      app_identifier: 'im.status.ethereum',
      keychain_name: kc.name,
      keychain_password: kc.pass
    )

    build_ios_app(
      scheme: 'StatusIm',
      workspace: 'ios/StatusIm.xcworkspace',
      configuration: 'Release',
      clean: true,
      export_method: 'app-store',
      output_directory: 'status-ios',
      buildlog_path: 'ios/logs',
      include_symbols: false,
      export_options: {
        "combileBitcode": true,
        "uploadBitcode": false,
        "ITSAppUsesNonExemptEncryption": false
      }
    )

    upload_to_testflight(
      ipa: 'status-ios/StatusIm.ipa',
      skip_waiting_for_build_processing: true
    )
  end

  desc '`fastlane ios clean` - remove inactive TestFlight users'
  lane :clean do
    clean_testflight_testers(
      username: ENV['FASTLANE_APPLE_ID'],
      days_of_inactivity: 30
    )
    # In the future we can try using 'oldest_build_allowed'
  end

  desc '`fastlane ios upload-diawi` - upload .ipa to diawi'
  desc 'expects to have an .ipa prepared: `status-ios/StatusIm.ipa`'
  desc 'expects to have a diawi token as DIAWI_TOKEN env variable'
  desc 'expects to have a github token as GITHUB_TOKEN env variable'
  desc "will fails if file isn't there"
  desc '---'
  desc 'Output: writes `fastlane/diawi.out` file url of the uploded file'
  lane :upload_diawi do
    ipa = ENV['DIAWI_IPA'] || 'status-ios/StatusIm.ipa'
    upload_to_diawi(ipa)
  end

  desc '`fastlane ios saucelabs` - upload .app to sauce labs'
  desc 'also notifies in a GitHub comments'
  desc 'expects to have an .apk prepared: `result/app.apk`'
  desc 'expects to have a saucelabs access key as SAUCE_ACCESS_KEY env variable'
  desc 'expects to have a saucelabs username token as SAUCE_USERNAME env variable'
  desc 'expects to have a saucelabs destination name as SAUCE_LABS_NAME env variable'
  desc "will fails if file isn't there"
  lane :saucelabs do
    upload_to_saucelabs(
      'status-ios/StatusIm.app.zip'
    )
  end

  desc 'This fastlane step cleans up XCode DerivedData folder'
  lane :cleanup do
    clear_derived_data
  end
end

platform :android do
  # Optional env variables
  APK_PATHS = ENV["APK_PATHS"]&.split(";") or ["result/app.apk"]

  desc 'Deploy a new internal build to Google Play'
  desc 'expects GOOGLE_PLAY_JSON_KEY environment variable'
  lane :nightly do
    upload_to_play_store(
      track: 'internal',
      apk_paths: APK_PATHS,
      json_key_data: ENV['GOOGLE_PLAY_JSON_KEY']
    )
  end

  desc 'Deploy a new alpha (public) build to Google Play'
  desc 'expects GOOGLE_PLAY_JSON_KEY environment variable'
  lane :release do
    upload_to_play_store(
      track: 'alpha',
      apk_paths: APK_PATHS,
      json_key_data: ENV['GOOGLE_PLAY_JSON_KEY']
    )
  end

  desc 'Upload metadata to Google Play.'
  desc 'Metadata is always updated when builds are uploaded,'
  desc 'but this action can update metadata without uploading a build.'
  desc 'expects GOOGLE_PLAY_JSON_KEY environment variable'
  lane :upload_metadata do
    upload_to_play_store(
      skip_upload_apk: true,
      skip_upload_changelogs: true,
      json_key_data: ENV['GOOGLE_PLAY_JSON_KEY'],
      # These don't matter much as we're not uploading any new builds
      # and indeed, we're skipping changelogs. This is just so that
      # the library can find what it thinks it needs and continue with
      # the work we actually want it to do.
      track: 'production',
      version_code: '2020042307'
    )
  end
end