# Roomfit Studio (B2B) — iOS Fastlane
#
# This lane file is the ONLY supported way to produce a release archive.
# Manual `flutter build ipa` / Xcode Archive runs have repeatedly shipped
# builds without `--dart-define=SUPABASE_ANON_KEY`, which Apple rejected
# under Guideline 2.1(a) on 2026-04-21. Every lane below validates the
# environment before invoking Flutter, so a misconfigured build cannot
# reach App Store Connect.

default_platform(:ios)

# ── Constants ────────────────────────────────────────────────────────
APP_IDENTIFIER = "com.roomfit.roomfitStudio"
TEAM_ID = "BMGD68PVC4"

API_KEY_ID = ENV["APP_STORE_CONNECT_API_KEY_ID"] || "SZSW99PX4R"
API_ISSUER_ID =
  ENV["APP_STORE_CONNECT_API_ISSUER_ID"] ||
  "78d91dfe-beb5-43bd-a621-24ddd158b605"
API_KEY_PATH =
  ENV["APP_STORE_CONNECT_API_KEY_PATH"] ||
  File.expand_path("~/.appstoreconnect/private_keys/AuthKey_#{API_KEY_ID}.p8")

APP_ROOT = File.expand_path("../..", __dir__)      # apps/b2b
PUBSPEC_PATH = File.expand_path("../../pubspec.yaml", __dir__)
DOTENV_PATH = File.expand_path("../../.env", __dir__)

# Dart-define keys that a production build MUST have. Missing any one
# of these aborts the lane before a single Flutter command runs.
REQUIRED_DART_DEFINES = %w[SUPABASE_ANON_KEY].freeze

# ── Helpers ──────────────────────────────────────────────────────────
def load_dotenv
  return unless File.exist?(DOTENV_PATH)
  File.foreach(DOTENV_PATH) do |line|
    next if line.strip.empty? || line.strip.start_with?("#")
    k, v = line.split("=", 2)
    ENV[k.strip] ||= v.to_s.strip.sub(/\A"(.*)"\z/, '\1')
  end
end

def assert_required_env!
  missing = REQUIRED_DART_DEFINES.select { |k| ENV[k].to_s.strip.empty? }
  return if missing.empty?
  UI.user_error!(
    "Missing required env vars: #{missing.join(", ")}. " \
    "Define them in `apps/b2b/.env` or export before running fastlane. " \
    "See apps/b2b/.env.example.",
  )
end

def read_pubspec_version
  raw = File.read(PUBSPEC_PATH, encoding: "UTF-8")
  match = raw.match(/^version:\s*(\d+\.\d+\.\d+)(?:\+(\d+))?/m)
  UI.user_error!("pubspec.yaml version 파싱 실패") unless match
  [match[1], match[2] || "0"]
end

def write_pubspec_version(version_name, build_number)
  raw = File.read(PUBSPEC_PATH, encoding: "UTF-8")
  updated = raw.sub(
    /^version:\s*\d+\.\d+\.\d+(?:\+\d+)?.*$/,
    "version: #{version_name}+#{build_number}",
  )
  File.write(PUBSPEC_PATH, updated, encoding: "UTF-8")
end

def load_api_key
  app_store_connect_api_key(
    key_id: API_KEY_ID,
    issuer_id: API_ISSUER_ID,
    key_filepath: API_KEY_PATH,
  )
end

def dart_define_args
  REQUIRED_DART_DEFINES.map { |k| "--dart-define=#{k}=#{ENV[k]}" }.join(" ")
end

