module Effective
  class DatatableColumnTool
    attr_reader :datatable
    attr_reader :columns

    def initialize(datatable)
      @datatable = datatable

      if datatable.active_record_collection?
        @columns = datatable.columns.select { |_, col| col[:sql_column].present? }
      else
        @columns = {}
      end
    end

    def scoped
      @scoped ||= datatable._scopes[datatable.scope]
    end

    def searched
      @searched ||= datatable.search.select { |name, _| columns.key?(name) }
    end

    def ordered
      @ordered_column ||= columns[datatable.order_name]
    end

    def order(collection)
      return collection unless ordered.present?

      collection = if ordered[:sort_method]
        datatable.dsl_tool.instance_exec(collection, datatable.order_direction, ordered, ordered[:sql_column], &ordered[:sort_method])
      else
        order_column(collection, datatable.order_direction, ordered, ordered[:sql_column])
      end

      raise 'sort method must return an ActiveRecord::Relation object' unless collection.kind_of?(ActiveRecord::Relation)

      collection
    end

    def order_column(collection, direction, column, sql_column)
      Rails.logger.info "COLUMN TOOL: order_column #{column.to_s} #{direction} #{sql_column}"

      if column[:sql_as_column]
        collection.order("#{sql_column} #{datatable.resource.sql_direction(direction)}")
      else
        Effective::Resource.new(collection)
          .order(column[:name], direction, as: column[:as], sort: column[:sort], sql_column: column[:sql_column])
      end
    end

    def scope(collection)
      return collection unless scoped.present?

      collection.send(scoped[:name], *scoped[:args])
    end

    def search(collection)
      searched.each do |name, value|
        column = columns[name]

        collection = if column[:search_method]
          datatable.dsl_tool.instance_exec(collection, value, column, column[:sql_column], &column[:search_method])
        else
          search_column(collection, value, column, column[:sql_column])
        end

        raise 'search method must return an ActiveRecord::Relation object' unless collection.kind_of?(ActiveRecord::Relation)
      end

      collection
    end

    def search_column(collection, value, column, sql_column)
      Rails.logger.info "COLUMN TOOL: search_column #{column.to_s} #{value} #{sql_column}"

      Effective::Resource.new(collection)
        .search(column[:name], value, as: column[:as], fuzzy: column[:search][:fuzzy], sql_column: sql_column)
    end

    def paginate(collection)
      collection.page(datatable.page).per(datatable.per_page)
    end

    # Not every ActiveRecord query will work when calling the simple .count
    # Custom selects:
    #   User.select(:email, :first_name).count will throw an error
    # Grouped Queries:
    #   User.all.group(:email).count will return a Hash
    def size(collection)
      count = (collection.size rescue nil)

      case count
      when Integer
        count
      when Hash
        count.size  # This represents the number of displayed datatable rows, not the sum all groups (which might be more)
      else
        if collection.klass.connection.respond_to?(:unprepared_statement)
          collection_sql = collection.klass.connection.unprepared_statement { collection.to_sql }
          (collection.klass.connection.exec_query("SELECT COUNT(*) FROM (#{collection_sql}) AS datatables_total_count").rows[0][0] rescue 1)
        else
          (collection.klass.connection.exec_query("SELECT COUNT(*) FROM (#{collection.to_sql}) AS datatables_total_count").rows[0][0] rescue 1)
        end.to_i
      end
    end

  end
end