module Dunlop::TargetFileModel
  extend ActiveSupport::Concern
  extend Memoist

  included do
    include Dunlop::Loggable
    has_many :downloads, class_name: 'Dunlop::FileDownload', as: :file
    attr_accessor :working_file, :original_file
    attr_accessor :resource

    mount_uploader :original_file, Dunlop::TargetFileUploader
    # used by dunlop-file_transfer
    def default_file
      original_file
    end

    state_machine initial: :new do
      before_transition on: :generating,           do: :set_start_time
      before_transition on: [:completed,:failed],  do: :set_end_time

      event(:scheduled)  { transition new:        :scheduled  }
      event(:generating) { transition scheduled:  :generating }
      event(:completed)  { transition generating: :completed  }
      event(:failed)     { transition any =>      :failed     }
    end
  end

  def set_start_time
    self.start_time = Time.zone.now
  end

  def set_end_time
    self.end_time = Time.zone.now
  end

  def duration
    if start_time and end_time
      end_time - start_time
    elsif start_time
      Time.zone.now - start_time
    else
      0
    end
  end

  def generate_and_post_process_file
    self.working_file = new_working_file
    generate_file
    post_process_file
    attach_target_file
    post_generate_hook
  ensure
    working_file.close! rescue SystemCallError # container permission issue
  end

  def local_target_filename
    result = File.join(Rails.application.config.working_path, target_basename)
    gzip_file? ? result + ".gz" : result
  end

  # TODO: consider builder.file_name if builder option is used by default.
  # This will synchronize file names between regular controller downloads and
  # target file creation.
  def target_basename
    "#{self.class.name.demodulize.underscore}-#{date_number}.csv"
  end

  def post_generate_hook
    # hook to implement post generation actions like sending by email and/or to other systems by means of web call and/or to create a transfer instance
  end

  # Implement in Subclass. Example:
  #   CsvBuilder::BladiBla.new(resource).data
  def generate_file
    working_file.puts builder.data
  end

  def builder
    @builder ||= self.class.builder_class.new(resource, builder_options)
  end

  # can be implemented by specific builder classes, for customized builder behaviour
  def builder_options
    {}
  end

  def header_line_count
    builder.headers.present? ? 1 : 0
  end

  def post_process_file
    self.working_file.close
    self.number_of_records = line_count
    self.number_of_records -= header_line_count
    gzip_file if gzip_file?
    self.size = working_file_size
    self.save!
  end

  def attach_target_file
    %x[cp #{Shellwords.escape(working_file.path)} #{Shellwords.escape(local_target_filename)}]
    self.original_file = File.open(local_target_filename, 'r')
    self.save!
  ensure
    FileUtils.rm_f(local_target_filename)
  end

  def new_working_file
    local_working_file = Tempfile.new("target_file", Rails.application.config.working_path)
    FileUtils.chmod(0666, local_working_file.path)
    local_working_file
  end

  def gzip_file?
    false #override in subclass
  end

  def gzip_file
    return if Dunlop::FileZip.mime_type(working_file.path) == "application/x-gzip" # no double action and working file replacement
    local_working_file = new_working_file
    Dunlop::FileZip.gzip(working_file.path,local_working_file.path)
    working_file.unlink rescue SystemCallError # container permission issue
    self.working_file = local_working_file
  end

  def line_count
    %x[wc -l #{Shellwords.escape(working_file.path)}].to_i
  end

  def sql_template_path
    Rails.root.join("app/models/target_file/sql_templates")
  end

  def prepare_working_file_for_sql_generation
    working_file.close
    FileUtils.rm_f(working_file.path)
  end

  def execute_generation_process
    with_nested_logger_and_catch_failed do
      scheduled! unless scheduled?
      generating!
      generate_and_post_process_file
      completed!
      self # return generated file instance
    end
  end

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

  module ClassMethods
    def factory(attrs)
      attrs ||= {}
      klass = classes.find { |c| c.to_s == attrs[:sti_type].to_s } || self
      klass.new(attrs)
    end

    def classes
      @classes ||= class_names.map(&:constantize)
    end

    def class_names
      target_file_names.map{|tfn| tfn.is_a?(Class) ? tfn.name : "#{self.name}::#{tfn.to_s.camelize}"}
    end

    def setup_target_files(target_file_names)
      @target_file_names = target_file_names
    end

    def target_file_names
      @target_file_names
    end

    def latest
      completed.last
    end

    def completed
      where(state: 'completed')
    end

    def activate_dunlop!
    end

    def generate!(resource = nil)
      new_record = create
      new_record.resource = resource if resource.present?
      new_record.execute_generation_process
      new_record
    end

    def cleanup
      #noop, can't make assumptions here
    end

    def create_from_source_file!(source_file)
      target_file = self.new(
        working_file: File.open(source_file.original_file.path, 'r'),
        state: 'generating',
        start_time: Time.now,
        number_of_records: source_file.number_of_records,
      )
      target_file.size = File.size(target_file.working_file.path)
      target_file.attach_target_file
      target_file.completed!
      target_file
    end

    def builder_class
      "#{name}CsvBuilder".safe_constantize || ::CsvBuilder
    end
  end

  private

  def working_file_size
    File.size(working_file.path)
  end

end
