require 'supply'
require 'supply/options'

module Fastlane
  module Actions
    class DownloadUniversalApkFromGooglePlayAction < Action
      def self.run(params)
        package_name = params[:package_name]
        version_code = params[:version_code]
        destination = params[:destination]
        cert_sha = params[:certificate_sha256_hash]

        client = Supply::Client.make_from_config(params: params)

        UI.message("Fetching the list of generated APKs from the Google API...")
        all_universal_apks = client.list_generated_universal_apks(package_name: package_name, version_code: version_code)
        matching_apks = all_universal_apks.select { |apk| cert_sha.nil? || apk.certificate_sha256_hash&.casecmp?(cert_sha) }

        all_certs_printable_list = all_universal_apks.map { |apk| " - #{apk.certificate_sha256_hash}" }
        if matching_apks.count > 1
          message = <<~ERROR
            We found multiple Generated Universal APK, with the following `certificate_sha256_hash`:
            #{all_certs_printable_list.join("\n")}

            Use the `certificate_sha256_hash` parameter to specify which one to download.
          ERROR
          UI.user_error!(message)
        elsif matching_apks.empty?
          # NOTE: if no APK was found at all to begin with, the client would already have raised a user_error!('Google Api Error ...')
          message = <<~ERROR
            None of the Universal APK(s) found for this version code matched the `certificate_sha256_hash` of `#{cert_sha}`.

            We found #{all_universal_apks.count} Generated Universal APK(s), but with a different `certificate_sha256_hash`:
            #{all_certs_printable_list.join("\n")}
          ERROR
          UI.user_error!(message)
        end

        UI.message("Downloading Generated Universal APK to `#{destination}`...")
        FileUtils.mkdir_p(File.dirname(destination))
        client.download_generated_universal_apk(generated_universal_apk: matching_apks.first, destination: destination)

        UI.success("Universal APK successfully downloaded to `#{destination}`.")
        destination
      end

      #####################################################
      # @!group Documentation
      #####################################################

      def self.description
        "Download the Universal APK of a given version code from the Google Play Console"
      end

      def self.details
        <<~DETAILS
        Download the universal APK of a given version code from the Google Play Console.

        This uses _fastlane_ `supply` (and the `AndroidPublisher` Google API) to download the Universal APK
        generated by Google after you uploaded an `.aab` bundle to the Play Console.

        See https://developers.google.com/android-publisher/api-ref/rest/v3/generatedapks/list
        DETAILS
      end

      def self.available_options
        # Only borrow _some_ of the ConfigItems from https://github.com/fastlane/fastlane/blob/master/supply/lib/supply/options.rb
        # So we don't have to duplicate the name, env_var, type, description, and verify_block of those here.
        supply_borrowed_options = Supply::Options.available_options.select do |o|
          %i[package_name version_code json_key json_key_data root_url timeout].include?(o.key)
        end
        # Adjust the description for the :version_code ConfigItem for our action's use case
        supply_borrowed_options.find { |o| o.key == :version_code }&.description = "The versionCode for which to download the generated APK"

        [
          *supply_borrowed_options,

          # The remaining ConfigItems below are specific to this action
          FastlaneCore::ConfigItem.new(key: :destination,
                                       env_name: 'DOWNLOAD_UNIVERSAL_APK_DESTINATION',
                                       optional: false,
                                       type: String,
                                       description: "The path on disk where to download the Generated Universal APK",
                                       verify_block: proc do |value|
                                         UI.user_error!("The 'destination' must be a file path with the `.apk` file extension") unless File.extname(value) == '.apk'
                                       end),
          FastlaneCore::ConfigItem.new(key: :certificate_sha256_hash,
                                       env_name: 'DOWNLOAD_UNIVERSAL_APK_CERTIFICATE_SHA256_HASH',
                                       optional: true,
                                       type: String,
                                       description: "The SHA256 hash of the signing key for which to download the Universal, Code-Signed APK for. " \
                                       + "Use 'xx:xx:xx:…' format (32 hex bytes separated by colons), as printed by `keytool -list -keystore <keystorefile>`. " \
                                       + "Only useful to provide if you have multiple signing keys configured on GPC, to specify which generated APK to download",
                                       verify_block: proc do |value|
                                         bytes = value.split(':')
                                         next if bytes.length == 32 && bytes.all? { |byte| /^[0-9a-fA-F]{2}$/.match?(byte) }

                                         UI.user_error!("When provided, the certificate sha256 must be in the 'xx:xx:xx:…:xx' (32 hex bytes separated by colons) format")
                                       end)
        ]
      end

      def self.output
        # Define the shared values you are going to provide
      end

      def self.return_value
        'The path to the downloaded Universal APK. The action will raise an exception if it failed to find or download the APK in Google Play'
      end

      def self.authors
        ['Automattic']
      end

      def self.category
        :production
      end

      def self.is_supported?(platform)
        platform == :android
      end
    end
  end
end
