# XuqmGroup Update Service — iOS Fastlane Release Lane
#
# Requires: fastlane (gem install fastlane)  — standard iOS platform toolchain
# Usage (from your iOS project root):
#   fastlane xuqm_release
#
# Config: add a .env.xuqm file to your project root with:
#   XUQM_SERVER_URL=https://update.dev.xuqinmin.com
#   XUQM_APP_KEY=your-app-key
#   XUQM_API_TOKEN=your-api-token
#   XUQM_SCHEME=MyApp
#   XUQM_WORKSPACE=MyApp.xcworkspace
#   XUQM_BUNDLE_ID=com.example.myapp
#   XUQM_STORE_TARGETS=APP_STORE         # comma-separated, e.g. "APP_STORE"
#   XUQM_AUTO_PUBLISH_AFTER_REVIEW=false
#   XUQM_PUBLISH_IMMEDIATELY=false
#   XUQM_WEBHOOK_URL=                    # optional
#   XUQM_SCHEDULED_PUBLISH_AT=           # optional, ISO datetime
#   # For App Store Connect API (used by upload_to_app_store):
#   APP_STORE_CONNECT_API_KEY_ID=XXXXXXXXXX
#   APP_STORE_CONNECT_API_ISSUER_ID=xxxx-xxxx-xxxx-xxxx
#   APP_STORE_CONNECT_API_KEY_FILEPATH=~/.appstoreconnect/AuthKey_XXXXXXXXXX.p8

require 'net/http'
require 'json'
require 'uri'

default_platform(:ios)

