module Actions
  # This action plans proxy tasks in batches.
  # It needs to be manually notified about the next batch being available by sending a TriggerNextBatch event.
  #
  # The ProxyAction needs to be planned with `:use_batch_triggering => true` to activate the feature
  class TriggerProxyBatch < Base
    TriggerNextBatch = Algebrick.type do
      fields! batches: Integer
    end
    TriggerLastBatch = Algebrick.atom

    def run(event = nil)
      case event
      when nil
        if output[:planned_count]
          check_finish
        else
          init_counts and suspend
        end
      when TriggerNextBatch
        trigger_remote_tasks_batches(event.batches) and suspend
      when TriggerLastBatch
        trigger_remote_tasks_batch and on_finish
      when ::Dynflow::Action::Skip
        # do nothing
      end
    end

    def trigger_remote_tasks_batches(amount = 1)
      amount.times { trigger_remote_tasks_batch }
    end

    def trigger_remote_tasks_batch
      # Find the tasks in batches, order them by proxy_url so we get all tasks
      # to a certain proxy "close to each other"
      batch = remote_tasks.pending.order(:proxy_url, :id).first(batch_size)
      # Group the tasks by operation, in theory there should be only one operation
      batch.group_by(&:operation).each do |operation, group|
        ForemanTasks::RemoteTask.batch_trigger(operation, group)
        output[:planned_count] += group.size
      end
    rescue => e
      action_logger.warn "Could not trigger task on the smart proxy"
      action_logger.warn e
      batch.each { |remote_task| remote_task.update_from_batch_trigger({}) }
      output[:failed_count] += batch.size
    end

    def init_counts
      output[:planned_count] = 0
      output[:failed_count] = 0
    end

    def check_finish
      if output[:planned_count] + output[:failed_count] + batch_size >= input[:total_count]
        trigger_remote_tasks_batch and on_finish
      else
        suspend
      end
    end

    def done?
      output[:planned_count] + output[:failed_count] >= input[:total_count]
    end

    def remote_tasks
      task.remote_sub_tasks
    end

    def on_finish
      # nothing for now
    end

    private

    def batch_size
      input[:batch_size] || Setting['foreman_tasks_proxy_batch_size']
    end
  end
end