require "fileutils"
require "json"
require "open3"
require "shellwords"
require "supply/client"

default_platform(:android)

ANDROID_FASTLANE_ROOT = File.expand_path(__dir__, Dir.pwd)
DEFAULT_PLAY_PACKAGE_NAME = "ai.openclaw.app"
DEFAULT_PLAY_TRACK = "internal"
DEFAULT_PLAY_RELEASE_STATUS = "completed"
ANDROID_RELEASE_SIGNING_GRADLE_PROPERTIES = [
  "OPENCLAW_ANDROID_STORE_FILE",
  "OPENCLAW_ANDROID_STORE_PASSWORD",
  "OPENCLAW_ANDROID_KEY_ALIAS",
  "OPENCLAW_ANDROID_KEY_PASSWORD"
].freeze

def load_env_file(path)
  return unless File.exist?(path)

  File.foreach(path) do |line|
    stripped = line.strip
    next if stripped.empty? || stripped.start_with?("#")

    key, value = stripped.split("=", 2)
    next if key.nil? || key.empty? || value.nil?

    ENV[key] = value if ENV[key].nil? || ENV[key].strip.empty?
  end
end

def env_present?(value)
  !value.nil? && !value.strip.empty?
end

def android_root
  File.expand_path("..", ANDROID_FASTLANE_ROOT)
end

def repo_root
  File.expand_path("../..", android_root)
end

def android_release_signing_script
  File.join(repo_root, "scripts", "android-release-signing.mjs")
end

def android_release_signing_materialized_properties_path
  File.join(android_root, "build", "release-signing", "gradle.properties")
end

def shell_join(args)
  args.shelljoin
end

def play_package_name
  raw = ENV["GOOGLE_PLAY_PACKAGE_NAME"].to_s.strip
  raw.empty? ? DEFAULT_PLAY_PACKAGE_NAME : raw
end

def play_track
  raw = ENV["GOOGLE_PLAY_TRACK"].to_s.strip
  raw.empty? ? DEFAULT_PLAY_TRACK : raw
end

def play_release_status
  raw = ENV["GOOGLE_PLAY_RELEASE_STATUS"].to_s.strip
  raw.empty? ? DEFAULT_PLAY_RELEASE_STATUS : raw
end

def play_validate_only?
  ENV["GOOGLE_PLAY_VALIDATE_ONLY"] == "1"
end

def play_metadata_upload_requested?
  ENV["SUPPLY_UPLOAD_METADATA"] == "1"
end

def play_screenshot_upload_requested?
  ENV["SUPPLY_UPLOAD_SCREENSHOTS"] == "1"
end

def play_image_upload_requested?
  ENV["SUPPLY_UPLOAD_IMAGES"] == "1"
end

def play_auth_options
  json_key = ENV["GOOGLE_PLAY_JSON_KEY"].to_s.strip
  json_key = ENV["SUPPLY_JSON_KEY"].to_s.strip if json_key.empty?
  json_key = ENV["GOOGLE_PLAY_JSON_KEY_PATH"].to_s.strip if json_key.empty?
  return { json_key: json_key } unless json_key.empty?

  json_key_data = ENV["GOOGLE_PLAY_JSON_KEY_DATA"].to_s.strip
  json_key_data = ENV["SUPPLY_JSON_KEY_DATA"].to_s.strip if json_key_data.empty?
  return { json_key_data: json_key_data } unless json_key_data.empty?

  UI.user_error!("Missing Google Play API credentials. Set GOOGLE_PLAY_JSON_KEY or GOOGLE_PLAY_JSON_KEY_DATA.")
end

def validate_play_auth!
  client = nil
  begin
    client = Supply::Client.make_from_config(params: play_auth_options)
    client.begin_edit(package_name: play_package_name)
  rescue => e
    UI.user_error!("Google Play API credentials are invalid for #{play_package_name}: #{e.message}")
  ensure
    if client&.current_edit
      begin
        client.abort_current_edit
      rescue => e
        UI.user_error!("Google Play API credentials opened a validation edit but could not close it: #{e.message}")
      end
    end
  end
