module Braintree
  module Util # :nodoc:
    def self.extract_attribute_as_array(hash, attribute)
      raise UnexpectedError.new("Unprocessable entity due to an invalid request") if hash.nil?
      value = hash.has_key?(attribute) ? hash.delete(attribute) : []
      value.is_a?(Array) ? value : [value]
    end

    def self.hash_to_query_string(hash, namespace = nil)
      hash.collect do |key, value|
        full_key = namespace ? "#{namespace}[#{key}]" : key
        if value.is_a?(Hash)
          hash_to_query_string(value, full_key)
        else
          url_encode(full_key) + "=" + url_encode(value)
        end
      end.sort * '&'
    end

    def self.parse_query_string(qs)
      qs.split('&').inject({}) do |result, couplet|
        pair = couplet.split('=')
        result[CGI.unescape(pair[0]).to_sym] = CGI.unescape(pair[1])
        result
      end
    end

    def self.url_encode(text)
      CGI.escape text.to_s
    end

    def self.symbolize_keys(hash)
      hash.inject({}) do |new_hash, (key, value)|
        if value.is_a?(Hash)
          value = symbolize_keys(value)
        elsif value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) }
          value = value.map { |v| symbolize_keys(v) }
        end

        new_hash.merge(key.to_sym => value)
      end
    end

    def self.raise_exception_for_status_code(status_code, message=nil)
      case status_code.to_i
      when 401
        raise AuthenticationError
      when 403
        raise AuthorizationError, message
      when 404
        raise NotFoundError
      when 426
        raise UpgradeRequiredError, "Please upgrade your client library."
      when 500
        raise ServerError
      when 503
        raise DownForMaintenanceError
      else
        raise UnexpectedError, "Unexpected HTTP_RESPONSE #{status_code.to_i}"
      end
    end

    def self.to_big_decimal(decimal)
      case decimal
      when BigDecimal, NilClass
        decimal
      when String
        BigDecimal.new(decimal)
      else
        raise ArgumentError, "Argument must be a String or BigDecimal"
      end
    end

    def self.verify_keys(valid_keys, hash)
      flattened_valid_keys = _flatten_valid_keys(valid_keys)
      invalid_keys = _flatten_hash_keys(hash) - flattened_valid_keys
      invalid_keys = _remove_wildcard_keys(flattened_valid_keys, invalid_keys)
      if invalid_keys.any?
        sorted = invalid_keys.sort_by { |k| k.to_s }.join(", ")
        raise ArgumentError, "invalid keys: #{sorted}"
      end
    end

    def self._flatten_valid_keys(valid_keys, namespace = nil)
      valid_keys.inject([]) do |result, key|
        if key.is_a?(Hash)
          full_key = key.keys[0]
          full_key = (namespace ? "#{namespace}[#{full_key}]" : full_key)
          nested_keys = key.values[0]
          if nested_keys.is_a?(Array)
            result += _flatten_valid_keys(nested_keys, full_key)
          else
            result << "#{full_key}[#{nested_keys}]"
          end
        else
          result << (namespace ? "#{namespace}[#{key}]" : key.to_s)
        end
        result
      end.sort
    end

    def self._flatten_hash_keys(element, namespace = nil)
      element = [element] if element.is_a?(String)

      element.inject([]) do |result, (key, value)|
        full_key = (namespace ? "#{namespace}[#{key}]" : key.to_s)
        if value.is_a?(Hash)
          result += _flatten_hash_keys(value, full_key)
        elsif value.is_a?(Array)
          value.each do |item|
            result += _flatten_hash_keys(item, full_key)
          end
        else
          result << full_key
        end
        result
      end.sort
    end

    def self._remove_wildcard_keys(valid_keys, invalid_keys)
      wildcard_keys = valid_keys.select { |k| k.include? "[_any_key_]" }
      return invalid_keys if wildcard_keys.empty?
      wildcard_keys.map! { |wk| wk.sub "[_any_key_]", "" }
      invalid_keys.select do |invalid_key|
        wildcard_keys.all? do |wildcard_key|
          invalid_key.index(wildcard_key) != 0
        end
      end
    end
  end
end
