RSpec.describe VimPerformanceState do
  let(:capture_time) { Time.now.utc.beginning_of_hour }

  describe ".capture" do
    it "uses now" do
      host = FactoryBot.create(:host)
      state = VimPerformanceState.capture(host)

      expect(state.timestamp).to be_within(1.minute).of(Time.now.utc.beginning_of_hour)
      expect(state.capture_interval).to eq(3600) # 1.hour
    end
  end

  describe "#capture_assoc_ids" do
    it "captures running containers" do
      container_image = FactoryBot.create(:container_image)
      containers = create_past_present_future(:container, {:container_image => container_image})

      state = VimPerformanceState.capture(container_image)
      expect(state.get_assoc(:containers).map(&:to_i)).to match_array(containers.map(&:id))
      expect(state.containers.map(&:id)).to match_array(containers.map(&:id))
    end
  end

  describe "#containers" do
    let(:capture_time) { 5.hours.ago.utc.beginning_of_hour }

    it "doesn't return records that are archived before the timestamp" do
      container_image = FactoryBot.create(:container_image)
      containers, other_containers = create_past_present_future_both(:containers, {:container_image => container_image})

      state_data = {:assoc_ids => {:containers => {:on => (containers + other_containers).map(&:id)}}}
      actual = described_class.new(:timestamp => capture_time, :state_data => state_data)
      expect(actual.containers).to match_array(containers)
    end

    it "handles pruned ids" do
      container_image = FactoryBot.create(:container_image)
      containers = FactoryBot.create_list(:container, 1, :container_image => container_image)

      state_data = {:assoc_ids => {:containers => {:on => (containers.map(&:id) + deleted_record_ids(containers))}}}
      actual = described_class.new(:timestamp => capture_time, :state_data => state_data)
      expect(actual.containers).to match_array(containers)
    end
  end

  describe "#container_groups" do
    # picking a date in the past to make sure active scope doesn't mess things up
    let(:capture_time) { 5.hours.ago.utc.beginning_of_hour }

    it "doesn't return records that are archived before the timestamp" do
      container_project = FactoryBot.create(:container_project)
      container_groups, other_container_groups = create_past_present_future_both(:container_group, :container_project => container_project)

      state_data = {:assoc_ids => {:container_groups => {:on => (container_groups + other_container_groups).map(&:id)}}}
      actual = described_class.new(:timestamp => capture_time, :state_data => state_data)
      expect(actual.container_groups).to match_array(container_groups)
    end

    it "handles pruned ids" do
      container_project = FactoryBot.create(:container_project)
      container_groups = FactoryBot.create_list(:container_group, 1, :container_project => container_project)

      state_data = {:assoc_ids => {:container_groups => {:on => (container_groups.map(&:id) + deleted_record_ids(container_groups))}}}
      actual = described_class.new(:timestamp => capture_time, :state_data => state_data)
      expect(actual.container_groups).to match_array(container_groups)
    end
  end

  describe "#vm_count_total" do
    it "will return the total vms regardless of mode" do
      state_data = {:assoc_ids => {:vms => {:on => [1], :off => [2]}}}
      actual = described_class.new(:state_data => state_data)
      expect(actual.vm_count_total).to eq(2)
    end
  end

  describe "#host_count_total" do
    it "will return the total hosts regardless of mode" do
      state_data = {:assoc_ids => {:hosts => {:on => [1], :off => [2]}}}
      actual = described_class.new(:state_data => state_data)
      expect(actual.host_count_total).to eq(2)
    end
  end

  describe "#host_sockets" do
    it "captures the host sockets when given a host" do
      hardware = FactoryBot.create(:hardware, :cpu_sockets => 2)
      host = FactoryBot.create(:host, :hardware => hardware)
      state = VimPerformanceState.capture(host)

      expect(state.state_data).to include(:host_sockets => 2)
      expect(state.host_sockets).to eq(2)
    end

    it "captures the rolled up sockets when given a cluster" do
      hardware_1 = FactoryBot.build(:hardware, :cpu_sockets => 2)
      hardware_2 = FactoryBot.build(:hardware, :cpu_sockets => 4)
      host_1 = FactoryBot.build(:host, :hardware => hardware_1)
      host_2 = FactoryBot.build(:host, :hardware => hardware_2)
      cluster = FactoryBot.create(:ems_cluster, :hosts => [host_1, host_2])
      state = VimPerformanceState.capture(cluster)

      expect(state.host_sockets).to eq(6)
    end
  end

  describe '#allocated_disk_types' do
    let(:ssd_size) { 1_234 }
    let(:hdd1_size) { 5_678 }
    let(:hdd2_size) { 9_101 }
    let(:ssd_volume) { FactoryBot.create(:cloud_volume_openstack, :volume_type => 'ssd') }
    let(:ssd_disk) { FactoryBot.create(:disk, :size => ssd_size, :backing => ssd_volume) }
    let(:hdd_volume) { FactoryBot.create(:cloud_volume_openstack, :volume_type => nil) }
    let(:hdd1_disk) { FactoryBot.create(:disk, :size => hdd1_size, :backing => hdd_volume) }
    let(:hdd2_disk) { FactoryBot.create(:disk, :size => hdd2_size) }
    let(:hardware) { FactoryBot.create(:hardware, :disks => [ssd_disk, hdd1_disk, hdd2_disk]) }
    let(:vm) { FactoryBot.create(:vm_openstack, :hardware => hardware) }

    subject { vm.perf_capture_state.allocated_disk_types }

    it { is_expected.to match('ssd' => ssd_size, 'unclassified' => hdd1_size + hdd2_size) }

    context 'when disk size is nil' do
      let!(:hdd1_disk) { FactoryBot.create(:disk, :size => nil, :backing => hdd_volume) }

      it { is_expected.to match('ssd' => ssd_size, 'unclassified' => hdd2_size) }

      context "when all disk's sizes are nil" do
        let!(:hdd2_disk) { FactoryBot.create(:disk, :size => nil, :backing => hdd_volume) }
        let!(:ssd_disk)  { FactoryBot.create(:disk, :size => nil, :backing => ssd_volume) }

        it { is_expected.to be_empty }
      end
    end
  end

  describe "#fetch_assoc_records (private)" do
    it "fetches infra record" do
      host = FactoryBot.create(:host)
      vms = FactoryBot.create_list(:vm, 1, :host => host)

      results = fetch_records(host, :vms)
      expect(results).to eq(vms)
    end

    it "fetches active container objects" do
      container_image = FactoryBot.create(:container_image, :created_on => 6.hours.ago)
      containers = create_past_present_future(:container, {:container_image => container_image})

      results = fetch_records(container_image, :containers, capture_time)
      expect(results).to eq(containers)

      state = VimPerformanceState.capture(container_image)
      expect(state.get_assoc(:containers).map(&:to_i)).to match_array(containers.map(&:id))
    end
  end

  private

  def create_past_present_future(factory, params)
    create_past_present_future_both(factory, params).first
  end

  def create_past_present_future_both(factory, params)
    # not deleted is captured
    ret = FactoryBot.create_list(factory, 1, params)
    # deleted before is not captured
    old = FactoryBot.create_list(factory, 1, params.merge(:deleted_on => (capture_time - 2.hours)))
    # deleted later is captured
    ret << FactoryBot.create(factory, params.merge(:deleted_on => (capture_time + 2.hours)))

    [ret, old]
  end

  # these represent records that have been pruned from the database
  # so a find(id) will not be found
  def deleted_record_ids(objs)
    [objs.last.id + 1]
  end

  def fetch_records(rec, association, timestamp = Time.now.utc)
    vps = VimPerformanceState.new(:timestamp => timestamp)
    vps.send(:fetch_assoc_records, rec, association)
  end
end