# ── Lanes ────────────────────────────────────────────────────────────
platform :ios do
  before_all do
    load_dotenv
  end

  desc "Validate that env + provisioning are ready for a release build"
  lane :preflight do
    assert_required_env!
    UI.success("✅ env OK (#{REQUIRED_DART_DEFINES.join(", ")} present)")
    UI.message("API key: #{API_KEY_PATH}")
    UI.user_error!("ASC API key not found at #{API_KEY_PATH}") unless File.exist?(API_KEY_PATH)
    UI.success("✅ ASC API key present")

    plist = File.read(File.expand_path("../Runner/Info.plist", __dir__))
    required_plist_keys = %w[
      NSBluetoothAlwaysUsageDescription
      NSBluetoothPeripheralUsageDescription
      ITSAppUsesNonExemptEncryption
    ]
    missing = required_plist_keys.reject { |k| plist.include?(k) }
    UI.user_error!("Info.plist missing keys: #{missing.join(", ")}") unless missing.empty?
    UI.success("✅ Info.plist required keys present")
  end

  desc "Push a new beta build to TestFlight"
  lane :beta do
    assert_required_env!
    api_key = load_api_key

    current_version, current_build = read_pubspec_version
    UI.message("Current pubspec.yaml: #{current_version}+#{current_build}")

    version_name = ENV["VERSION_NAME"].to_s.empty? ? current_version : ENV["VERSION_NAME"]
    build_number =
      if !ENV["BUILD_NUMBER"].to_s.empty?
        ENV["BUILD_NUMBER"].to_i
      else
        latest = latest_testflight_build_number(
          api_key: api_key,
          app_identifier: APP_IDENTIFIER,
          initial_build_number: 0,
        )
        latest.to_i + 1
      end

    UI.important("🚀 Beta #{version_name}+#{build_number} (prev: #{current_version}+#{current_build})")

    write_pubspec_version(version_name, build_number)

    sh(
      "cd #{APP_ROOT} && flutter build ios --config-only --release " \
      "--build-name=#{version_name} --build-number=#{build_number} " \
      "#{dart_define_args}",
    )

    get_provisioning_profile(
      api_key: api_key,
      app_identifier: APP_IDENTIFIER,
      team_id: TEAM_ID,
      force: true,
    )

    build_app(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store",
      # DART_DEFINES are injected into Generated.xcconfig by the
      # `flutter build ios --config-only --dart-define=...` step above
      # in a base64-encoded form. Passing them again here as plain xcargs
      # (e.g. `DART_DEFINES=SUPABASE_ANON_KEY=...`) would overwrite that
      # encoded value and Flutter's xcode_backend.sh would fail with
      # "Error parsing assemble command: your generated configuration
      # may be out of date." Do not re-inject here.
      export_options: {
        signingStyle: "manual",
        teamID: TEAM_ID,
        provisioningProfiles: {
          APP_IDENTIFIER => ENV["sigh_#{APP_IDENTIFIER}_appstore_profile-name"] || "",
        },
      },
    )

    upload_to_testflight(
      api_key: api_key,
      skip_waiting_for_build_processing: true,
    )

    UI.success("✅ Uploaded #{version_name}+#{build_number} to TestFlight")
  end

  desc "Build and upload to App Store (submit manually in ASC)"
  lane :release do
    assert_required_env!
    api_key = load_api_key

    current_version, current_build = read_pubspec_version
    version_name = ENV["VERSION_NAME"].to_s.empty? ? current_version : ENV["VERSION_NAME"]
    build_number =
      if !ENV["BUILD_NUMBER"].to_s.empty?
        ENV["BUILD_NUMBER"].to_i
      else
        latest = latest_testflight_build_number(
          api_key: api_key,
          app_identifier: APP_IDENTIFIER,
          initial_build_number: 0,
        )
        latest.to_i + 1
      end

    UI.important("🚀 Release #{version_name}+#{build_number}")

    write_pubspec_version(version_name, build_number)

    sh(
      "cd #{APP_ROOT} && flutter build ios --config-only --release " \
      "--build-name=#{version_name} --build-number=#{build_number} " \
      "#{dart_define_args}",
    )

    get_provisioning_profile(
      api_key: api_key,
      app_identifier: APP_IDENTIFIER,
      team_id: TEAM_ID,
      force: true,
    )

    build_app(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store",
      # DART_DEFINES are injected into Generated.xcconfig by the
      # `flutter build ios --config-only --dart-define=...` step above
      # in a base64-encoded form. Passing them again here as plain xcargs
      # (e.g. `DART_DEFINES=SUPABASE_ANON_KEY=...`) would overwrite that
      # encoded value and Flutter's xcode_backend.sh would fail with
      # "Error parsing assemble command: your generated configuration
      # may be out of date." Do not re-inject here.
      export_options: {
        signingStyle: "manual",
        teamID: TEAM_ID,
      },
    )

    deliver(
      api_key: api_key,
      metadata_path: "../fastlane/metadata/ios",
      screenshots_path: "../fastlane/screenshots/ios",
      skip_screenshots: false,
      submit_for_review: false,
      automatic_release: false,
      force: true,
      precheck_include_in_app_purchases: false,
    )

    UI.success("✅ Uploaded #{version_name}+#{build_number}. Submit for review in App Store Connect.")
  end

  desc "Upload metadata + review info only (no binary). Fix 2.1(a) rejections fast."
  lane :metadata do
    api_key = load_api_key
    deliver(
      api_key: api_key,
      metadata_path: "../fastlane/metadata/ios",
      screenshots_path: "../fastlane/screenshots/ios",
      skip_binary_upload: true,
      skip_screenshots: false,
      skip_metadata: false,
      force: true,
      precheck_include_in_app_purchases: false,
      submit_for_review: false,
    )
  end
end
