require 'httparty'

class QueueMonitorJob
  include Sidekiq::Worker

  def perform(queue_id)
    logger.info "Beginning to monitor queue id: #{queue_id}"
    poll_queue_api(queue_id)
  end

  private

  def poll_queue_api(queue_id, max_attempts: 500, wait_amount: 10, max_error_amount: 10)
    attempt = 1
    error_count = 0 # Track how many errors we get so that we can potentially abort early
    record = SmocaBuild.find_by(queue_id: queue_id.to_s)

    if record.nil?
      # Make sure the record can be pulled from the DB...
      raise "Encountered an issue looking up build by Queue ID: #{queue_id}"
    end

    while (attempt <= max_attempts) && (error_count < max_error_amount)
      body = get_build_queue(queue_id)

      if body
        if id_assigned?(body)
          # Build is unblocked and has an ID. Update and finish the job by breaking from loop.
          build_id = parse_build_id(body)
          logger.info "Queue ID #{queue_id} is unblocked"

          if build_id
            record.update_attributes!(build_id: build_id)
            logger.info "Updated Record ID #{record.id} with build number: #{build_id}"
            break
          else
            # Not sure cases this would happen, but just incase...
            logger.error "Encountered an issue. Record: #{record}. Build ID: #{build_id}"
            error_count += 1
          end
        elsif build_cancelled?(body)
          logger.info "Queue ID #{queue_id} was cancelled. Updating #{record.id}"
          record.update_attributes!(result: 'ABORTED', cancelled: true, pending: false)
          break
        else
          # No ID assigned, and it hasn't been cancelled. Must be blocked...
          message = "Queue ID #{queue_id} is blocked."
          if parse_reason(body)
            # Append the reason if one exists
            message += " Reason: #{parse_reason(body)}"
          end
          logger.info message
        end
      else
        # API Issues?
        logger.error "Encountered a problem retrieving the body of Queue ID #{queue_id}"
        error_count += 1
      end

      sleep(wait_amount)
      attempt += 1
    end
  end

  def get_build_queue(queue_id)
    jenkins_username = Rails.application.secrets.jenkins_username || ENV['JENKINS_USERNAME']
    jenkins_token    = Rails.application.secrets.jenkins_token    || ENV['JENKINS_TOKEN']
    auth = { :username => jenkins_username, :password => jenkins_token }

    endpoint = "https://jenkins.internal.justin.tv/queue/item/#{queue_id}/api/json"
    response = HTTParty.get(endpoint, :basic_auth => auth )

    if response.code == 200
      return JSON.parse(response.body)
    else
      logger.error "Received unexpected Response Code #{response.code} on endpoint: #{endpoint}"
      return nil
    end
  end

  # @return Boolean If the build is currently blocked
  def build_blocked?(body)
    return body['blocked']
  end

  # @return Boolean Whether a build ID has been assigned or not
  # Jenkins API doesn't include number in the hash if it hasn't been assigned, so using dig
  def id_assigned?(body)
    return !!body.dig('executable', 'number')
  end

  # @return Int The Build ID that was assigned
  # Jenkins API doesn't include number in the hash if it hasn't been assigned, so using dig
  def parse_build_id(body)
    return body.dig('executable', 'number')
  end

  # @return Boolean Whether a build has been cancelled or not
  def build_cancelled?(body)
    if body.has_key?('cancelled') # Jenkins API usually only includes this key if it was indeed cancelled
      return body['cancelled']
    else
      return false
    end
  end

  # @return String The reason as to why a build is blocked
  def parse_reason(body)
    return body.dig('why')
  end
end
