# frozen_string_literal: true

class PasswordHasher
  class InvalidAlgorithmError < StandardError
  end

  class UnsupportedAlgorithmError < StandardError
  end

  HANDLERS = {}

  def self.register_handler(id, &blk)
    HANDLERS[id] = blk
  end

  # Algorithm should be specified according to the id/params parts of the
  # PHC string format.
  # https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
  def self.hash_password(password:, salt:, algorithm:)
    algorithm = algorithm.delete_prefix("$").delete_suffix("$")

    parts = algorithm.split("$")
    raise InvalidAlgorithmError if parts.length != 2

    algorithm_id, algorithm_params = parts

    algorithm_params = algorithm_params.split(",").map { |pair| pair.split("=") }.to_h

    handler = HANDLERS[algorithm_id]
    if handler.nil?
      raise UnsupportedAlgorithmError.new "#{algorithm_id} is not a supported password algorithm"
    end

    handler.call(password: password, salt: salt, params: algorithm_params)
  end

  register_handler("pbkdf2-sha256") do |password:, salt:, params:|
    raise ArgumentError.new("Salt and password must be supplied") if password.blank? || salt.blank?

    if params["l"].to_i != 32
      raise UnsupportedAlgorithmError.new("pbkdf2 implementation only supports l=32")
    end

    if params["i"].to_i < 1
      raise UnsupportedAlgorithmError.new("pbkdf2 iterations must be 1 or more")
    end

    Pbkdf2.hash_password(password, salt, params["i"].to_i, "sha256")
  end
end