end

def read_android_version_metadata
  stdout, stderr, status = Open3.capture3(
    "node",
    "--import",
    "tsx",
    File.join(repo_root, "scripts", "android-version.ts"),
    "--json",
    "--root",
    repo_root
  )
  unless status.success?
    detail = stderr.to_s.strip
    detail = stdout.to_s.strip if detail.empty?
    UI.user_error!("Failed to read Android version metadata: #{detail}")
  end

  parsed = JSON.parse(stdout)
  version = parsed.fetch("canonicalVersion").to_s
  version_code = parsed.fetch("versionCode").to_i
  UI.user_error!("Android version helper returned incomplete metadata.") if version.empty? || version_code <= 0

  { version: version, version_code: version_code }
rescue JSON::ParserError => e
  UI.user_error!("Invalid JSON from Android version helper: #{e.message}")
end

def sync_android_versioning!
  sh(shell_join(["node", "--import", "tsx", File.join(repo_root, "scripts", "android-sync-versioning.ts"), "--check", "--root", repo_root]))
end

def android_release_notes_path
  File.join(ANDROID_FASTLANE_ROOT, "metadata", "android", "en-US", "release_notes.txt")
end

def validate_android_release_notes!
  release_notes_path = android_release_notes_path
  UI.user_error!("Missing Android release notes at #{release_notes_path}. Run `pnpm android:version:sync`.") unless File.exist?(release_notes_path)
  UI.user_error!("Android release notes at #{release_notes_path} are empty.") unless env_present?(File.read(release_notes_path))
end

def android_changelog_path(version_code)
  File.join(ANDROID_FASTLANE_ROOT, "metadata", "android", "en-US", "changelogs", "#{version_code}.txt")
end

def sync_android_changelog!(version_code)
  validate_android_release_notes!

  changelog_path = android_changelog_path(version_code)
  FileUtils.mkdir_p(File.dirname(changelog_path))
  File.write(changelog_path, File.read(android_release_notes_path))
  changelog_path
end

def play_metadata_path
  File.join(ANDROID_FASTLANE_ROOT, "metadata", "android")
end

def play_screenshot_paths
  Dir[File.join(play_metadata_path, "**", "images", "**", "*.png")]
end

def validate_android_screenshots!
  return unless play_screenshot_upload_requested?

  if play_screenshot_paths.empty?
    UI.user_error!("SUPPLY_UPLOAD_SCREENSHOTS=1 but no PNG screenshots were found under apps/android/fastlane/metadata/android/*/images.")
  end
end

def release_artifact_path(version)
  File.join(android_root, "build", "release-artifacts", "openclaw-#{version}-play-release.aab")
end

def build_release_artifacts!
  sh(shell_join(["bun", File.join(android_root, "scripts", "build-release-artifacts.ts")]))
end

def capture_android_screenshots!
  sh(shell_join(["bash", File.join(repo_root, "scripts", "android-screenshots.sh")]))
end

def mobile_release_ref_script
  File.join(repo_root, "scripts", "mobile-release-ref.ts")
end

def release_git_sha
  stdout, stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: repo_root)
  UI.user_error!("Unable to resolve release Git SHA: #{stderr.strip}") unless status.success?
  stdout.strip
end

def mobile_release_ref_command(command, platform:, version:, build: nil, version_code: nil, sha: nil)
  args = [
    "node",
    "--import",
    "tsx",
    mobile_release_ref_script,
    command,
    "--platform",
    platform,
    "--version",
    version,
    "--root",
    repo_root,
  ]
  args.push("--build", build.to_s) if build
  args.push("--version-code", version_code.to_s) if version_code
  args.push("--sha", sha.to_s) if sha
  sh(shell_join(args))
end

