module OneRoster::ParseMethods
  extend ActiveSupport::Concern

  PREDICATES = ['>=', '<=', '!=', '=', '>', '<', '~']

  def bind_and_parse(data)
    data = filter_json(data) if params.key?(:filter)
    data = sort_json(data) if params.key?(:sort)
    data = field_selection(data) if params.key?(:fields)
    paginate_json(data.reject(&:blank?))
  end

  private
    def filter_json(data)
      # Raise :bad_request when no arguments supplied to params filter
      bad_request!(:invalid_filter_field) if params[:filter].nil?

      if params[:filter].include?(' AND ')
        filters_1 = filtered_variables(params[:filter].split(' AND ').first)
        filters_2 = filtered_variables(params[:filter].split(' AND ').last)
        data.select do |record|
          flag = filtered_record_selected?(filters_1, record)
          next flag if flag.blank?

          filtered_record_selected?(filters_2, record)
        end
      elsif params[:filter].include?(' OR ')
        filters_1 = filtered_variables(params[:filter].split(' OR ').first)
        filters_2 = filtered_variables(params[:filter].split(' OR ').last)
        data.select do |record|
          flag = filtered_record_selected?(filters_1, record)
          next flag if flag.present?

          filtered_record_selected?(filters_2, record)
        end
      else
        filters = filtered_variables(params[:filter])
        data.select { |d| filtered_record_selected?(filters, d) }
      end
    end

    def filtered_variables(filter)
      predicate = filter.scan(Regexp.union(PREDICATES)).first
      field, value = filter.split(predicate)
      # Raise :bad_request if no value supplied after predicate
      bad_request!(:invalid_filter_field) if value.nil?
      values = value.split(',')
      values.map! { |v| v.match(/'/) ? v.gsub("'", '') : v.to_f }
      predicate = '==' if predicate == '='
      { predicate: predicate, field: field.to_sym, value: value, values: values }
    end

    def filtered_record_selected?(filters, record)
      # Raise :bad_request status code if filter field is not present in controller props
      if endpoint_props.include?(filters[:field].to_sym).blank?
        bad_request!(:invalid_filter_field, "Invalid field: #{filters[:field]}")
      end

      return false if record[filters[:field]].nil?

      if filters[:predicate] == '~'
        field_value = if record[filters[:field]].is_a?(Symbol)
          record[filters[:field]].to_s
        else
          record[filters[:field]]
        end

        if field_value.is_a?(Array)
          field_value.each do |value|
            return true if filters[:values].include?(value)
          end
        elsif filters[:values].include?(field_value)
          return true
        end

        return false
      else
        field_value = if [String, Symbol, Date, DateTime].include?(record[filters[:field]].class)
          record[filters[:field]].to_s.to_json
        else
          record[filters[:field]]
        end

        value = if filters[:values].many?
          filters[:values]
        elsif field_value.is_a?(Array)
          [filters[:value].match(/'/) ? filters[:value].gsub("'", '') : filters[:value].to_f]
        else
          filters[:value]
        end
      end

      eval("#{field_value} #{filters[:predicate]} #{value}")
    end

    def sort_json(data)
      data.sort_by! { |v| [v[params[:sort].to_sym] ? 1 : 0, v[params[:sort].to_sym]] }
      return data unless params[:orderBy] == 'desc'

      data.reverse
    end

    def field_selection(data)
      fields = params[:fields].split(',')
      # Raise :bad_request status code if field selection field is blank
      bad_request!(:invalid_blank_selection_field) if fields.empty? || fields.any?(&:blank?)

      # set status_info_set when field selection field is not present in controller props
      if (invalid_field = fields.find { |f| endpoint_props.include?(f.to_sym).blank? })
        @status_info_set = {
          imsx_codeMajor: :success,
          imsx_severity: :warning,
          imsx_description: "Invalid field: #{invalid_field}",
          imsx_codeMinor: :invalid_selection_field
        }
        return data
      end

      data.map do |record|
        {}.tap do |props|
          fields.each { |f| props[f.to_sym] = record[f.to_sym] }
        end.compact
      end
    end

    def paginate_json(data)
      offset = (params[:offset] || 0).to_i
      limit = (params[:limit] || 100).to_i
      data[offset, limit] || []
    end
end
