require "inventory_refresh"

RSpec.describe EmsRefresh do
  include Spec::Support::SupportsHelper

  context ".queue_refresh" do
    before do
      zone = EvmSpecHelper.local_miq_server.zone
      @ems = FactoryBot.create(:ems_vmware, :zone => zone)
    end

    it "with Ems" do
      target = @ems
      queue_refresh_and_assert_queue_item(target, [target])
    end

    it "with Ems that doesn't support refresh_ems" do
      stub_supports_not(@ems, :refresh_ems)
      described_class.queue_refresh(@ems)
      expect(MiqQueue.count).to be_zero
    end

    it "with InventoryRefresh::Target" do
      target = InventoryRefresh::Target.load(
        :manager_id  => @ems.id,
        :association => :vms,
        :manager_ref => {:ems_ref => "vm_1"},
        :options     => {:opt1 => "opt1", :opt2 => "opt2"}
      )

      queue_refresh_and_assert_queue_item(target, [target])
    end

    it "with Host" do
      target = FactoryBot.create(:host_vmware, :ext_management_system => @ems)
      queue_refresh_and_assert_queue_item(target, [target])
    end

    it "with Vm" do
      target = FactoryBot.create(:vm_vmware, :ext_management_system => @ems)
      queue_refresh_and_assert_queue_item(target, [target])
    end

    it "with Storage" do
      target = FactoryBot.create(:storage_vmware, :ext_management_system => @ems)
      queue_refresh_and_assert_queue_item(target, [target])
    end

    it "with Vm and an item already on the queue" do
      target = @ems
      queue_refresh_and_assert_queue_item(target, [target])
      target2 = FactoryBot.create(:vm_vmware, :ext_management_system => @ems)
      queue_refresh_and_assert_queue_item(target2, [target, target2])
    end
  end

  context "stopping targets unbounded growth" do
    before do
      zone = EvmSpecHelper.local_miq_server.zone
      @ems = FactoryBot.create(:ems_vmware, :zone => zone)
    end

    let(:targets) do
      targets = []
      997.times do |i|
        targets << InventoryRefresh::Target.load(
          :manager_id  => @ems.id,
          :association => :vms,
          :manager_ref => {:ems_ref => "vm_1"},
          :event_id    => i,
          :options     => {:opt1 => "opt#{i}", :opt2 => "opt2"}
        )
      end

      targets << vm_target
      targets << host_target
      targets << @ems
      targets
    end

    let(:vm_target) { FactoryBot.create(:vm_vmware, :ext_management_system => @ems) }
    let(:host_target) { FactoryBot.create(:host_vmware, :ext_management_system => @ems) }

    it "doesn't call uniq on targets if size is <= 1000" do
      described_class.queue_refresh(targets)

      expect(MiqQueue.last.data.size).to eq(1_000)
    end

    it "uniques targets if next queuing breaches size 1000" do
      described_class.queue_refresh(targets)
      described_class.queue_refresh([host_target, vm_target, @ems])

      expect(MiqQueue.last.data.size).to eq(4)
      described_class.queue_refresh(targets)
      expect(MiqQueue.last.data.size).to eq(4)
    end

    it "uniques targets if queuing breaches size 1000" do
      # We need different Vm, since targets are uniqued before queueing
      described_class.queue_refresh(targets << FactoryBot.create(:vm_vmware, :ext_management_system => @ems))

      expect(MiqQueue.last.data.size).to eq(5)
    end
  end

  context ".queue_refresh_task" do
    before do
      zone = EvmSpecHelper.local_miq_server.zone
      @ems  = FactoryBot.create(:ems_vmware, :zone => zone)
      @ems2 = FactoryBot.create(:ems_vmware, :zone => zone)
    end

    context "with a refresh already on the queue" do
      let(:target1) { @ems }
      let(:target2) { FactoryBot.create(:vm_vmware, :ext_management_system => @ems) }

      it "only creates one task" do
        described_class.queue_refresh_task(target1)
        described_class.queue_refresh_task(target2)

        expect(MiqTask.count).to eq(1)
      end

      it "returns the first task" do
        task_ids = described_class.queue_refresh_task(target1)
        task_ids2 = described_class.queue_refresh_task(target2)

        expect(task_ids.length).to  eq(1)
        expect(task_ids2.length).to eq(1)
        expect(task_ids.first).to   eq(task_ids2.first)
      end

      it "sets the queue callback correctly" do
        task_ids = described_class.queue_refresh_task(target1)
        described_class.queue_refresh_task(target2)

        queue_item = MiqQueue.find_by(:task_id => task_ids.first.to_s)
        expect(queue_item.miq_callback[:class_name]).to eq("MiqTask")
      end
    end

    context "with Vms on different EMSs" do
      let(:vm1) { FactoryBot.create(:vm_vmware, :ext_management_system => @ems) }
      let(:vm2) { FactoryBot.create(:vm_vmware, :ext_management_system => @ems2) }
      it "returns a task for each EMS" do
        task_ids = described_class.queue_refresh_task([vm1, vm2])
        expect(task_ids.length).to eq(2)
      end
    end
  end

  def queue_refresh_and_assert_queue_item(target, expected_targets)
    described_class.queue_refresh(target)
    assert_queue_item(expected_targets)
  end

  def assert_queue_item(expected_targets)
    q_all = MiqQueue.all
    expect(q_all.length).to eq(1)
    expect(q_all[0].data).to eq(expected_targets.collect { |t| [t.class.name, t.id] })
    expect(q_all[0].class_name).to eq(described_class.name)
    expect(q_all[0].method_name).to eq('refresh')
    expect(q_all[0].role).to eq("ems_inventory")
  end

  context ".get_target_objects" do
    it "array of class/ids pairs" do
      ems1 = FactoryBot.create(:ems_vmware, :name => "ems_vmware1")
      ems2 = FactoryBot.create(:ems_redhat, :name => "ems_redhat1")
      pairs = [
        [ems1.class, ems1.id],
        [ems2.class, ems2.id]
      ]

      expect(described_class.get_target_objects(pairs)).to match_array([ems1, ems2])
    end

    it "array of class/hash pairs for InventoryRefresh::Target objects" do
      ems1 = FactoryBot.create(:ems_vmware, :name => "ems_vmware1")
      ems2 = FactoryBot.create(:ems_redhat, :name => "ems_redhat1")

      target1     = InventoryRefresh::Target.load(:manager_id  => ems1.id,
                                                  :association => :vms,
                                                  :manager_ref => {:ems_ref => "vm1"})
      target2     = InventoryRefresh::Target.load(:manager_id  => ems2.id,
                                                  :association => :network_ports,
                                                  :manager_ref => {:ems_ref => "network_port_1"})
      target3     = InventoryRefresh::Target.new(:manager_id  => ems1.id,
                                                 :association => :vms,
                                                 :manager_ref => {:ems_ref => "vm2"})
      target1_dup = InventoryRefresh::Target.load(:manager_id  => ems1.id,
                                                  :association => :vms,
                                                  :manager_ref => {:ems_ref => "vm1"})
      pairs = [
        [target1.class, target1.id],
        [target2.class, target2.id],
        [target3.class, target3.id],
        [target1_dup.class, target1_dup.id],
      ]

      expect(described_class.get_target_objects(pairs).map(&:dump)).to match_array([target1, target2, target3].map(&:dump))
    end
  end

  context ".refresh" do
    it "accepts VMs" do
      ems = FactoryBot.create(:ems_vmware, :name => "ems_vmware1")
      vm1 = FactoryBot.create(:vm_vmware, :name => "vm_vmware1", :ext_management_system => ems)
      vm2 = FactoryBot.create(:vm_vmware, :name => "vm_vmware2", :ext_management_system => ems)
      expect(ManageIQ::Providers::Vmware::InfraManager::Refresher).to receive(:refresh) do |args|
        # Refresh code doesn't care about args order so neither does the test
        # TODO: use array_including in rspec 3
        (args - [vm2, vm1]).empty?
      end

      EmsRefresh.refresh([
        [vm1.class, vm1.id],
        [vm2.class, vm2.id],
      ])
    end

    it "ignores an EMS-less (archived) VM" do
      ems = FactoryBot.create(:ems_vmware, :name => "ems_vmware1")
      vm1 = FactoryBot.create(:vm_vmware, :name => "vm_vmware1", :ext_management_system => ems)
      vm2 = FactoryBot.create(:vm_vmware, :name => "vm_vmware2", :ext_management_system => nil)
      expect(ManageIQ::Providers::Vmware::InfraManager::Refresher).to receive(:refresh).with([vm1])
      EmsRefresh.refresh([
        [vm1.class, vm1.id],
        [vm2.class, vm2.id],
      ])
    end
  end

  describe '.queue_merge' do
    let(:ems) { FactoryBot.create(:ems_vmware, :name => "ems_vmware1") }
    let(:vm)  { FactoryBot.create(:vm_vmware, :name => "vm_vmware1", :ext_management_system => ems) }

    it 'sends the command to queue' do
      EmsRefresh.queue_merge([vm.class.to_s, vm.id], ems)
      expect(MiqQueue.count).to eq(1)
    end

    it 'raises arg error if passed an object' do
      expect { EmsRefresh.queue_merge([vm], ems) }.to raise_error(ArgumentError)
    end

    context "task creation" do
      before do
        @miq_task = FactoryBot.create(:miq_task)
        allow(EmsRefresh).to receive(:create_refresh_task).and_return(@miq_task)
      end

      it 'returns id of MiqTask linked to queued item' do
        task_id = EmsRefresh.queue_merge([vm.class.to_s, vm.id], ems, true)
        expect(task_id).to eq @miq_task.id
      end

      it 'links created task with queued item' do
        task_id = EmsRefresh.queue_merge([vm.class.to_s, vm.id], ems)
        queue_item = MiqQueue.find_by(:method_name => 'refresh', :role => "ems_inventory")
        expect(queue_item.miq_task_id).to eq task_id
      end
    end
  end
end
