# Customise this file, documentation can be found here:
# https://github.com/KrauseFx/fastlane/tree/master/docs

$:.unshift File.dirname(__FILE__)

default_platform :ios

configurations_to_test_on_pull = [
  {device: 'iPhone 14 Pro', os: '16.3'},
  {device: 'iPad Pro (12.9-inch) (5th generation)', os: '15.7.1'}
]

def get_devices_by_scheme_from_options(options)
  configs = options[:configurations] || [{device: 'iPhone 14 Pro', os: '16.3'}]
  devices_by_scheme = {}
  configs.each do |options|
    scheme = options[:scheme] || 'Wikipedia'
    device = options[:device] || 'iPhone 14 Pro'
    os = options[:os]         || '16.3'
    sim = "#{device} (#{os})"
    devices = devices_by_scheme[scheme] || []
    devices << sim
    devices_by_scheme[scheme] = devices
  end
  return devices_by_scheme
end


def get_version_number_from_options(options)
  scheme = options[:scheme] || 'Wikipedia'
  config = options[:configuration] || 'Release'
  return get_version_number(target: scheme, configuration: config)
end

def clean_out_reference_images
  UI.header "Step: Clear previous reference images"
  UI.command "FileUtils.rm_rf(Dir.glob('../WikipediaUnitTests/ReferenceImages_64/*'))"
  FileUtils.rm_rf(Dir.glob('../WikipediaUnitTests/ReferenceImages_64/*'))
end

