require 'fileutils'
require 'digest'
require 'open3'
require 'shellwords'

opt_out_usage

ROOT = File.expand_path('..', __dir__)
FLAVORS = {
  'staging' => {
    artifact_prefix: 'whitenoise-staging',
    android_package_name: 'org.parres.whitenoise.staging',
    ios_app_identifier: 'dev.ipf.whitenoise.staging',
  },
  'production' => {
    artifact_prefix: 'whitenoise',
    android_package_name: 'org.parres.whitenoise',
    ios_app_identifier: 'org.parres.whitenoise',
  },
}.freeze

def run_from_root(parts)
  env_parts = [
    'env',
    '-u', 'BUNDLE_BIN_PATH',
    '-u', 'BUNDLE_GEMFILE',
    '-u', 'BUNDLE_PATH',
    '-u', 'BUNDLER_VERSION',
    '-u', 'BUNDLER_SETUP',
    '-u', 'RUBYOPT',
  ]
  sh("cd #{Shellwords.escape(ROOT)} && #{(env_parts + parts).shelljoin}")
end

def flavor_config(flavor)
  config = FLAVORS[flavor.to_s]
  UI.user_error!("Unknown release flavor: #{flavor}") unless config
  config
end

def release_tag(options = {})
  tag = options[:tag].to_s.strip
  return tag unless tag.empty?

  tag = ENV.fetch('RELEASE_TAG', '').to_s.strip
  return tag unless tag.empty?

  return ENV.fetch('GITHUB_REF_NAME', '').to_s.strip if ENV['GITHUB_REF_TYPE'] == 'tag'

  ''
end

def release_info(options = {})
  tag = release_tag(options)

  command = ['./scripts/validate_release_version.sh']
  command += ['--tag', tag] unless tag.empty?

  output, error, status = Open3.capture3(*command, chdir: ROOT)
  UI.user_error!(error.strip.empty? ? output.strip : error.strip) unless status.success?

  info = {}
  output.lines.each do |line|
    key, value = line.strip.split('=', 2)
    info[key] = value if key && value
  end

  UI.user_error!('Release version could not be resolved') unless info['full_version']
  UI.user_error!('Release build number could not be resolved') unless info['build_number']

  info
end

def flutter_build_args(info)
  ['--build-name', info['version_name'], '--build-number', info['build_number']]
end

def release_output_dir(info, flavor, *parts)
  File.join(ROOT, 'build', 'releases', "v#{info['full_version']}", flavor.to_s, *parts)
end

def stage_artifact(source, destination_dir, destination_name, checksum: false)
  UI.user_error!("Missing artifact: #{source}") unless File.file?(source)

  FileUtils.mkdir_p(destination_dir)
  destination = File.join(destination_dir, destination_name)
  FileUtils.cp(source, destination)
  UI.message("Staged #{destination}")

  if checksum
    digest = Digest::SHA256.file(destination).hexdigest
    checksum_path = "#{destination}.sha256"
    File.write(checksum_path, "#{digest}  #{destination_name}\n")
    UI.message("Staged #{checksum_path}")
  end

  destination
end

def latest_ipa
  Dir[File.join(ROOT, 'build', 'ios', 'ipa', '*.ipa')].max_by { |path| File.mtime(path) }
end

def android_aab(flavor)
  candidates = Dir[File.join(ROOT, 'build', 'app', 'outputs', 'bundle', '**', "*#{flavor}*.aab")]
  candidates.max_by { |path| File.mtime(path) }
end

def require_macos!
  UI.user_error!('iOS release builds require macOS') unless RUBY_PLATFORM.include?('darwin')
end

def build_android_flavor(flavor, options, build_native: true)
  info = release_info(options)
  config = flavor_config(flavor)
  args = flutter_build_args(info)

  run_from_root(['./scripts/build_android.sh']) if build_native
  run_from_root(['flutter', 'build', 'apk', '--flavor', flavor, '--split-per-abi'] + args)
  run_from_root(['flutter', 'build', 'appbundle', '--flavor', flavor] + args)

  apk_dir = File.join(ROOT, 'build', 'app', 'outputs', 'flutter-apk')
  output_dir = release_output_dir(info, flavor, 'android')

  %w[arm64-v8a armeabi-v7a x86_64].each do |abi|
    source = File.join(apk_dir, "app-#{abi}-#{flavor}-release.apk")
    unless File.file?(source)
      next UI.important("Skipping missing optional APK: #{source}") if abi == 'armeabi-v7a'

      UI.user_error!("Required APK was not found: #{source}")
    end

    stage_artifact(
      source,
      output_dir,
      "#{config[:artifact_prefix]}-#{info['version_name']}-#{abi}.apk",
      checksum: true,
    )
  end

  source = android_aab(flavor)
  UI.user_error!("#{flavor.capitalize} AAB was not found") unless source

  stage_artifact(
    source,
    output_dir,
    "#{config[:artifact_prefix]}-#{info['version_name']}.aab",
    checksum: true,
  )
end

def build_ios_flavor(flavor, options, build_native: true)
  require_macos!
  info = release_info(options)
  config = flavor_config(flavor)
  export_options = File.join(ROOT, 'ios', "ExportOptions.#{flavor}-ad-hoc.plist")

  run_from_root(['./scripts/build_ios.sh']) if build_native
  build_args = ['flutter', 'build', 'ipa', '--flavor', flavor]
  build_args += if File.file?(export_options)
                  ['--export-options-plist', export_options]
                else
                  ['--export-method', 'app-store']
                end
  run_from_root(build_args + flutter_build_args(info))

  source = latest_ipa
  UI.user_error!("#{flavor.capitalize} IPA was not found") unless source

  stage_artifact(
    source,
    release_output_dir(info, flavor, 'ios'),
    "#{config[:artifact_prefix]}-#{info['full_version']}.ipa",
  )
end

def build_channel_release(flavor, options)
  build_android_flavor(flavor, options)

  if RUBY_PLATFORM.include?('darwin')
    build_ios_flavor(flavor, options)
  else
    UI.important("Skipping #{flavor} iOS artifacts because this machine is not macOS")
  end
end

desc 'Validate release version metadata'
lane :release_doctor do |options|
  info = release_info(options)
  UI.success("Release #{info['full_version']} is ready to build")
end

desc 'Build staging Android APKs and AAB'
lane :build_android_staging do |options|
  build_android_flavor('staging', options)
end

desc 'Build production Android APKs and AAB'
lane :build_android_production do |options|
  build_android_flavor('production', options)
end

desc 'Build staging iOS IPA for App Store Connect/TestFlight'
lane :build_ios_staging do |options|
  build_ios_flavor('staging', options)
end

desc 'Build production iOS IPA for App Store Connect/TestFlight'
lane :build_ios_production do |options|
  build_ios_flavor('production', options)
end

desc 'Build staging app release artifacts'
lane :build_staging_release do |options|
  build_channel_release('staging', options)
end

desc 'Build production app release artifacts'
lane :build_production_release do |options|
  build_channel_release('production', options)
end

desc 'Build staging and production release artifacts'
lane :build_all_release_artifacts do |options|
  build_android_flavor('staging', options)
  build_android_flavor('production', options, build_native: false)

  if RUBY_PLATFORM.include?('darwin')
    build_ios_flavor('staging', options)
    build_ios_flavor('production', options, build_native: false)
  else
    UI.important('Skipping iOS artifacts because this machine is not macOS')
  end
end
