require "digest"
require "json"

# -- Shared helpers --

def version_info
  pubspec = File.read(File.expand_path("../pubspec.yaml", __dir__))
  version = pubspec.match(/^version:\s*(.+)$/)[1].strip
  name, build = version.split("+")
  { name: name, build: build }
end

def version_name
  v = version_info
  "#{v[:build]} (#{v[:name]})"
end

def changelog_notes
  version = version_info[:name]
  changelog_path = File.expand_path("../CHANGELOG.md", __dir__)
  return "Release #{version}" unless File.exist?(changelog_path)

  changelog = File.read(changelog_path)
  escaped = Regexp.escape(version)
  pattern = /^## (?:\[#{escaped}\]|#{escaped}[\s(]).*?\n(.*?)(?=^## |\z)/m
  match = changelog.match(pattern)
  return "Release #{version}" unless match

  match[1].strip
    .gsub(/\s*\(\[[\da-f]+\]\([^)]+\)\)/, "")
    .gsub(/^\*\s+\*\*[^*]+:\*\*\s*/m, "- ")
    .gsub(/^\*\s+/, "- ")
    .gsub(/^### /, "")
    .gsub(/\n{3,}/, "\n\n")
    .strip
    .then { |n| n.empty? ? "Release #{version}" : n }
end

# -- Android --

default_platform(:android)

platform :android do
  # Google Play enforces a 500-char limit on release notes per language.
  PLAY_NOTES_MAX = 500
  PLAY_NOTES_TRAILER = "\n\n… see full notes on GitHub."

  def play_changelog
    notes = changelog_notes
    return notes if notes.length <= PLAY_NOTES_MAX

    budget = PLAY_NOTES_MAX - PLAY_NOTES_TRAILER.length
    truncated = notes[0, budget]
    last_newline = truncated.rindex("\n")
    truncated = truncated[0, last_newline] if last_newline && last_newline > budget / 2
    truncated.rstrip + PLAY_NOTES_TRAILER
  end

  # -- Image change detection --

  IMAGE_HASH_CACHE = File.expand_path(".image_hashes.json", __dir__)

  def current_image_hashes
    root = File.expand_path("metadata/android", __dir__)
    return {} unless Dir.exist?(root)

    files = Dir.glob(File.join(root, "**/images/**/*")).select { |f| File.file?(f) }
    files.sort.each_with_object({}) do |path, acc|
      rel = path.sub("#{root}/", "")
      acc[rel] = Digest::SHA256.file(path).hexdigest
    end
  end

  def cached_image_hashes
    return {} unless File.exist?(IMAGE_HASH_CACHE)
    JSON.parse(File.read(IMAGE_HASH_CACHE))
  rescue StandardError
    {}
  end

  def images_changed?
    current_image_hashes != cached_image_hashes
  end

  def save_image_hashes
    File.write(IMAGE_HASH_CACHE, JSON.pretty_generate(current_image_hashes))
  end

  desc "Upload AAB to Google Play"
  lane :deploy do |options|
    # Write changelog if not already present (CI generates it during the
    # release PR, but this covers manual deploys).
    version_code = version_info[:build]
    changelog_dir = File.expand_path("metadata/android/en-US/changelogs", __dir__)
    changelog_file = File.join(changelog_dir, "#{version_code}.txt")
    unless File.exist?(changelog_file)
      FileUtils.mkdir_p(changelog_dir)
      File.write(changelog_file, play_changelog)
    end

    changed = images_changed?
    UI.message(changed ? "Images changed — uploading." : "Images unchanged — skipping.")

    upload_to_play_store(
      aab: File.expand_path("../build/app/outputs/bundle/release/app-release.aab", __dir__),
      track: options[:track] || "internal",
      release_status: options[:status] || "draft",
      version_name: version_name,
      metadata_path: File.expand_path("metadata/android", __dir__),
      skip_upload_images: !changed,
      skip_upload_screenshots: !changed,
    )

    save_image_hashes if changed
  end

  desc "Sync metadata and screenshots only (no AAB upload)"
  lane :metadata do
    changed = images_changed?
    UI.message(changed ? "Images changed — uploading." : "Images unchanged — skipping.")

    upload_to_play_store(
      skip_upload_aab: true,
      skip_upload_apk: true,
      skip_upload_changelogs: true,
      metadata_path: File.expand_path("metadata/android", __dir__),
      skip_upload_images: !changed,
      skip_upload_screenshots: !changed,
    )

    save_image_hashes if changed
  end

  desc "Promote a release from one track to another"
  lane :promote do |options|
    from = options[:from] || "internal"
    to = options[:to] || "production"
    status = options[:status] || "draft"

    upload_to_play_store(
      track: from,
      track_promote_to: to,
      release_status: status,
      version_name: version_name,
      skip_upload_aab: true,
      skip_upload_apk: true,
      skip_upload_metadata: true,
      skip_upload_changelogs: true,
      skip_upload_images: true,
      skip_upload_screenshots: true,
    )
  end
end

# -- iOS --

platform :ios do
  def api_key
    key_id = ENV.fetch("APP_STORE_API_KEY")
    app_store_connect_api_key(
      key_id: key_id,
      issuer_id: ENV.fetch("APP_STORE_ISSUER_ID"),
      key_filepath: File.join(ENV.fetch("APP_STORE_KEY_PATH"), "AuthKey_#{key_id}.p8"),
    )
  end

  def find_ipa
    ipa_path = Dir[File.expand_path("../build/ios/ipa/*.ipa", __dir__)].first
    UI.user_error!("No IPA found in build/ios/ipa/. Run 'make ios-build' first.") unless ipa_path
    ipa_path
  end

  def sync_release_notes
    version_code = version_info[:build]
    changelog_file = File.expand_path("metadata/ios/en-US/changelogs/#{version_code}.txt", __dir__)
    notes = File.exist?(changelog_file) ? File.read(changelog_file).strip : changelog_notes
    release_notes_path = File.expand_path("metadata/ios/en-US/release_notes.txt", __dir__)
    File.write(release_notes_path, notes)
    UI.message("Synced release notes from build #{version_code} (#{notes.length} chars)")
    notes
  end

  desc "Upload to TestFlight"
  lane :beta do
    notes = sync_release_notes
    upload_to_testflight(
      api_key: api_key,
      ipa: find_ipa,
      changelog: notes,
      skip_waiting_for_build_processing: true,
    )
  end

  desc "Upload to App Store"
  lane :release do
    sync_release_notes
    deliver(
      api_key: api_key,
      ipa: find_ipa,
      metadata_path: File.expand_path("metadata/ios", __dir__),
      screenshots_path: File.expand_path("metadata/ios/en-US/screenshots", __dir__),
      skip_screenshots: true,
      submit_for_review: true,
      precheck_include_in_app_purchases: false,
    )
  end

  desc "Sync iOS metadata only (no IPA upload)"
  lane :metadata do
    deliver(
      api_key: api_key,
      metadata_path: File.expand_path("metadata/ios", __dir__),
      screenshots_path: File.expand_path("metadata/ios/en-US/screenshots", __dir__),
      skip_binary_upload: true,
      skip_screenshots: true,
      submit_for_review: false,
      precheck_include_in_app_purchases: false,
    )
  end
end