platform :ios do
  before_all do
     # Set project for commit_version_bump, which seems to get confused by projects in other folders
     ENV['FL_BUILD_NUMBER_PROJECT'] = "Wikipedia.xcodeproj"
     ensure_git_status_clean if ENV['FL_ENSURE_CLEAN']
  end

  desc "Checks out the sha specified in the environment variables or the main branch"
  lane :checkout do
    sha = ENV['SHA']
    if sha != nil
      puts sha
      commit = last_git_commit
      sh "git checkout #{sha}"
    end
  end

  desc "Runs linting (and eventually static analysis)"
  lane :analyze do
    read_xcversion
    xcodebuild(
      project: "Wikipedia.xcodeproj",
      scheme: "Wikipedia",
      configuration: "Debug",
      sdk: 'iphonesimulator',
      destination: 'platform=iOS Simulator,OS=15.1,name=iPhone 13 Pro',
      analyze: true
    )
  end

  desc "Runs tests on select platforms for verifying pull requests"
  lane :verify_pull_request do |options|
    verify({configurations: configurations_to_test_on_pull})
  end

  desc "Reads Xcode version from the .xcversion file and sets it using xcversion()"
  lane :read_xcversion do |options|
    version = `cat ../.xcversion`
    if ENV['GITHUB_ACTIONS']
      # GitHub actions keeps beta versions around that confuse xcversion()
      # Use the absolute path to select the right version instead
      # Remove trailing 0's to match GitHub's naming conventions
      version_name = version.split('.').select { |n| n != '0' }.join('.')
      `sudo xcode-select -s /Applications/Xcode_#{version_name}.app`
    else
      xcversion(version: version)
    end
  end

  desc "Runs unit tests, generates reports."
  lane :verify do |options|
    slack_message = nil
    if ENV.key?('CIRCLECI')
      url = ENV['CIRCLE_BUILD_URL']
      number = ENV['CIRCLE_BUILD_NUM']
      job = ENV['CIRCLE_JOB']
      slack_message = "<#{url}|CircleCI #{job} ##{number}>"
    end
    devices_by_scheme = get_devices_by_scheme_from_options(options)
    devices_by_scheme.each do |scheme, devices|
      opts = {
        devices: devices,
        scheme: scheme,
        project: 'Wikipedia.xcodeproj',
        configuration: 'Test',
        disable_concurrent_testing: true,
        output_files: "#{scheme}.junit",
        output_types: 'junit',
        slack_message: slack_message
      }
      scan(opts)
    end
  end

  desc "Records visual tests."
  lane :record_visual_tests do |options|
    read_xcversion
    clean_out_reference_images
    options[:configurations] ||= configurations_to_test
    devices_by_scheme = get_devices_by_scheme_from_options(options)
    devices_by_scheme.each do |scheme, devices|
      opts = {
        devices: devices,
        scheme: scheme,
        disable_concurrent_testing: true,
        project: 'Wikipedia.xcodeproj',
        buildlog_path: './build',
        configuration: 'Test',
        xcargs: "GCC_PREPROCESSOR_DEFINITIONS='\$(value) WMF_VISUAL_TEST_RECORD_MODE=1'",
        fail_build: false,
        output_types: ''
      }
      scan(opts)
    end
  end

  desc "Set the build number"
  lane :set_build_number do |options|
    build = options[:build] || 0
    increment_build_number({
      build_number: build
    })
  end

  desc "Set version number"
  lane :set_version_number do |options|
    increment_version_number(
      version_number: options[:version_number]
    )
  end

  desc "Increment the app version patch"
  lane :bump_patch do
    increment_version_number(
      bump_type: "patch"
    )
  end

  desc "Increment the app version minor"
  lane :bump_minor do
    increment_version_number(
      bump_type: "minor"
    )
  end

  desc "Increment the app version major"
  lane :bump_major do
    increment_version_number(
      bump_type: "major"
    )
  end

  desc "Change version number and create PR with changes"
  lane :change_version do |options|

    ensure_git_status_clean(show_diff: true)

    size = options[:size]
    branch = "fastlane_version_bump"
    case size
    when "patch"
      bump_patch()
    when "minor"
      bump_minor()
    when "major"
      bump_major()
    end

    commit_version_bump(message: "Version bump")
    push_to_git_remote(
      remote: "origin",
      remote_branch: branch,
      force: true,
      set_upstream: true
    )
    create_pull_request(
      api_token: ENV['GITHUB_TOKEN'],
      repo: "wikimedia/wikipedia-ios",
      title: "Version bump",
      head: branch,
      base: "main",
      body: "Please merge this in."
    )
  end

  desc "Add a build tag for the current build number and push to repo. While this tags a build, tag_release sets a release tag."
  lane :tag do |options|
    prefix = options[:prefix] || "betas"
    build_number = options[:build_number] || options[:build] || get_build_number
    tag_name = "#{prefix}/#{build_number}"
    message = options[:message] || ""
    add_git_tag(tag: tag_name, message: message)
    sh "git push origin --tags"
  end

  desc "Add a release tag for the latest beta and push to repo. For tagging non-releases, use `tag`."
  lane :tag_release do |options|
    specified_build = options[:build_tag]
    if specified_build.nil? || specified_build.empty?
        latest_beta = get_latest_build_number()
        sh "git checkout betas/#{latest_beta}"
    else
        sh "git checkout #{specified_build}"
    end
    version = options[:version] || get_version_number_from_options(options)
    tag_name = "releases/#{version}"
    add_git_tag(tag: tag_name)
    sh "git push origin --tags && git checkout main"
    set_github_release(
        repository_name: "wikimedia/wikipedia-ios",
        api_token: ENV['GITHUB_TOKEN'],
        name: version,
        tag_name: tag_name,
        description: "release to iOS app store"
     )
  end

  desc "Build the app for distribution"
  lane :build do |options|
    project_dir = ".."
    build_dir = "../build"

    product_name = options[:product_name] || "Wikipedia"
    project_name = options[:project] || product_name
    scheme_name = options[:scheme] || product_name
    version = options[:version] || ''
    number = options[:number] || ''

    read_xcversion

    sh "xcodebuild -project \"#{project_dir}/#{project_name}.xcodeproj\" -scheme \"#{scheme_name}\" -archivePath \"#{build_dir}/#{product_name}.xcarchive\" archive"
    sh "xcodebuild -exportArchive -exportOptionsPlist ExportOptions.plist -archivePath \"#{build_dir}/#{product_name}.xcarchive\" -exportPath \"#{build_dir}\""
  end

  desc "Pushes both the production and staging app to TestFlight and tags the release. Only releases to internal testers. (This is very similar to `push_production`, although this command also tags the build in git.)"
  lane :deploy do |options|
    tag_prefix = "betas"
    last_build = get_latest_build_number(prefix: tag_prefix)
    build = options[:build] || last_build + 1
    last_public_release_tag = get_latest_tag_with_prefix(prefix: "releases/")[0..-2]
    last_public_release_number = last_public_release_tag.gsub("releases/", "")

    full_merges_since_last_build = get_recent_commits(start: "#{tag_prefix}/#{last_build}", is_brief: false)
    full_merges_since_last_release = get_recent_commits(start: last_public_release_tag, is_brief: false)
    full_changelog = "\n__New since last build (#{last_build})__\n#{full_merges_since_last_build.empty? ? 'None' : full_merges_since_last_build}\n\n" +
        "__New since last public release (#{last_public_release_number})__\n#{full_merges_since_last_release.empty? ? 'None' : full_merges_since_last_release}"

    brief_merges_since_last_build = get_recent_commits(start: "betas/#{last_build}", is_brief: true)
    brief_merges_since_last_release = get_recent_commits(start: last_public_release_tag, is_brief: true)
    brief_changelog = "__New since last build (#{last_build})__\n#{brief_merges_since_last_build.empty? ? 'None' : brief_merges_since_last_build}\n\n" +
            "__New since last public release (#{last_public_release_number})__\n#{brief_merges_since_last_release.empty? ? 'None' : brief_merges_since_last_release}"

    push_production(build: build, changelog: brief_changelog)
    tag(prefix: tag_prefix, build_number: build, message: full_changelog)
    push_staging(build: build)
  end

  desc "Updates version, builds, and pushes the production build to TestFlight. Only releases to internal testers."
  lane :push_production do |options|
    push(
      product_name: "Wikipedia",
      app_identifier: "org.wikimedia.wikipedia",
      build: options[:build],
      changelog: options[:changelog]
    )
  end

  desc "Updates version, builds, and pushes the staging build to TestFlight. Only releases to internal testers."
  lane :push_staging do |options|
    push(
      product_name: "Staging",
      app_identifier: "org.wikimedia.wikipedia.tfbeta",
      build: options[:build]
    )
  end

  desc "Updates version, builds, and pushes experimental build to TestFlight. Only releases to internal testers."
  lane :push_experimental do |options|
    tag_prefix = "alphas"
    build = options[:build] || get_latest_build_number(prefix: tag_prefix) + 1
    push(
      product_name: "Experimental",
      app_identifier: "org.wikimedia.wikipedia.tfalpha",
      build: build
    )
    tag(prefix: tag_prefix, build_number: build)
  end

  lane :get_latest_tag_with_prefix do |options|
    prefix = options[:prefix] || "betas/"
    `git tag -l #{prefix}* --sort=-creatordate | head -n 1`
  end

  lane :get_latest_build_number do |options|
    prefix = options[:prefix] || "betas"
    prefix = "#{prefix}/"
    get_latest_tag_with_prefix(prefix: prefix)[prefix.length..-1].to_i
  end

  lane :get_recent_commits do |options|
    start = options[:start] || "HEAD"
    is_brief = options[:is_brief] || false
    format = is_brief ? '• %b (%s)' : '• %h - %b (%s)'
    small_sed = "s/(Merge pull.*\\(#[0-9]*\\).*)/(\\1)/"
    sed_removal = is_brief ? small_sed : "s/Merge pull request //g"

    if is_brief
      list_without_localizations = `git log #{start}..HEAD --merges --first-parent origin/main --grep="Merge branch 'main' into" --grep="Localisation updates from" --invert-grep --pretty=format:"#{format}" | sed '#{sed_removal}'`
      localization_numbers = `git log #{start}..HEAD --merges --first-parent origin/main --grep="Localisation updates from" --pretty=format:"%s" | sed "s/Merge pull.*\\(#[0-9]*\\).*/\\1/" | xargs`
      localizations_list = "• Localisation updates from https://translatewiki.net. Pull requests: " + localization_numbers
      localization_numbers.empty? ? list_without_localizations : "#{list_without_localizations}\n#{localizations_list}"
    else
      `git log #{start}..HEAD --merges --first-parent origin/main --grep="Merge branch 'main' into" --invert-grep --pretty=format:"#{format}" | sed '#{sed_removal}'`
    end
  end

  desc "updates version, builds, and pushes to TestFlight"
  lane :push do |options|
    build_dir = "build"
    product_name = options[:product_name] || "Wikipedia"
    app_identifier = options[:app_identifier] || "org.wikimedia.wikipedia"
    ipa_path = "#{build_dir}/#{product_name}.ipa"
    build_number =  options[:build] || get_latest_build_number(prefix: "betas") + 1
    version = get_version_number_from_options(options)
    apple_key = `cat ~/AuthKey.p8`
    issuer_id = `cat ~/issuerID.txt`
    key_id = `cat ~/keyID.txt`
    api_key = app_store_connect_api_key(
      key_id: key_id,
      issuer_id: issuer_id,
      key_content: apple_key
    )

    increment_build_number(
      build_number: build_number
    )

    # the changelog was overwriting whatever custom test notes we added
    # changelog_from_git_commits(
    #   pretty: '- (%ae) %s', # Optional, lets you provide a custom format to apply to each commit when generating the changelog text
    #   tag_match_pattern: "#{tag_prefix}/*", # Optional, lets you search for a tag name that matches a glob(7) pattern
    #   include_merges: false # Optional, lets you filter out merge commits
    # )

    build(
      product_name: product_name,
      project: "Wikipedia",
      number: build_number,
      version: version
    )

    pilot(
      api_key: api_key,
      ipa: ipa_path,
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false,
      app_identifier:  app_identifier,
      beta_app_feedback_email: "mobile-ios-wikipedia@wikimedia.org",
      changelog: options[:changelog]
    )
  end

  desc "Upload app store metadata"
  lane :upload_app_store_metadata do
    deliver(skip_binary_upload: true, skip_screenshots: true)
  end

  desc "Download dSYMs from iTunes Connect"
  lane :dsyms do |options|
    app_identifier = options[:app_identifier]
    version = options[:version]
    build_number = options[:build_number]  || latest_testflight_build_number(app_identifier: app_identifier)
    ipa_path = options[:ipa_path]
    output_directory = options[:output_directory] || "build"

    raise "Missing parameters" unless app_identifier && version && build_number && ipa_path && output_directory

    download_dsyms(
      build_number: build_number.to_s,
      version: version.to_s,
      app_identifier: app_identifier,
      output_directory: output_directory
    ) # Download dSYM files from iTC
  end

  lane :dsyms_alpha do |options|
    app_identifier = "org.wikimedia.wikipedia.tfalpha"
    ipa_path = "build/Wikipedia Alpha.ipa"
    version = options[:version] || get_version_number_from_options(options)
    dsyms(
      app_identifier: app_identifier,
      version: version,
      build_number: options[:build_number],
      ipa_path: ipa_path
    )
  end

  lane :dsyms_beta do |options|
    app_identifier = "org.wikimedia.wikipedia"
    ipa_path = "build/Wikipedia.ipa"
    version = options[:version] || get_version_number_from_options(options)
    dsyms(
      app_identifier: app_identifier,
      version: version,
      build_number: options[:build_number] || options[:build],
      ipa_path: ipa_path
    )
  end

  lane :dsyms_beta_app do |options|
    app_identifier = "org.wikimedia.wikipedia.tfbeta"
    ipa_path = "build/Wikipedia.ipa"
    version = options[:version] || get_version_number_from_options(options)
    dsyms(
      app_identifier: app_identifier,
      version: version,
      build_number: options[:build_number] || options[:build],
      ipa_path: ipa_path
    )
  end

end
