# frozen_string_literal: true

module JSONAPI
  class Paginator
    def initialize(_params)
    end

    def apply(_relation, _order_options)
      # relation
    end

    def links_page_params(_options = {})
      # :nocov:
      {}
      # :nocov:
    end

    class << self
      def requires_record_count
        # :nocov:
        false
        # :nocov:
      end

      def paginator_for(paginator)
        paginator_class_name = "#{paginator.to_s.camelize}Paginator"
        paginator_class_name.safe_constantize if paginator_class_name
      end
    end
  end
end

class OffsetPaginator < JSONAPI::Paginator
  attr_reader :limit, :offset

  def initialize(params)
    parse_pagination_params(params)
    verify_pagination_params
  end

  def self.requires_record_count
    true
  end

  def apply(relation, _order_options)
    relation.offset(@offset).limit(@limit)
  end

  def links_page_params(options = {})
    record_count = options[:record_count]
    links_page_params = {}

    links_page_params['first'] = {
      'offset' => 0,
      'limit' => @limit
    }

    if @offset > 0
      previous_offset = @offset - @limit

      previous_offset = 0 if previous_offset < 0

      links_page_params['prev'] = {
        'offset' => previous_offset,
        'limit' => @limit
      }
    end

    next_offset = @offset + @limit

    unless next_offset >= record_count
      links_page_params['next'] = {
        'offset' => next_offset,
        'limit' => @limit
      }
    end

    if record_count
      last_offset = record_count - @limit

      last_offset = 0 if last_offset < 0

      links_page_params['last'] = {
        'offset' => last_offset,
        'limit' => @limit
      }
    end

    links_page_params
  end

  private

  def parse_pagination_params(params)
    if params.nil?
      @offset = 0
      @limit = JSONAPI.configuration.default_page_size
    elsif params.is_a?(ActionController::Parameters)
      validparams = params.permit(:offset, :limit)

      @offset = validparams[:offset] ? validparams[:offset].to_i : 0
      @limit = validparams[:limit] ? validparams[:limit].to_i : JSONAPI.configuration.default_page_size
    else
      fail JSONAPI::Exceptions::InvalidPageObject.new
    end
  rescue ActionController::UnpermittedParameters => e
    raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
  end

  def verify_pagination_params
    if @limit < 1
      fail JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit)
    elsif @limit > JSONAPI.configuration.maximum_page_size
      fail JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit,
                                                     detail: "Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
    end

    if @offset < 0
      fail JSONAPI::Exceptions::InvalidPageValue.new(:offset, @offset)
    end
  end
end

class PagedPaginator < JSONAPI::Paginator
  attr_reader :size, :number

  def initialize(params)
    parse_pagination_params(params)
    verify_pagination_params
  end

  def self.requires_record_count
    true
  end

  def calculate_page_count(record_count)
    (record_count / @size.to_f).ceil
  end

  def apply(relation, _order_options)
    offset = (@number - 1) * @size
    relation.offset(offset).limit(@size)
  end

  def links_page_params(options = {})
    record_count = options[:record_count]
    page_count = calculate_page_count(record_count)

    links_page_params = {}

    links_page_params['first'] = {
      'number' => 1,
      'size' => @size
    }

    if @number > 1
      links_page_params['prev'] = {
        'number' => @number - 1,
        'size' => @size
      }
    end

    unless @number >= page_count
      links_page_params['next'] = {
        'number' => @number + 1,
        'size' => @size
      }
    end

    if record_count
      links_page_params['last'] = {
        'number' => page_count == 0 ? 1 : page_count,
        'size' => @size
      }
    end

    links_page_params
  end

  private

  def parse_pagination_params(params)
    if params.nil?
      @number = 1
      @size = JSONAPI.configuration.default_page_size
    elsif params.is_a?(ActionController::Parameters)
      validparams = params.permit(:number, :size)

      @size = validparams[:size] ? validparams[:size].to_i : JSONAPI.configuration.default_page_size
      @number = validparams[:number] ? validparams[:number].to_i : 1
    else
      @size = JSONAPI.configuration.default_page_size
      @number = params.to_i
    end
  rescue ActionController::UnpermittedParameters => e
    raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
  end

  def verify_pagination_params
    if @size < 1
      fail JSONAPI::Exceptions::InvalidPageValue.new(:size, @size)
    elsif @size > JSONAPI.configuration.maximum_page_size
      fail JSONAPI::Exceptions::InvalidPageValue.new(:size, @size,
                                                     detail: "size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
    end

    if @number < 1
      fail JSONAPI::Exceptions::InvalidPageValue.new(:number, @number)
    end
  end
end
