require "shellwords"

default_platform(:ios)

APP_IDENTIFIER = "net.buzzert.XaibatsuControlPanel"
TEAM_ID = "DQQH5H6GBD"
ROOT_DIR = File.expand_path("..", __dir__)
PROJECT = File.join(ROOT_DIR, "XIONControlPanel.xcodeproj")
SCHEME = "XIONControlPanel"
INFO_PLIST = "XIONControlPanel/SupportingFiles/Info.plist"
ARCHIVE_PATH = File.join(ROOT_DIR, "build/XaibatsuControlPanel.xcarchive")
IPA_PATH = File.join(ROOT_DIR, "build/XaibatsuControlPanel.ipa")
PROFILE_NAME = "match AppStore #{APP_IDENTIFIER}"
CI_KEYCHAIN_NAME = "xaibatsu_control_panel_ci_keychain"
CI_KEYCHAIN_PASSWORD = "xaibatsu-control-panel-ci-keychain-password"
CI_KEYCHAIN_DB_PATH = File.expand_path("~/Library/Keychains/#{CI_KEYCHAIN_NAME}-db")

def present?(value)
  !value.to_s.strip.empty?
end

def ci?
  present?(ENV["CI"])
end

def read_plist_value(path, key)
  Fastlane::Actions.sh(
    "/usr/bin/plutil -extract #{key.shellescape} raw #{path.shellescape}",
    log: false
  ).strip
end

def app_store_connect_key
  app_store_connect_api_key(
    key_id: ENV.fetch("APP_STORE_CONNECT_KEY_ID"),
    issuer_id: ENV.fetch("APP_STORE_CONNECT_ISSUER_ID"),
    key_content: ENV.fetch("APP_STORE_CONNECT_KEY_CONTENT"),
    is_key_content_base64: true,
    duration: 1_200,
    in_house: false
  )
end

def release_metadata
  tag = ENV["RELEASE_TAG"] || ENV["GITHUB_REF_NAME"]
  match = tag&.match(%r{\Arelease/v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)\z})

  UI.user_error!("Expected a release tag in the form release/vX.X.X; got #{tag.inspect}") unless match

  build_number = ENV["GITEA_BUILD_NUMBER"] || ENV["GITHUB_RUN_NUMBER"]
  unless build_number&.match?(/\A[1-9]\d*\z/)
    UI.user_error!("The Gitea run number must be a positive integer; got #{build_number.inspect}")
  end

  {
    version: "#{match[1]}.#{match[2]}.#{match[3]}",
    build_number: build_number
  }
end

platform :ios do
  # Keep this app's unlocked signing identity first in the shared runner's
  # keychain search list. Other projects use the same distribution identity,
  # and their locked keychains can otherwise shadow this one.
  private_lane :prepare_ci_keychain do
    next unless ci?

    delete_keychain(name: CI_KEYCHAIN_NAME) if File.file?(CI_KEYCHAIN_DB_PATH)
    create_keychain(
      name: CI_KEYCHAIN_NAME,
      password: CI_KEYCHAIN_PASSWORD,
      unlock: true,
      timeout: 3_600,
      add_to_search_list: false
    )

    other_keychains = sh("security list-keychains -d user", log: false)
      .scan(/"([^"]+)"/)
      .flatten
      .reject { |path| path.include?(CI_KEYCHAIN_NAME) }
    sh("security list-keychains -d user -s #{([CI_KEYCHAIN_DB_PATH] + other_keychains).shelljoin}")

    ENV["MATCH_KEYCHAIN_NAME"] = CI_KEYCHAIN_NAME
    ENV["MATCH_KEYCHAIN_PASSWORD"] = CI_KEYCHAIN_PASSWORD
  end

  desc "Create or renew the App Store provisioning profile in the Match repository"
  lane :prepare_signing do
    match(
      type: "appstore",
      app_identifier: APP_IDENTIFIER,
      team_id: TEAM_ID,
      api_key: app_store_connect_key,
      readonly: false
    )
  end

  desc "Build a release tag and upload it to TestFlight"
  lane :release do |options|
    metadata = release_metadata
    api_key = app_store_connect_key

    prepare_ci_keychain

    match(
      type: "appstore",
      app_identifier: APP_IDENTIFIER,
      team_id: TEAM_ID,
      api_key: api_key,
      readonly: true
    )

    update_info_plist(
      plist_path: INFO_PLIST,
      block: proc do |plist|
        plist["CFBundleShortVersionString"] = metadata[:version]
        plist["CFBundleVersion"] = metadata[:build_number]
      end
    )

    stamped_version = get_info_plist_value(path: INFO_PLIST, key: "CFBundleShortVersionString").to_s
    stamped_build = get_info_plist_value(path: INFO_PLIST, key: "CFBundleVersion").to_s
    UI.user_error!("Failed to stamp the release version") unless stamped_version == metadata[:version]
    UI.user_error!("Failed to stamp the Gitea build number") unless stamped_build == metadata[:build_number]

    build_app(
      project: PROJECT,
      scheme: SCHEME,
      configuration: "Release",
      clean: true,
      archive_path: ARCHIVE_PATH,
      output_directory: File.dirname(IPA_PATH),
      output_name: File.basename(IPA_PATH),
      export_method: "app-store",
      export_options: {
        signingStyle: "manual",
        provisioningProfiles: {
          APP_IDENTIFIER => PROFILE_NAME
        },
        manageAppVersionAndBuildNumber: false
      }
    )

    archived_plist = File.join(
      ARCHIVE_PATH,
      "Products",
      "Applications",
      "XION.app",
      "Info.plist"
    )
    archived_version = read_plist_value(archived_plist, "CFBundleShortVersionString")
    archived_build = read_plist_value(archived_plist, "CFBundleVersion")
    UI.user_error!("Archived version does not match the release tag") unless archived_version == metadata[:version]
    UI.user_error!("Archived build does not match Gitea") unless archived_build == metadata[:build_number]

    if options[:skip_upload]
      UI.important("Skipping the TestFlight upload for local validation")
    else
      upload_to_testflight(
        api_key: api_key,
        app_identifier: APP_IDENTIFIER,
        ipa: IPA_PATH,
        skip_waiting_for_build_processing: true,
        uses_non_exempt_encryption: false
      )
    end
  ensure
    delete_keychain(name: CI_KEYCHAIN_NAME) if ci? && File.file?(CI_KEYCHAIN_DB_PATH)
  end
end