def ensure_mobile_release_ref_available!(platform:, version:, build: nil, version_code: nil, sha: nil)
  mobile_release_ref_command(
    "preflight",
    platform: platform,
    version: version,
    build: build,
    version_code: version_code,
    sha: sha
  )
end

def record_mobile_release_ref!(platform:, version:, build: nil, version_code: nil, sha: nil)
  mobile_release_ref_command(
    "record",
    platform: platform,
    version: version,
    build: build,
    version_code: version_code,
    sha: sha
  )
end

def read_android_release_signing_properties!(path)
  UI.user_error!("Missing materialized Android release signing properties at #{path}.") unless File.exist?(path)

  properties = {}
  File.foreach(path) do |line|
    stripped = line.strip
    next if stripped.empty? || stripped.start_with?("#")

    key, value = stripped.split("=", 2)
    next if key.nil? || key.empty? || value.nil?

    properties[key] = value.strip
  end

  missing = ANDROID_RELEASE_SIGNING_GRADLE_PROPERTIES.reject { |key| env_present?(properties[key]) }
  UI.user_error!("Materialized Android release signing properties are missing: #{missing.join(', ')}.") unless missing.empty?

  properties
end

def export_android_release_signing_properties!(path)
  read_android_release_signing_properties!(path).each do |key, value|
    ENV["ORG_GRADLE_PROJECT_#{key}"] = value
  end
end

def sync_android_release_signing!
  sh(shell_join(["node", android_release_signing_script, "--mode", "sync-pull"]))
  export_android_release_signing_properties!(android_release_signing_materialized_properties_path)
end

def prepare_android_release_signing!
  if env_present?(ENV["MATCH_PASSWORD"])
    sync_android_release_signing!
  elsif File.exist?(android_release_signing_materialized_properties_path)
    export_android_release_signing_properties!(android_release_signing_materialized_properties_path)
  end
end

def validate_android_release_signing!
  Dir.chdir(android_root) do
    sh(shell_join(["./gradlew", ":app:bundlePlayRelease", "--dry-run"]))
  end
end

def print_android_release_plan!(version_metadata)
  UI.message("Android Play release plan:")
  UI.message("  package: #{play_package_name}")
  UI.message("  track: #{play_track}")
  UI.message("  release_status: #{play_release_status}")
  UI.message("  validate_only: #{play_validate_only?}")
  UI.message("  versionName: #{version_metadata.fetch(:version)}")
  UI.message("  versionCode: #{version_metadata.fetch(:version_code)}")
end

def validate_android_release_preflight!(version_metadata)
  validate_play_auth!
  prepare_android_release_signing!
  validate_android_release_signing!
  validate_android_release_notes!
  print_android_release_plan!(version_metadata)
end

def upload_play_store_metadata!(version_metadata)
  validate_android_screenshots!
  sync_android_changelog!(version_metadata.fetch(:version_code))

  upload_to_play_store(
    **play_auth_options,
    package_name: play_package_name,
    track: play_track,
    version_code: version_metadata.fetch(:version_code),
    metadata_path: play_metadata_path,
    skip_upload_apk: true,
    skip_upload_aab: true,
    skip_upload_metadata: !play_metadata_upload_requested?,
    skip_upload_changelogs: false,
    skip_upload_images: !play_image_upload_requested?,
    skip_upload_screenshots: !play_screenshot_upload_requested?,
    validate_only: play_validate_only?
  )
end

