require 'thor'
require 'debci'
require 'debci/amqp'
require 'debci/job'

module Debci
  class Drain
    class LifeCycle
      def initialize(life_cycle = 10)
        @mutex = Mutex.new
        @timestamp = Time.now
        @life_cycle = life_cycle
        @expired = false
      end

      def retain
        @mutex.synchronize do
          @timestamp = Time.now
        end
      end

      def expire!
        @mutex.synchronize do
          @expired = true
        end
      end

      def expired?
        @mutex.synchronize do
          @expired || (Time.now - @timestamp > @life_cycle)
        end
      end
    end

    def run
      life_cycle = LifeCycle.new
      seen = {}

      channel = Debci::AMQP.amqp_channel

      queues.each do |queue|
        queue.subscribe(manual_ack: true) do |delivery_info, _properties, payload|
          job = get_job(payload)
          next unless job

          if discard_job?(job)
            # acknowledge the message so it vanishes from the queue
            channel.acknowledge(delivery_info.delivery_tag, false)
            life_cycle.retain
          elsif seen.key?(job.run_id)
            # If we've seen this job before, this means we went over the
            # entire queue so it's time to stop
            life_cycle.expire!
          else
            # In the first time we see a given job, reject the message and
            # send it back to the queue so a worker can pick it up later
            channel.reject(delivery_info.delivery_tag, true)
            seen[job.run_id] = true
          end
        end
      end

      sleep 1 until life_cycle.expired?
    end

    def queues
      combinations = Debci.config.arch_list.map do |arch|
        Debci.config.backend_list.map do |backend|
          [arch, backend]
        end
      end.flatten(1)

      existing = combinations.select do |arch, backend|
        Debci::AMQP.queue_exists?(arch, backend)
      end

      existing.map do |arch, backend|
        Debci::AMQP.get_queue(arch, backend)
      end
    end

    def get_job(payload)
      run_id_param = payload.split.find { |part|  part =~ /^run-id:/ }
      return nil unless run_id_param

      run_id = run_id_param.split(":").last.to_i
      return nil unless run_id

      Debci::Job.includes(:package).find(run_id)
    end

    def discard_job?(job)
      if job.status.nil?
        return false
      end

      Debci.log("DROP ##{job.run_id} (#{job.package.name} #{job.suite}/#{job.arch}/#{job.backend})")
      true
    end

    class CLI < Thor
      desc 'run', 'Drains cancelled jobs from the queue'
      def start
        Drain.new.run
      end
      default_task :start
    end
  end
end