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