def upload_play_store_build!(version_metadata, upload_metadata: false, upload_images: false, upload_screenshots: false)
  release_sha = release_git_sha
  ensure_mobile_release_ref_available!(
    platform: "android",
    version: version_metadata.fetch(:version),
    version_code: version_metadata.fetch(:version_code),
    sha: release_sha
  )
  ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1" if upload_screenshots
  validate_android_screenshots!
  sync_android_changelog!(version_metadata.fetch(:version_code))
  artifact_path = release_artifact_path(version_metadata.fetch(:version))
  UI.user_error!("Missing Play release artifact at #{artifact_path}. Run pnpm android:release:archive first.") unless File.exist?(artifact_path)

  upload_to_play_store(
    **play_auth_options,
    package_name: play_package_name,
    aab: artifact_path,
    track: play_track,
    release_status: play_release_status,
    metadata_path: play_metadata_path,
    skip_upload_apk: true,
    skip_upload_metadata: !upload_metadata,
    skip_upload_changelogs: false,
    skip_upload_images: !upload_images,
    skip_upload_screenshots: !upload_screenshots,
    validate_only: play_validate_only?
  )

  unless play_validate_only?
    record_mobile_release_ref!(
      platform: "android",
      version: version_metadata.fetch(:version),
      version_code: version_metadata.fetch(:version_code),
      sha: release_sha
    )
  end
end

load_env_file(File.join(ANDROID_FASTLANE_ROOT, ".env"))

platform :android do
  desc "Validate Google Play API credentials"
  lane :auth_check do
    validate_play_auth!
    UI.success("Google Play API credentials are valid.")
  end

  desc "Print the Android release signing plan"
  lane :signing_plan do
    sh(shell_join(["node", android_release_signing_script, "--mode", "plan"]))
  end

  desc "Pull encrypted Android release signing assets and validate Gradle release signing"
  lane :signing_check do
    sync_android_release_signing!
    validate_android_release_signing!
    UI.success("Android release signing assets are available locally.")
  end

  desc "Pull encrypted Android release signing assets from the shared signing repo"
  lane :signing_sync_pull do
    sync_android_release_signing!
    UI.success("Pulled Android release signing assets.")
  end

  desc "Create or refresh encrypted Android release signing assets in the shared signing repo"
  lane :signing_sync_push do
    sh(shell_join(["node", android_release_signing_script, "--mode", "sync-push"]))
    UI.success("Pushed Android release signing assets.")
  end

  desc "Validate Android Play release auth, signing, versioning, and release notes"
  lane :release_preflight do
    sync_android_versioning!
    version_metadata = read_android_version_metadata
    validate_android_release_preflight!(version_metadata)
    UI.success("Android Play release preflight passed for #{version_metadata[:version]} (#{version_metadata[:version_code]}).")
  end

  desc "Upload Google Play metadata, changelog, and optional screenshots"
  lane :metadata do
    sync_android_versioning!
    version_metadata = read_android_version_metadata
    ENV["SUPPLY_UPLOAD_METADATA"] = "1" unless ENV.key?("SUPPLY_UPLOAD_METADATA")
    upload_play_store_metadata!(version_metadata)
    UI.success("Uploaded Android Play metadata for #{version_metadata[:version]} (#{version_metadata[:version_code]}).")
  end

  desc "Build signed Android release artifacts locally without uploading"
  lane :play_store_archive do
    sync_android_versioning!
    prepare_android_release_signing!
    build_release_artifacts!
  end

  desc "Generate deterministic Android screenshots for Google Play metadata"
  lane :screenshots do
    capture_android_screenshots!
  end

  desc "Upload the signed Play AAB to Google Play"
  lane :play_store do
    sync_android_versioning!
    version_metadata = read_android_version_metadata
    upload_play_store_build!(version_metadata)
    UI.success("Uploaded Android Play build to #{play_track}: version=#{version_metadata[:version]} code=#{version_metadata[:version_code]}")
  end

  desc "Upload Android metadata, archive release artifacts, then upload the Play AAB"
  lane :release_upload do
    sync_android_versioning!
    version_metadata = read_android_version_metadata
    validate_android_release_preflight!(version_metadata)
    screenshots
    ENV["SUPPLY_UPLOAD_METADATA"] = "1"
    ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1"
    build_release_artifacts!
    upload_play_store_build!(version_metadata, upload_metadata: true, upload_screenshots: true)
    UI.success("Uploaded Android Play build to #{play_track}: version=#{version_metadata[:version]} code=#{version_metadata[:version_code]}")
    UI.important("Production promotion remains manual in Google Play Console.")
  end
end
