require 'sidekiq'

module Softwear
  module Library
    module Enqueue
      extend ActiveSupport::Concern

      class Worker
        include ::Sidekiq::Worker

        def perform(model_name, id, method, *args)
          model_name.constantize.find(id).send(method, *args)
        rescue ActiveRecord::RecordNotFound
          # Do nothing if the record was destroyed.
        end
      end

      module ClassMethods
        def enqueue(*method_names)
          if method_names.last.is_a?(Hash)
            options = method_names.pop
          else
            options = {}
          end
          raise "enqueue what?" if method_names.empty?

          # We will extend the class mod and include the instance mod.
          # This allows us to override the generated enqueue methods and
          # call `super`.
          #
          enqueue_class_mod = Module.new
          enqueue_instance_mod = Module.new

          method_names.each do |method_name|
            enqueue_class_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
              def #{method_name}(id, *args)
                find(id).#{method_name}(*args)
              end
            RUBY

            if Rails.env.production?
              enqueue_class_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
                def enqueue_#{method_name}(id, *args)
                  ::Sidekiq::Client.push(
                    'class' => ::Softwear::Library::Enqueue::Worker,
                    'args'  => [name, id, #{method_name.inspect}] + args,
                    'queue' => #{options[:queue].inspect}
                  )
                end
              RUBY

              enqueue_instance_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
                def enqueue_#{method_name}(*args)
                  self.class.enqueue_#{method_name}(id, *args)
                end
              RUBY
            else
              enqueue_class_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
                def enqueue_#{method_name}(id, *args)
                  self.#{method_name}(id, *args)
                end
              RUBY

              enqueue_instance_mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
                def enqueue_#{method_name}(*args)
                  self.#{method_name}(*args)
                end
              RUBY
            end
          end

          send :extend, enqueue_class_mod
          send :include, enqueue_instance_mod

          r = method_names.map { |m| "enqueue_#{m}".to_sym }
          r.size == 1 ? r.first : r
        end
      end

      included do
        extend ClassMethods

        def enqueue(method_name, *args)
          send("enqueue_#{method_name}", *args)
        end
      end
    end
  end
end