platform :ios do

  desc "Build, upload to XuqmGroup update service, and submit to App Store"
  lane :xuqm_release do
    server_url  = ENV.fetch("XUQM_SERVER_URL")
    app_key     = ENV.fetch("XUQM_APP_KEY")
    api_token   = ENV.fetch("XUQM_API_TOKEN")
    tenant_url  = ENV.fetch("XUQM_TENANT_URL", "").strip
    scheme      = ENV.fetch("XUQM_SCHEME")
    workspace   = ENV.fetch("XUQM_WORKSPACE")
    bundle_id   = ENV.fetch("XUQM_BUNDLE_ID", "")
    tenant_cfg = xuqm_fetch_sdk_config(tenant_url, app_key, "IOS")
    store_targets = ENV.fetch("XUQM_STORE_TARGETS", tenant_cfg.fetch("updateDefaultStoreTargets", "APP_STORE"))
    auto_publish  = ENV.fetch("XUQM_AUTO_PUBLISH_AFTER_REVIEW", tenant_cfg.fetch("updateDefaultAutoPublishAfterReview", "false")) == "true"
    publish_immediately = ENV.fetch("XUQM_PUBLISH_IMMEDIATELY", tenant_cfg.fetch("updateDefaultPublishImmediately", "false")) == "true"
    webhook_url   = ENV.fetch("XUQM_WEBHOOK_URL", tenant_cfg.fetch("updateDefaultWebhookUrl", ""))
    scheduled_at  = ENV.fetch("XUQM_SCHEDULED_PUBLISH_AT", tenant_cfg.fetch("updateDefaultScheduledPublishAt", ""))
    publish_mode  = ENV.fetch("XUQM_PUBLISH_MODE", tenant_cfg.fetch("updateDefaultPublishMode", "")).to_s.strip.upcase
    dry_run       = ENV.fetch("XUQM_DRY_RUN", "false") == "true" || publish_mode == "DRY_RUN" || !tenant_cfg.fetch("updateEnabled", true)
    allow_version_mismatch = ENV.fetch("XUQM_ALLOW_VERSION_MISMATCH", "false") == "true"

    # ── 1. Read local version ────────────────────────────────────────────
    version_name = get_version_number(target: scheme)
    version_code = get_build_number.to_i
    UI.message("Local version: #{version_name} (#{version_code}), appKey: #{app_key}")

    # ── 2. Check server latest ───────────────────────────────────────────
    server_version_code = xuqm_get_latest_version_code(server_url, app_key, api_token, "IOS")
    UI.message("Server latest versionCode: #{server_version_code}")

    if version_code <= server_version_code
      if !allow_version_mismatch
        UI.message("Local version is not greater than server latest. Please enter corrected release version info.")
        version_name_input = prompt(text: "Release version name [#{version_name}]: ", ci_input: "").to_s.strip
        version_code_input = prompt(text: "Release version code [#{version_code}]: ", ci_input: "").to_s.strip
        version_name = version_name_input unless version_name_input.empty?
        version_code = version_code_input.to_i unless version_code_input.empty?
      end
      UI.user_error!(
        "Local versionCode (#{version_code}) must be greater than server (#{server_version_code}). " \
        "Please bump CFBundleVersion before releasing."
      ) if version_code <= server_version_code
    end

    if version_name != get_version_number(target: scheme)
      increment_version_number(version_number: version_name)
    end
    if version_code != get_build_number.to_i
      increment_build_number(build_number: version_code.to_s)
    end

    # ── 3. Archive & export IPA ──────────────────────────────────────────
    archive_path = File.join(Dir.tmpdir, "xuqm_#{scheme}.xcarchive")
    ipa_output   = File.join(Dir.tmpdir, "xuqm_#{scheme}_ipa")

    build_app(
      scheme: scheme,
      workspace: workspace,
      archive_path: archive_path,
      output_directory: ipa_output,
      export_method: "app-store",
      silent: false,
    )

    ipa_path = Dir.glob("#{ipa_output}/*.ipa").first
    UI.user_error!("IPA not found in #{ipa_output}") unless ipa_path
    UI.success("IPA: #{ipa_path}")

    if dry_run
      UI.message("Dry-run summary:")
      UI.message("  updateEnabled=#{tenant_cfg.fetch("updateEnabled", true)}")
      UI.message("  releaseVersion=#{version_name} (#{version_code})")
      UI.message("  publishMode=#{publish_mode.empty? ? "MANUAL" : publish_mode}")
      UI.message("  publishImmediately=#{publish_immediately}")
      UI.message("  autoPublishAfterReview=#{auto_publish}")
      UI.message("  scheduledAt=#{scheduled_at.empty? ? "-" : scheduled_at}")
      UI.message("  webhookUrl=#{webhook_url.empty? ? "-" : webhook_url}")
      UI.message("  storeTargets=#{store_targets.empty? ? "-" : store_targets}")
      UI.message("Dry-run completed. No upload performed.")
      next
    end

    # ── 4. Upload to XuqmGroup update service ────────────────────────────
    change_log    = prompt(text: "Release notes: ", ci_input: "")
    if publish_mode.empty?
      publish_mode = if publish_immediately
                       "NOW"
                     elsif !scheduled_at.empty?
                       "SCHEDULED"
                     elsif auto_publish
                       "AUTO_REVIEW"
                     else
                       prompt(text: "Publish mode (MANUAL/NOW/SCHEDULED/AUTO_REVIEW) [MANUAL]: ", ci_input: "").to_s.strip.upcase
                     end
    end
    dry_run = true if publish_mode == "DRY_RUN"
    if !scheduled_at.empty?
      publish_immediately = false
      auto_publish = false
    end
    if publish_mode == "SCHEDULED" && scheduled_at.empty?
      scheduled_at = prompt(text: "Scheduled publish time (ISO datetime): ", ci_input: "").to_s.strip
    end
    if publish_mode == "NOW"
      publish_immediately = true
      auto_publish = false
    elsif publish_mode == "AUTO_REVIEW"
      auto_publish = true
      publish_immediately = false
    end
    version_id    = xuqm_upload_ipa(
      server_url: server_url,
      app_key: app_key,
      api_token: api_token,
      ipa_path: ipa_path,
      version_name: version_name,
      version_code: version_code,
      change_log: change_log,
      package_name: bundle_id,
      store_targets: store_targets.empty? ? [] : store_targets.split(",").map(&:strip),
      auto_publish_after_review: auto_publish,
      scheduled_publish_at: scheduled_at,
      webhook_url: webhook_url,
    )
    UI.success("Uploaded, version ID: #{version_id}")

    # ── 5. Submit to App Store via App Store Connect API ─────────────────
    if store_targets.include?("APP_STORE")
      UI.message("Submitting to App Store Connect...")

      # Use Fastlane's built-in action — handles JWT auth, upload, and submission
      # Requires APP_STORE_CONNECT_API_KEY_* env vars
      api_key = app_store_connect_api_key(
        key_id:        ENV.fetch("APP_STORE_CONNECT_API_KEY_ID"),
        issuer_id:     ENV.fetch("APP_STORE_CONNECT_API_ISSUER_ID"),
        key_filepath:  ENV.fetch("APP_STORE_CONNECT_API_KEY_FILEPATH"),
      )

      upload_to_app_store(
        api_key:           api_key,
        ipa:               ipa_path,
        submit_for_review: true,
        force:             true,
        automatic_release: auto_publish,
        skip_metadata:     true,
        skip_screenshots:  true,
        run_precheck_before_submit: false,
      )

      # Notify update service that we've submitted
      xuqm_post(
        url: "#{server_url}/api/v1/updates/store/app/#{version_id}/submit",
        token: api_token,
        body: { storeTypes: ["APP_STORE"] }.to_json,
      )
      UI.success("App Store submission done. Review status will update via webhook or manually.")
    end

    # ── 6. Trigger server-side store submission for other stores ──────────
    other_stores = store_targets.split(",").map(&:strip).reject { |s| s == "APP_STORE" }
    unless other_stores.empty?
      xuqm_post(
        url: "#{server_url}/api/v1/updates/store/app/#{version_id}/execute-submit",
        token: api_token,
        body: { storeTypes: other_stores }.to_json,
      )
    end

    if publish_immediately && !auto_publish && scheduled_at.empty?
      xuqm_post(
        url: "#{server_url}/api/v1/updates/app/#{version_id}/publish",
        token: api_token,
        body: {}.to_json,
      )
      UI.success("Update service version published immediately.")
    end

    UI.success("Release complete. Version ID: #{version_id}")
    if scheduled_at.empty?
      if auto_publish
        UI.message("Will auto-publish after all store reviews pass.")
      elsif publish_immediately
        UI.message("Published immediately in update service.")
      else
        UI.message("Publish manually or wait for auto-publish.")
      end
    else
      UI.message("Will publish at #{scheduled_at}")
    end
  end

  # ── Private helpers ──────────────────────────────────────────────────────

  private_lane :xuqm_fetch_sdk_config do |options|
    tenant_url = options[:tenant_url].to_s.strip
    return {} if tenant_url.empty?

    uri = URI("#{tenant_url}/api/sdk/config?appId=#{options[:app_key]}&platform=#{options[:platform]}")
    req = Net::HTTP::Get.new(uri)
    resp = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |h| h.request(req) }
    body = JSON.parse(resp.body)
    body["data"] || {}
  rescue => e
    UI.message("Failed to load tenant defaults: #{e.message}")
    {}
  end

  private_lane :xuqm_get_latest_version_code do |options|
    uri = URI("#{options[:server_url]}/api/v1/updates/app/list?appId=#{options[:app_key]}&platform=#{options[:platform]}")
    req = Net::HTTP::Get.new(uri)
    req["Authorization"] = "Bearer #{options[:api_token]}"
    resp = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |h| h.request(req) }
    data = JSON.parse(resp.body)
    codes = (data.dig("data") || []).filter_map { |v| v["versionCode"] }
    codes.max || 0
  end

  private_lane :xuqm_upload_ipa do |options|
    require 'net/http/post/multipart'

    uri = URI("#{options[:server_url]}/api/v1/updates/app/upload")
    request = Net::HTTP::Post::Multipart.new(uri.path,
      "appId"                  => options[:app_key],
      "platform"               => "IOS",
      "versionName"            => options[:version_name],
      "versionCode"            => options[:version_code].to_s,
      "changeLog"              => options[:change_log].to_s,
      "forceUpdate"            => "false",
      "packageName"            => options[:package_name].to_s,
      "autoPublishAfterReview" => options[:auto_publish_after_review].to_s,
      "storeSubmitTargets"     => options[:store_targets].empty? ? "" : options[:store_targets].to_json,
      "scheduledPublishAt"     => options[:scheduled_publish_at].to_s,
      "webhookUrl"             => options[:webhook_url].to_s,
      "apkFile"                => UploadIO.new(File.open(options[:ipa_path]), "application/octet-stream", File.basename(options[:ipa_path])),
    )
    request["Authorization"] = "Bearer #{options[:api_token]}"

    resp = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |h| h.request(request) }
    data = JSON.parse(resp.body)
    data.dig("data", "id") || UI.user_error!("Upload failed: #{resp.body}")
  end

  private_lane :xuqm_post do |options|
    uri = URI(options[:url])
    req = Net::HTTP::Post.new(uri)
    req["Authorization"] = "Bearer #{options[:token]}"
    req["Content-Type"]  = "application/json"
    req.body = options[:body]
    Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |h| h.request(req) }
  end

end
