class JavaKeystore
  attr_reader :errors, :location, :keystore_alias, :password, :fingerprint
  attr_reader :signature_algorithm_name

  def initialize(location, keystore_alias, password, key_password = nil)
    raise "No such keystore file '#{location}'" unless File.exists?(File.expand_path(location))
    calabash_log "Reading keystore data from keystore file '#{File.expand_path(location)}'"

    keystore_data = system_with_stdout_on_success(Calabash::Android::Dependencies.keytool_path, '-list', '-v', '-alias', keystore_alias, '-keystore', location, '-storepass', password, '-J"-Dfile.encoding=utf-8"', '-J"-Duser.language=en-US"')

    if keystore_data.nil?
      if keystore_alias.empty?
        calabash_log "Could not obtain keystore data. Will try to extract alias automatically"

        keystore_data = system_with_stdout_on_success(Calabash::Android::Dependencies.keytool_path, '-list', '-v', '-keystore', location, '-storepass', password, '-J"-Dfile.encoding=utf-8"', '-J"-Duser.language=en-US"')
        aliases = keystore_data.scan(/Alias name\:\s*(.*)/).flatten

        if aliases.length == 0
          raise 'Could not extract alias automatically. Please specify alias using calabash-android setup'
        elsif aliases.length > 1
          raise 'Multiple aliases found in keystore. Please specify alias using calabash-android setup'
        else
          keystore_alias = aliases.first
          calabash_log "Extracted keystore alias '#{keystore_alias}'. Continuing"

          return initialize(location, keystore_alias, password)
        end
      else
        error = "Could not list certificates in keystore. Probably because the password was incorrect."
        @errors = [{:message => error}]
        calabash_log error
        raise error
      end
    end

    @location = location
    @keystore_alias = keystore_alias
    @password = password
    @key_password = key_password
    calabash_log "Key store data:"
    calabash_log keystore_data
    @fingerprint = extract_sha1_fingerprint(keystore_data)
    @signature_algorithm_name = extract_signature_algorithm_name(keystore_data)
    calabash_log "Fingerprint: #{fingerprint}"
    calabash_log "Signature algorithm name: #{signature_algorithm_name}"
  end

  def sign_apk(apk_path, dest_path)
    raise "Cannot sign with a miss configured keystore" if errors
    raise "No such file: #{apk_path}" unless File.exists?(apk_path)

    # E.g. MD5withRSA or MD5withRSAandMGF1
    encryption = signature_algorithm_name.split('with')[1].split('and')[0]

    # keytool with newer java versions has "Signature algorithm name: SHA1withRSA (weak)"
    encryption.gsub!(' (weak)', '')

    signing_algorithm = "SHA1with#{encryption}"
    digest_algorithm = 'SHA1'

    calabash_log "Signing using the signature algorithm: '#{signing_algorithm}'"
    calabash_log "Signing using the digest algorithm: '#{digest_algorithm}'"

    cmd_args = {
      '-sigfile' => 'CERT',
      '-sigalg' => signing_algorithm,
      '-digestalg' => digest_algorithm,
      '-signedjar' => dest_path,
      '-storepass' => password,
      '-keystore' =>  location,
    }

    unless @key_password.nil?
      cmd_args['-keypass'] = @key_password
    end

    cmd_args = cmd_args.flatten
    cmd_args << apk_path
    cmd_args << keystore_alias

    result = system_with_stdout_on_success(Calabash::Android::Dependencies.jarsigner_path, *cmd_args)

    unless result
      raise "Could not sign app: #{apk_path}"
    end
  end

  def system_with_stdout_on_success(cmd, *args)
    a = Escape.shell_command(args)
    cmd = "\"#{cmd}\" #{a.gsub("'", '"')}"
    calabash_log cmd
    out = `#{cmd}`
    if $?.exitstatus == 0
      out
    else
      nil
    end
  end

  def self.read_keystore_with_default_password_and_alias(path)
    path = File.expand_path path

    if File.exists? path
      keystore = JavaKeystore.new(path, 'androiddebugkey', 'android')
      if keystore.errors
        calabash_log "Trying to "
        nil
      else
        calabash_log "Unlocked keystore at #{path} - fingerprint: #{keystore.fingerprint}"
        keystore
      end
    else
      calabash_log "Trying to read keystore from: #{path} - no such file"
      nil
    end
  end

  def self.get_keystores
    if keystore = keystore_from_settings
      [ keystore ]
    else
      [
        read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], "/.android/debug.keystore")),
        read_keystore_with_default_password_and_alias("debug.keystore"),
        read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], ".local/share/Xamarin/Mono\\ for\\ Android/debug.keystore")),
        read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], "AppData/Local/Xamarin/Mono for Android/debug.keystore")),
      ].compact
    end
  end

  def self.keystore_from_settings
      keystore = JSON.parse(IO.read(".calabash_settings")) if File.exist? ".calabash_settings"
      keystore = JSON.parse(IO.read("calabash_settings")) if File.exist? "calabash_settings"
      return unless keystore
      fail_if_key_missing(keystore, "keystore_location")
      fail_if_key_missing(keystore, "keystore_password")
      fail_if_key_missing(keystore, "keystore_alias")
      keystore["keystore_location"] = File.expand_path(keystore["keystore_location"])
      calabash_log("Keystore location specified in #{File.exist?(".calabash_settings") ? ".calabash_settings" : "calabash_settings"}.")
      JavaKeystore.new(keystore["keystore_location"], keystore["keystore_alias"], keystore["keystore_password"], keystore['key_password'])
  end

  def self.fail_if_key_missing(map, key)
    raise "Found .calabash_settings but no #{key} defined." unless map[key]
  end

end
