namespace :katello do

desc <<-DESCRIPTION
Report on hypervisors without VDC subscriptions and guests consuming virtual entitlements.  Returns non-negative exit code if any issue is found.
Options:
  CSV - Output in csv format.  Example:  CSV=true
  LIMIT - only execute on a specified number of hosts (useful when re-running with different options).  Example:  LIMIT=500
  IGNORE - ignore one or more pools, separated via a pipe character '|'.
    Example:  IGNORE="Red Hat Enterprise Linux Server with Smart Management, Standard (Physical or Virtual Nodes)|Some Other Pool"
DESCRIPTION
task :virt_who_report => ["environment", "check_ping"] do
  class ProblemItem
    attr_accessor :host, :hypervisor, :message

    def initialize(hash)
      hash.each { |k, v| instance_variable_set("@#{k}", v) }
    end

    def hostname
      host.try(:name)
    end

    def host_id
      host.try(:id)
    end
  end

  def physical_entitlements(entitlements)
    entitlements.select do |ent|
      attributes = ent['pool']['productAttributes'] + ent['pool']["attributes"]
      virtual = attributes.find { |att| att['name'] == 'virt_only' || att['name'] == 'virtual' }.try(:[], 'value') == 'true'
      ent['pool']['quantity'] != -1 && virtual != true
    end
  end

  #version specific methods
  def total_count
    Katello::Host::SubscriptionFacet.count
  end

  def for_each_item_with_index
    Katello::Host::SubscriptionFacet.find_each.with_index do |item, index|
      yield(item, index)
    end
  end

  def candlepin_attributes(item)
    item.candlepin_consumer.consumer_attributes
  end

  def entitlements(item)
    item.candlepin_consumer.entitlements
  end

  def hypervisor_for(item)
    item.candlepin_consumer.virtual_host
  end

  def host_for(item)
    item.host
  end

  #output writing
  def output_txt(directory, hypervisors, guests, errors)
    write_to_file(File.join(directory, 'hypervisors.txt'), hypervisors) do |file, problem|
      file.write("#{problem.hostname} (#{problem.host_id}): #{problem.message}\n")
    end

    write_to_file(File.join(directory, 'guests.txt'), guests) do |file, problem|
      hypervisor_msg = "Could not identify hypervisor."
      hypervisor_msg = "Hypervisor identified as #{problem.hypervisor.name} (#{problem.hypervisor.id})." if problem.hypervisor
      file.write("#{problem.hostname} (#{problem.host_id}): #{problem.message} #{hypervisor_msg}\n")
    end

    write_to_file(File.join(directory, 'errors.txt'), errors) do |file, problem|
      file.write("#{problem.hostname} (#{problem.host_id}): #{problem.message}\n")
    end
  end

  def output_csv(directory, hypervisors, guests, errors)
    hypervisor_header = ['Name', 'Id', 'Problem reason']
    write_to_file(File.join(directory, 'hypervisors.csv'), hypervisors, hypervisor_header) do |file, problem|
      file << [problem.hostname, problem.host_id, problem.message]
    end

    guests_header = ['Name', 'Id', 'Problem reason', 'Hypervisor name', 'Hypervisor id']
    write_to_file(File.join(directory, 'guests.csv'), guests, guests_header) do |file, problem|
      file << [problem.hostname, problem.host_id, problem.message, problem.hypervisor.try(:name), problem.hypervisor.try(:id)]
    end

    errors_header = ['Name', 'Id', 'Error']
    write_to_file(File.join(directory, 'errors.csv'), errors, errors_header) do |file, problem|
      file << [problem.hostname, problem.host_id, problem.message]
    end
  end

  def write_to_file(filename, items, csv_header = nil)
    file_obj = csv_header ? CSV : File
    file_obj.open(filename, 'w') do |file|
      file << csv_header if csv_header
      items.each do |item|
        yield(file, item)
      end
    end
  end

  total = total_count
  guests = []
  hypervisors = []
  errors = []
  ignored_pools = ENV['IGNORE'] ? ENV['IGNORE'].split('|') : []

  for_each_item_with_index do |item, index|
    break if ENV['LIMIT'] && ENV['LIMIT'].to_i < index

    puts "#{index + 1}/#{total}"
    begin
      consumer_attributes = candlepin_attributes(item)
      entitlements = entitlements(item)
      if consumer_attributes['type']['label'] == 'hypervisor' && entitlements.empty?
        hypervisors << ProblemItem.new(:host => host_for(item), :message => "Has no entitlements")
      elsif consumer_attributes['facts']['virt.is_guest'].try(:to_s) == 'true'
        physical_ents = physical_entitlements(entitlements).select { |ent| !ignored_pools.include?(ent) }

        if physical_ents.count > 0
          names = physical_ents.map { |ent| ent['pool']['productName'] }.join(',')
          guests << ProblemItem.new(:host => host_for(item), :message => "Has physical entitlements: #{names}.", :hypervisor => hypervisor_for(item))
        end
      end
    rescue StandardError => e
      errors << ProblemItem.new(:host => host_for(item), :message => e.to_s)
    end
  end

  directory = Dir.mktmpdir('virt_who_report')
  if hypervisors.any? || guests.any? || errors.any?
    puts "#{hypervisors.count} hypervisor issues found."
    puts "#{guests.count} guest issues found."
    puts "#{errors.count} errors encountered."
    puts "Saving results to #{directory}/."

    if ENV['CSV'] && ENV['CSV'] == 'true'
      output_csv(directory, hypervisors, guests, errors)
    else
      output_txt(directory, hypervisors, guests, errors)
    end
    exit(-1)
  else
    puts "No issues found."
  end
end

end