require "csv"
module Dunlop::CsvBuilder
  extend ActiveSupport::Concern

  included do
    attr_reader :resource, :options, :csv_options, :controller
    class_attribute :default_csv_options
    self.default_csv_options = {}
  end

  def initialize(resource, options = {})
    @resource, @options = resource, options
    @csv_options = default_csv_options.merge(options.slice(:col_sep, :row_sep, :encoding, :field_size_limit, :quote_char, :force_quotes))
    @controller = options[:controller]
  end

  def file_name
    options[:file_name] || "#{Rails.application.config.application_name}-#{date_number}-data.csv"
  end

  def headers
    []
  end

  def for_each_serialized_row(&block)
    iterate_over = iteration_resource
    iterate_over = Array.wrap(iterate_over) unless iterate_over.respond_to?(:find_each_ordered) || iterate_over.respond_to?(:each) # allow resource to be a record using the index's csv builder
    iteration_method = iterate_over.respond_to?(:find_each_ordered) ? :find_each_ordered : :each
    iterate_over.public_send(iteration_method) do |*args|
      block.call serialize_row(*args)
    end
  end

  def serialize_row(record)
    [record.id]
  end

  def data
    # use #to_csv for base_class builders if present
    # in custom class implement"
    #   def data
    #     resource.to_csv
    #   end
    # since Arrays implement #to_csv, but we do not want to use it in this case we explicitly exclude it
    return resource.to_csv if self.class == CsvBuilder and resource.present? and resource.respond_to?(:to_csv) and !resource.is_a?(Array)

    # try iteration otherwise
    str = CSV.generate csv_options do |csv|
      csv << headers if headers.present?
      for_each_serialized_row do |serialized_row|
        csv << serialized_row
      end
    end
    str
  end

  def date_number
    Date.current.to_s(:number)
  end

  # This method can be overridden in order to use another iteration source than the resource eg:
  #   resouce = batch
  #   def iteration_resource
  #     resource.workflow_instances
  #   end
  def iteration_resource
    resource
  end

  #TODO: this is a separate include, not part of the core CsvBuilder object
  def workflow_instances_in_batch
    resource.workflow_instances.for_filtered_export
  end

  # Do a lookup on a record of an attribute and map the value to the
  # csv required version if appliccable. At the moment booleans are
  # converted to zeros and ones
  def record_attribute(record, attribute_name)
    result = record.public_send(attribute_name)
    case result
    when TrueClass then 1
    when FalseClass then 0
    when String then result.strip.gsub(/\r\n?|\n/, " ")
    when DayTimeBase then result.number
    else result
    end
  end

  # Make some options available as methods
  %i[action_name controller_path].each do |option_method|
    define_method option_method do
      options[option_method]
    end
  end

  module ClassMethods
    # find the csv builder for the given route spec
    def find_for_route(route_spec)
      find_for_route_lookup_order(route_spec).lazy.map(&:safe_constantize).find(&:itself)
    end

    # separate method since this is the important method to test, so extracted from the main method
    def find_for_route_lookup_order(route_spec)
      controller_path, action_name = route_spec.split('#')

      controller_parts = controller_path.split('/')
      if controller_parts.size < 2
        parts = controller_parts.map(&:classify)
      else
        parts = controller_parts[0..-2].map(&:camelize)
        parts << controller_parts.last.classify
      end

      # Create a lookup tree that uses all the namespaces, followed by the actoin name
      # and ending with CsvBuilder for consitancy. Aka: if the route is:
      #   workflow_instances/scenario1s#show
      # the lookup_order should be:
      #   ["WorkflowInstanceBatch::Scenario1::ShowCsvBuilder", "WorkflowInstanceBatch::ShowCsvBuilder"]
      action_postfix = "::#{action_name.camelize}CsvBuilder"
      parts.map.with_index{|p, i| parts[0..(i - 1)].join('::') + action_postfix}
    end
  end
end
