RSpec.describe RelationshipMixin do
  let(:test_rel_type) { "testing" }
  #      0
  #  1        2
  # 3 4    5  6  7
  #             8 9
  let(:vms_rel_tree) { {0 => [{1 => [3, 4]}, {2 => [5, 6, {7 => [8, 9]}]}]} }
  let(:vms) { build_relationship_tree(vms_rel_tree) }
  # host with no tree
  let(:host) { FactoryBot.create(:host) }

  describe "#remove_children" do
    it "handles [nil]" do
      expect { vms[3].remove_children(nil) }.to_not raise_error
    end
  end

  context "tree with relationship" do
    it "#with_relationship_type and #relationship_type" do
      expect(vms[0].relationship_type).not_to eq(test_rel_type)
      vms[0].with_relationship_type(test_rel_type) do
        expect(vms[0].relationship_type).to eq(test_rel_type)
      end

      expect(vms[0].parents).to be_empty
      expect(vms[0].children).to be_empty

      vms[0].clear_relationships_cache

      expect(vms[0].with_relationship_type(test_rel_type) { |v| v.parents.length  }).to eq(0)
      expect(vms[0].with_relationship_type(test_rel_type) { |v| v.children.length }).to eq(2)

      expect(vms[0].parents).to be_empty
      expect(vms[0].children).to be_empty
    end

    it "#parents" do
      expect(vms[0].parents).to be_empty
      recurse_relationship_tree(vms_rel_tree) do |parent, child|
        vms[child].with_relationship_type(test_rel_type) do |c|
          expect(c.parents).to eq([vms[parent]])
        end
      end
    end

    it "#children" do
      recurse_relationship_tree(vms_rel_tree) do |parent, child|
        vms[parent].with_relationship_type(test_rel_type) do |p|
          expect(p.children).to include vms[child]
        end
      end
    end

    # NOTE for understanding the next 4 contexts:
    # Objects (VMs, Hosts, etc) have associated tree nodes entries in the
    # relationships table which are linked.  If an object must reside in
    # multiple parts of the tree via having multiple parents, it will need more
    # than one associated tree node.

    context "#set_child on a new parent object" do
      before { @parent = FactoryBot.create(:vm_vmware) }

      it "with a second new object will link a new tree node for the parent to a new tree node for the child" do
        child = FactoryBot.create(:vm_vmware)
        @parent.with_relationship_type(test_rel_type) { |v| v.set_child(child) }

        assert_parent_child_structure(test_rel_type,
                                      @parent, 1, [], [child],
                                      child,   1, [@parent], []
                                     )
      end

      it "with a root object will link a new tree node for the parent to the existing tree node for the child" do
        child = vms[0]
        @parent.with_relationship_type(test_rel_type) { |v| v.set_child(child) }

        assert_parent_child_structure(test_rel_type,
                                      @parent, 1, [], [child],
                                      child,   1, [@parent], [vms[1], vms[2]]
                                     )
      end

      it "with an inner object will link a new tree node for the parent to a second new tree node for the child" do
        child = vms[1]
        @parent.with_relationship_type(test_rel_type) { |v| v.set_child(child) }

        assert_parent_child_structure(test_rel_type,
                                      @parent, 1, [], [child],
                                      child,   2, [vms[0], @parent], [vms[3], vms[4]]
                                     )
      end

      it "with a leaf object will link a new tree node for the parent to a second new tree node for the child" do
        child = vms[3]
        @parent.with_relationship_type(test_rel_type) { |v| v.set_child(child) }

        assert_parent_child_structure(test_rel_type,
                                      @parent, 1, [], [child],
                                      child,   2, [vms[1], @parent], []
                                     )
      end
    end

    context "#set_parent on a new child object" do
      before { @child = FactoryBot.create(:vm_vmware) }

      it "with a second new object will link a new tree node for the parent to a new tree node for the child" do
        parent = FactoryBot.create(:vm_vmware)
        @child.with_relationship_type(test_rel_type) { |v| v.set_parent(parent) }

        assert_parent_child_structure(test_rel_type,
                                      parent, 1, [], [@child],
                                      @child, 1, [parent], []
                                     )
      end

      it "with a root object will link the existing tree node for the parent to a new tree node for the child" do
        parent = vms[0]
        @child.with_relationship_type(test_rel_type) { |v| v.set_parent(parent) }

        assert_parent_child_structure(test_rel_type,
                                      parent, 1, [], [vms[1], vms[2], @child],
                                      @child, 1, [parent], []
                                     )
      end

      it "with an inner object will link the existing tree node for the parent to a new tree node for the child" do
        parent = vms[1]
        @child.with_relationship_type(test_rel_type) { |v| v.set_parent(parent) }

        assert_parent_child_structure(test_rel_type,
                                      parent, 1, [vms[0]], [vms[3], vms[4], @child],
                                      @child, 1, [parent], []
                                     )
      end

      it "with a leaf object will link the existing tree node for the parent to a new tree node for the child" do
        parent = vms[3]
        @child.with_relationship_type(test_rel_type) { |v| v.set_parent(parent) }

        assert_parent_child_structure(test_rel_type,
                                      parent, 1, [vms[1]], [@child],
                                      @child, 1, [parent], []
                                     )
      end
    end

    context "with a new parent object, #replace_parent" do
      before { @parent = FactoryBot.create(:vm_vmware) }

      it "on a second new object will link a new tree node for the parent to a new tree node for the child and be the only parent for the child" do
        child = FactoryBot.create(:vm_vmware)
        child.with_relationship_type(test_rel_type) { |v| v.replace_parent(@parent) }

        assert_parent_child_structure(test_rel_type,
                                      @parent, 1, [], [child],
                                      child,   1, [@parent], []
                                     )
      end

      it "on a root object will link a new tree node for the parent to the existing tree node for the child and be the only parent for the child" do
        child = vms[0]
        child.with_relationship_type(test_rel_type) { |v| v.replace_parent(@parent) }

        assert_parent_child_structure(test_rel_type,
                                      @parent, 1, [], [child],
                                      child,   1, [@parent], [vms[1], vms[2]]
                                     )
      end

      it "on an inner object will link a new tree node for the parent to the existing tree node for the child and be the only parent for the child" do
        child = vms[1]
        child.with_relationship_type(test_rel_type) { |v| v.replace_parent(@parent) }

        assert_parent_child_structure(test_rel_type,
                                      @parent, 1, [], [child],
                                      child,   1, [@parent], [vms[3], vms[4]]
                                     )
      end

      it "on a leaf object will link a new tree node for the parent to the existing tree node for the child and be the only parent for the child" do
        child = vms[3]
        child.with_relationship_type(test_rel_type) { |v| v.replace_parent(@parent) }

        assert_parent_child_structure(test_rel_type,
                                      @parent, 1, [], [child],
                                      child,   1, [@parent], []
                                     )
      end
    end

    context "#replace_parent on an inner object" do
      it "with another inner object will link the existing tree node for the parent to the existing tree node for the child and be the only parent for the child " do
        parent = vms[1]
        child = vms[2]
        child.with_relationship_type(test_rel_type) { |v| v.replace_parent(parent) }

        assert_parent_child_structure(test_rel_type,
                                      parent, 1, [vms[0]], [child, vms[3], vms[4]],
                                      child,  1, [parent], [vms[5], vms[6], vms[7]]
                                     )
      end
    end

    describe "shared ancestry/rel methods" do
      let(:service1) {  ancestry_class.create }
      let(:service2) {  ancestry_class.create }
      let(:service3) {  ancestry_class.create }
      let(:service4) {  ancestry_class.create }

      let(:ancestry_class) do
        Class.new(ApplicationRecord) do
          def self.name
            "services"
          end
          has_ancestry
          self.table_name = "services"
          include RelationshipMixin
          self.skip_relationships += ["custom"]
          self.default_relationship_type = "ems_metadata"
        end
      end

      # ems_metadata:
      #   1
      #  2
      # custom:
      #   2
      #  1
      # 3 4

      before do
        service2.update!(:parent => service1)
        service1.with_relationship_type("custom") { service1.update!(:parent => service2) }
        service3.with_relationship_type("custom") { service3.update!(:parent => service1) }
        service4.with_relationship_type("custom") { service4.update!(:parent => service1) }
      end

      it "#ancestors" do
        expect(service3.with_relationship_type("custom") { service3.ancestors }).to contain_exactly(service1, service2)
      end

      it "#ancestor_ids" do
        expect(service3.with_relationship_type("custom") { service3.ancestor_ids }).to contain_exactly(service1.id, service2.id)
      end

      it "#children" do
        expect(service2.with_relationship_type("custom") { service2.children }).to contain_exactly(service1)
      end

      it "#subtree" do
        expect(service1.with_relationship_type("custom") { service1.subtree }).to contain_exactly(service3, service4, service1)
      end

      it "#siblings" do
        expect(service4.with_relationship_type("custom") { service4.siblings }).to contain_exactly(service3, service4)
      end

      it "#sibling_ids" do
        expect(service4.with_relationship_type("custom") { service4.sibling_ids }).to contain_exactly(service3.id, service4.id)
      end

      it "#root" do
        expect(service4.with_relationship_type("custom") { service4.root_id }).to eq(service2.id)
      end

      it "#root_id" do
        expect(service4.with_relationship_type("custom") { service4.root_id }).to eq(service2.id)
      end

      it "#parent" do
        expect(service1.with_relationship_type("custom") { service1.parent }).to eq(service2)
      end

      it "#parent_id" do
        expect(service1.with_relationship_type("custom") { service1.parent_id }).to eq(service2.id)
      end

      it "#is_root?" do
        expect(service2.with_relationship_type("custom") { service2.is_root? }).to eq(true)
      end

      it "#is_root? false" do
        expect(service1.with_relationship_type("custom") { service1.is_root? }).to eq(false)
      end

      it "#descendant_ids" do
        expect(service2.with_relationship_type("custom") { service2.descendant_ids }).to contain_exactly(service1.id, service3.id, service4.id)
      end

      it "#has_siblings?" do
        expect(service4.with_relationship_type("custom") { service4.has_siblings? }).to eq(true)
      end

      it "#has_siblings? false" do
        expect(service1.with_relationship_type("custom") { service1.has_siblings? }).to eq(false)
      end

      it "#depth" do
        expect(service3.with_relationship_type("custom") { service3.depth }).to eq(2)
      end

      it "#path_ids" do
        expect(service3.with_relationship_type("custom") { service3.path_ids }).to contain_exactly(service1.id, service3.id, service2.id)
      end

      it "#child_ids" do
        expect(service2.with_relationship_type("custom") { service2.child_ids }).to eq([service1.id])
      end

      it "#is_childless?" do
        expect(service4.with_relationship_type("custom") { service4.is_childless? }).to eq(true)
      end

      it "#is_childless? false" do
        expect(service2.with_relationship_type("custom") { service2.is_childless? }).to eq(false)
      end

      it "#descendants" do
        expect(service2.with_relationship_type("custom") { service2.descendants }).to contain_exactly(service1, service3, service4)
      end

      it "#subtree_ids" do
        expect(service1.with_relationship_type("custom") { service1.subtree_ids }).to contain_exactly(service1.id, service3.id, service4.id)
      end

      it "#is_only_child?" do
        expect(service1.with_relationship_type("custom") { service1.is_only_child? }).to eq(true)
      end

      it "#is_only_child? false" do
        expect(service3.with_relationship_type("custom") { service3.is_only_child? }).to eq(false)
      end

      it "#has_children?" do
        expect(service2.with_relationship_type("custom") { service2.has_children? }).to eq(true)
      end

      it "#has_children? false" do
        expect(service4.with_relationship_type("custom") { service4.has_children? }).to eq(false)
      end

      it "#is_ancestor_of?" do
        expect(service2.with_relationship_type("custom") { service2.is_ancestor_of?(service3) }).to eq(true)
      end

      it "#is_descendant_of?" do
        expect(service3.with_relationship_type("custom") { service3.is_descendant_of?(service1) }).to eq(true)
      end

      it "#remove_children" do
        expect(service1.with_relationship_type("custom") { service1.remove_children(service4) }).to eq([service4])
        expect(service1.with_relationship_type("custom") { service1.children }).to contain_exactly(service3)
      end

      it "#add_children" do
        service5 = ancestry_class.create
        expect(service1.with_relationship_type("custom") { service1.add_children(service5) }).to eq([service5])
        service5.save!
        expect(service1.with_relationship_type("custom") { service1.children }).to contain_exactly(service3, service4, service5)
      end

      it "#parent=" do
        service6 = ancestry_class.create
        service6.update(:parent => service1)
        expect(service2.with_relationship_type("custom") { service2.parent = (service6) }).to eq(service6)
        service2.save!
        expect(service6.with_relationship_type("custom") { service6.child_ids }).to eq([service2.id])
      end

      context "#replace_children" do
        it "with one child to keep and one to add" do
          service = ancestry_class.create
          service1.with_relationship_type("custom") { service1.replace_children(service, service4) }
          expect(service1.with_relationship_type("custom") { service1.reload.children }).to contain_exactly(service, service4)
        end

        it "with all to remove" do
          service1.with_relationship_type("custom") { service1.replace_children }

          expect(service1.with_relationship_type("custom") { service1.reload.children }).to eq([])
        end

        it "with one child to keep" do
          service1.with_relationship_type("custom") { service1.replace_children(service4) }
          expect(service1.with_relationship_type("custom") { service1.reload.children }).to contain_exactly(service4)
        end

        it "with one child to remove" do
          service = ancestry_class.create
          service1.with_relationship_type("custom") { service1.replace_children(service) }
          expect(service1.with_relationship_type("custom") { service1.reload.children }).to eq([service])
        end

        it "with no children to start adds them all" do
          service = ancestry_class.create
          service.with_relationship_type("custom") { service.replace_children(service4) }
          expect(service.with_relationship_type("custom") { service.reload.children }).to eq([service4])
        end
      end
    end

    describe "#add_parent" do
      let(:folder1) { FactoryBot.create(:ems_folder) }
      let(:folder2) { FactoryBot.create(:ems_folder) }
      let(:vm)      { FactoryBot.create(:vm) }

      it "puts an object under another object" do
        vm.with_relationship_type(test_rel_type) do
          vm.add_parent(folder1)

          expect(vm.parents).to eq([folder1])
          expect(vm.parent).to eq(folder1)
        end

        expect(folder1.with_relationship_type(test_rel_type) { folder1.children }).to eq([vm])
      end

      it "allows an object to be placed under multiple parents" do
        vm.with_relationship_type(test_rel_type) do
          vm.add_parent(folder1)
          vm.add_parent(folder2)

          expect(vm.parents).to match_array([folder1, folder2])
          expect { vm.parent }.to raise_error(RuntimeError, "Multiple parents found.")
        end

        expect(folder1.with_relationship_type(test_rel_type) { folder1.children }).to eq([vm])
        expect(folder2.with_relationship_type(test_rel_type) { folder2.children }).to eq([vm])
      end
    end

    describe "#parent=" do
      let(:folder) { FactoryBot.create(:ems_folder) }
      let(:vm)     { FactoryBot.create(:vm) }

      it "puts an object under another object" do
        vm.with_relationship_type(test_rel_type) do
          vm.parent = folder
          expect(vm.parent).to eq(folder)
        end

        expect(folder.with_relationship_type(test_rel_type) { folder.children }).to eq([vm])
      end

      it "moves an object that already has a parent under an another object" do
        vm.with_relationship_type(test_rel_type) { vm.parent = FactoryBot.create(:ems_folder) }
        vm.reload

        vm.with_relationship_type(test_rel_type) do
          vm.parent = folder
          expect(vm.parent).to eq(folder)
        end

        expect(folder.with_relationship_type(test_rel_type) { folder.children }).to eq([vm])
      end
    end

    it "#replace_children" do
      new_vms = (0...2).collect { FactoryBot.create(:vm_vmware) }
      vms[0].with_relationship_type(test_rel_type) do |v|
        v.replace_children(new_vms)
        expect(new_vms).to match_array(v.children)
      end

      vms[1].with_relationship_type(test_rel_type) do |v|
        expect(v.parents).to be_empty
      end
    end

    it "#remove_all_parents" do
      vms[1].with_relationship_type(test_rel_type) do |v|
        v.remove_all_parents
        expect(v.parents).to be_empty
      end
    end

    it "#remove_all_children" do
      vms[1].with_relationship_type(test_rel_type) do |v|
        v.remove_all_children
        expect(v.children).to be_empty
      end
    end

    it "#remove_all_relationships" do
      vms[1].with_relationship_type(test_rel_type) do |v|
        v.remove_all_relationships
        expect(v.parents).to be_empty
        expect(v.children).to be_empty
      end
    end

    it "#is_descendant_of?" do
      expect(vms[1].with_relationship_type(test_rel_type) { |v| v.is_descendant_of?(vms[0]) }).to be_truthy
      expect(vms[3].with_relationship_type(test_rel_type) { |v| v.is_descendant_of?(vms[0]) }).to be_truthy
      expect(vms[2].with_relationship_type(test_rel_type) { |v| v.is_descendant_of?(vms[1]) }).not_to be_truthy
    end

    it "#is_ancestor_of?" do
      expect(vms[0].with_relationship_type(test_rel_type) { |v| v.is_ancestor_of?(vms[1]) }).to be_truthy
      expect(vms[0].with_relationship_type(test_rel_type) { |v| v.is_ancestor_of?(vms[3]) }).to be_truthy
      expect(vms[2].with_relationship_type(test_rel_type) { |v| v.is_ancestor_of?(vms[1]) }).not_to be_truthy
    end

    it "#ancestors" do
      expect(vms[0].with_relationship_type(test_rel_type) { |v| v.ancestors.empty? }).to be_truthy
      expect(vms[9].with_relationship_type(test_rel_type, &:ancestors)).to match_array([vms[7], vms[2], vms[0]])
    end

    it "#descendants" do
      expect(vms[9].with_relationship_type(test_rel_type) { |v| v.descendants.empty? }).to be_truthy
      expect(vms[0].with_relationship_type(test_rel_type, &:descendants)).to match_array(vms.values - [vms[0]])
    end
  end

  context "tree with relationship type 'ems_metadata'" do
    let(:vms) { build_relationship_tree(vms_rel_tree, "ems_metadata") }

    it "#detect_ancestor" do
      expect(vms[8].with_relationship_type("ems_metadata") { |v| v.detect_ancestor { |a| a.id == vms[2].id } }).not_to be_nil
      expect(vms[8].with_relationship_type("ems_metadata") { |v| v.detect_ancestor { |a| a.id == vms[1].id } }).to be_nil
    end
  end

  describe "#root" do
    it "is self with with no relationships" do
      host # execute the query
      expect do
        nodes = host.with_relationship_type(test_rel_type, &:root)
        expect(nodes).to eq(host)
      end.to make_database_queries(:count => 1) # lookup the relationship node
    end

    it "is a self with a tree's root node" do
      vms # execute the lookup query
      expect do
        nodes = vms[0].with_relationship_type(test_rel_type, &:root)
        expect(nodes).to eq(vms[0])
      end.to make_database_queries(:count => 1) # lookup the relationship node
    end

    it "is a parent with a tree's child node" do
      nodes = vms[7].with_relationship_type(test_rel_type, &:root)
      expect(nodes).to eq(vms[0])
    end
  end

  describe "#root_id" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:root_id)
      expect(nodes).to eq(["Host", host.id])
    end

    it "is a self with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:root_id)
      expect(nodes).to eq(["VmOrTemplate", vms[0].id])
    end

    it "is a parent with a tree's child node" do
      nodes = vms[7].with_relationship_type(test_rel_type, &:root_id)
      expect(nodes).to eq(["VmOrTemplate", vms[0].id])
    end
  end

  # VMs override path, so we will work with host trees
  describe "#path" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:path)
      expect(nodes).to eq([host])
    end

    it "is a self with a tree's root node" do
      hosts = build_relationship_tree({0 => [1, 2]}, test_rel_type, :host_vmware)
      nodes = hosts[0].with_relationship_type(test_rel_type, &:path)
      expect(nodes).to eq([hosts[0]])
    end

    it "is a parent with a tree's child node" do
      hosts = build_relationship_tree({0 => [{1 => [3, 4]}, 2]}, test_rel_type, :host_vmware)
      nodes = hosts[3].with_relationship_type(test_rel_type, &:path)
      expect(nodes).to eq([hosts[0], hosts[1], hosts[3]])
    end
  end

  describe "#path_id" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:path_ids)
      expect(nodes).to eq([["Host", host.id]])
    end

    it "is a self with a tree's root node" do
      hosts = build_relationship_tree({0 => [1, 2]}, test_rel_type, :host_vmware)
      nodes = hosts[0].with_relationship_type(test_rel_type, &:path_ids)
      expect(nodes).to eq([["Host", hosts[0].id]])
    end

    it "is a parent with a tree's child node" do
      hosts = build_relationship_tree({0 => [{1 => [3, 4]}, 2]}, test_rel_type, :host_vmware)
      nodes = hosts[3].with_relationship_type(test_rel_type, &:path_ids)
      expect(nodes).to eq([["Host", hosts[0].id], ["Host", hosts[1].id], ["Host", hosts[3].id]])
    end
  end

  describe "#path_count" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:path_count)
      expect(nodes).to eq(1)
    end

    it "is a self with a tree's root node" do
      hosts = build_relationship_tree({0 => [1, 2]}, test_rel_type, :host_vmware)
      nodes = hosts[0].with_relationship_type(test_rel_type, &:path_count)
      expect(nodes).to eq(1)
    end

    it "is a parent with a tree's child node" do
      hosts = build_relationship_tree({0 => [{1 => [3, 4]}, 2]}, test_rel_type, :host_vmware)
      nodes = hosts[3].with_relationship_type(test_rel_type, &:path_count)
      expect(nodes).to eq(3)
    end
  end

  describe "#ancestors" do
    it "is empty with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:ancestors)
      expect(nodes).to eq([])
    end

    it "is empty with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:ancestors)
      expect(nodes).to eq([])
    end

    it "is an ancestor with child nodes" do
      nodes = vms[7].with_relationship_type(test_rel_type, &:ancestors)
      expect(nodes).to eq([vms[0], vms[2]])
    end
  end

  describe "#subtree" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:subtree)
      expect(nodes).to eq([host])
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:subtree)
      expect(nodes).to match_array(vms.values)
    end

    it "is a subtree with a tree's child node" do
      nodes = vms[2].with_relationship_type(test_rel_type, &:subtree)
      expect(nodes).to match_array([vms[2], vms[5], vms[6], vms[7], vms[8], vms[9]])
    end
  end

  describe "#subtree_arranged" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:subtree_arranged)
      expect(nodes).to eq(host => {})
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:subtree_arranged)
      expect(nodes).to eq(
        vms[0] => {
          vms[1] => {
            vms[3] => {},
            vms[4] => {}
          },
          vms[2] => {
            vms[5] => {},
            vms[6] => {},
            vms[7] => {
              vms[8] => {},
              vms[9] => {}
            }
          }
        }
      )
    end

    it "is a subtree with a tree's child node" do
      nodes = vms[2].with_relationship_type(test_rel_type, &:subtree_arranged)
      expect(nodes).to eq(
        vms[2] => {
          vms[5] => {},
          vms[6] => {},
          vms[7] => {
            vms[8] => {},
            vms[9] => {}
          }
        }
      )
    end
  end

  describe "#subtree_ids" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:subtree_ids)
      expect(nodes).to eq([["Host", host.id]])
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:subtree_ids)
      expect(nodes).to match_array(vms.values.map { |vm| ["VmOrTemplate", vm.id] })
    end

    it "is a subtree with a tree's child node" do
      nodes = vms[2].with_relationship_type(test_rel_type, &:subtree_ids)
      expect(nodes).to match_array(
        [["VmOrTemplate", vms[2].id], ["VmOrTemplate", vms[5].id], ["VmOrTemplate", vms[6].id],
         ["VmOrTemplate", vms[7].id], ["VmOrTemplate", vms[8].id], ["VmOrTemplate", vms[9].id]]
      )
    end
  end

  describe "#subtree_ids_arranged" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:subtree_ids_arranged)
      expect(nodes).to eq([host.class.name, host.id] => {})
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:subtree_ids_arranged)
      expect(nodes).to eq(
        ["VmOrTemplate", vms[0].id] => {
          ["VmOrTemplate", vms[1].id] => {
            ["VmOrTemplate", vms[3].id] => {},
            ["VmOrTemplate", vms[4].id] => {}
          },
          ["VmOrTemplate", vms[2].id] => {
            ["VmOrTemplate", vms[5].id] => {},
            ["VmOrTemplate", vms[6].id] => {},
            ["VmOrTemplate", vms[7].id] => {
              ["VmOrTemplate", vms[8].id] => {},
              ["VmOrTemplate", vms[9].id] => {}
            }
          }
        }
      )
    end

    it "is a subtree with a tree's child node" do
      nodes = vms[2].with_relationship_type(test_rel_type, &:subtree_ids_arranged)
      expect(nodes).to eq(
        ["VmOrTemplate", vms[2].id] => {
          ["VmOrTemplate", vms[5].id] => {},
          ["VmOrTemplate", vms[6].id] => {},
          ["VmOrTemplate", vms[7].id] => {
            ["VmOrTemplate", vms[8].id] => {},
            ["VmOrTemplate", vms[9].id] => {}
          }
        }
      )
    end
  end

  describe "#subtree_count" do
    it "is 1 with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:subtree_count)
      expect(nodes).to eq(1)
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:subtree_count)
      expect(nodes).to eq(10)
    end

    it "is a subtree with a tree's child node" do
      nodes = vms[2].with_relationship_type(test_rel_type, &:subtree_count)
      expect(nodes).to eq(6)
    end
  end

  describe "#descendants" do
    it "is empty with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:descendants)
      expect(nodes).to eq([])
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:descendants)
      expect(nodes).to match_array([vms[1], vms[3], vms[4], vms[2], vms[5], vms[6], vms[7], vms[8], vms[9]])
    end

    it "is a subtree with a tree's child node" do
      nodes = vms[2].with_relationship_type(test_rel_type, &:descendants)
      expect(nodes).to match_array([vms[5], vms[6], vms[7], vms[8], vms[9]])
    end
  end

  describe "#descendants_arranged" do
    it "is empty with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:descendants_arranged)
      expect(nodes).to eq({})
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:descendants_arranged)
      expect(nodes).to eq(
        vms[1] => {
          vms[3] => {},
          vms[4] => {}
        },
        vms[2] => {
          vms[5] => {},
          vms[6] => {},
          vms[7] => {
            vms[8] => {},
            vms[9] => {}
          }
        }
      )
    end

    it "is a subtree with a tree's child node" do
      nodes = vms[2].with_relationship_type(test_rel_type, &:descendants_arranged)
      expect(nodes).to eq(
        vms[5] => {},
        vms[6] => {},
        vms[7] => {
          vms[8] => {},
          vms[9] => {}
        }
      )
    end
  end

  describe "#descendant_ids_arranged" do
    it "is empty with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:descendant_ids_arranged)
      expect(nodes).to eq({})
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:descendant_ids_arranged)
      expect(nodes).to eq(
        ["VmOrTemplate", vms[1].id] => {
          ["VmOrTemplate", vms[3].id] => {},
          ["VmOrTemplate", vms[4].id] => {}
        },
        ["VmOrTemplate", vms[2].id] => {
          ["VmOrTemplate", vms[5].id] => {},
          ["VmOrTemplate", vms[6].id] => {},
          ["VmOrTemplate", vms[7].id] => {
            ["VmOrTemplate", vms[8].id] => {},
            ["VmOrTemplate", vms[9].id] => {}
          }
        }
      )
    end

    it "is a subtree with a tree's child node" do
      nodes = vms[2].with_relationship_type(test_rel_type, &:descendant_ids_arranged)
      expect(nodes).to eq(
        ["VmOrTemplate", vms[5].id] => {},
        ["VmOrTemplate", vms[6].id] => {},
        ["VmOrTemplate", vms[7].id] => {
          ["VmOrTemplate", vms[8].id] => {},
          ["VmOrTemplate", vms[9].id] => {}
        }
      )
    end
  end

  describe "#fulltree" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:fulltree)
      expect(nodes).to eq([host])
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:fulltree)
      expect(nodes).to match_array([vms[0], vms[1], vms[3], vms[4], vms[2], vms[5], vms[6], vms[7], vms[8], vms[9]])
    end

    it "is the full tree with a tree's child node" do
      nodes = vms[8].with_relationship_type(test_rel_type, &:fulltree)
      expect(nodes).to match_array([vms[0], vms[1], vms[3], vms[4], vms[2], vms[5], vms[6], vms[7], vms[8], vms[9]])
    end
  end

  describe "#fulltree_arranged" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:fulltree_arranged)
      expect(nodes).to eq(host => {})
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:fulltree_arranged)
      expect(nodes).to eq(
        vms[0] => {
          vms[1] => {
            vms[3] => {},
            vms[4] => {}
          },
          vms[2] => {
            vms[5] => {},
            vms[6] => {},
            vms[7] => {
              vms[8] => {},
              vms[9] => {}
            }
          }
        }
      )
    end

    it "is the full tree with a tree's child node" do
      nodes = vms[8].with_relationship_type(test_rel_type, &:fulltree_arranged)
      expect(nodes).to eq(
        vms[0] => {
          vms[1] => {
            vms[3] => {},
            vms[4] => {}
          },
          vms[2] => {
            vms[5] => {},
            vms[6] => {},
            vms[7] => {
              vms[8] => {},
              vms[9] => {}
            }
          }
        }
      )
    end
  end

  describe "#fulltree_ids_arranged" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:fulltree_ids_arranged)
      expect(nodes).to eq([host.class.name, host.id] => {})
    end

    it "is the full tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:fulltree_ids_arranged)
      expect(nodes).to eq(
        ["VmOrTemplate", vms[0].id] => {
          ["VmOrTemplate", vms[1].id] => {
            ["VmOrTemplate", vms[3].id] => {},
            ["VmOrTemplate", vms[4].id] => {}
          },
          ["VmOrTemplate", vms[2].id] => {
            ["VmOrTemplate", vms[5].id] => {},
            ["VmOrTemplate", vms[6].id] => {},
            ["VmOrTemplate", vms[7].id] => {
              ["VmOrTemplate", vms[8].id] => {},
              ["VmOrTemplate", vms[9].id] => {}
            }
          }
        }
      )
    end

    it "is the full tree with a tree's child node" do
      nodes = vms[8].with_relationship_type(test_rel_type, &:fulltree_ids_arranged)
      expect(nodes).to eq(
        ["VmOrTemplate", vms[0].id] => {
          ["VmOrTemplate", vms[1].id] => {
            ["VmOrTemplate", vms[3].id] => {},
            ["VmOrTemplate", vms[4].id] => {}
          },
          ["VmOrTemplate", vms[2].id] => {
            ["VmOrTemplate", vms[5].id] => {},
            ["VmOrTemplate", vms[6].id] => {},
            ["VmOrTemplate", vms[7].id] => {
              ["VmOrTemplate", vms[8].id] => {},
              ["VmOrTemplate", vms[9].id] => {}
            }
          }
        }
      )
    end
  end

  describe "#fulltree_count" do
    it "is self with with no relationships" do
      nodes = host.with_relationship_type(test_rel_type, &:fulltree_count)
      expect(nodes).to eq(1)
    end

    it "is the tree with a tree's root node" do
      nodes = vms[0].with_relationship_type(test_rel_type, &:fulltree_count)
      expect(nodes).to eq(10)
    end

    it "is the full tree with a tree's child node" do
      nodes = vms[8].with_relationship_type(test_rel_type, &:fulltree_count)
      expect(nodes).to eq(10)
    end
  end

  describe "#parent_rels" do
    it "works with relationships" do
      pars = vms[8].with_relationship_type(test_rel_type, &:parent_rels)
      pars_vms = pars.map(&:resource)
      expect(pars_vms).to eq([vms[7]])
    end
  end

  describe "#parent_rel_ids" do
    it "works with relationships" do
      ids = vms[8].with_relationship_type(test_rel_type, &:parent_rel_ids)
      parent_vms = Relationship.where(:id => ids).map(&:resource)
      expect(parent_vms).to eq([vms[7]])
    end

    it "works with cached relationships" do
      ids = vms[8].with_relationship_type(test_rel_type) do |o|
        # load relationships into the cache
        o.all_relationships
        o.parent_rel_ids
      end
      parent_vms = Relationship.where(:id => ids).map(&:resource)
      expect(parent_vms).to eq([vms[7]])
    end
  end

  describe "#grandchild_rels" do
    it "works with relationships" do
      vms[0].with_relationship_type(test_rel_type) do
        rels = vms[0].grandchild_rels
        expect(rels.map(&:resource)).to match_array([vms[3], vms[4], vms[5], vms[6], vms[7]])
      end
    end
  end

  describe "#grandchildren" do
    it "works with relationships" do
      vms[0].with_relationship_type(test_rel_type) do
        expect(vms[0].grandchildren).to match_array([vms[3], vms[4], vms[5], vms[6], vms[7]])
      end
    end
  end

  describe "#child_and_grandchild_rels" do
    it "works with relationships" do
      vms[0].with_relationship_type(test_rel_type) do
        rels = vms[0].child_and_grandchild_rels
        expect(rels.map(&:resource)).to match_array([vms[1], vms[2], vms[3], vms[4], vms[5], vms[6], vms[7]])
      end
    end
  end

  protected

  def build_relationship_tree(tree, rel_type = test_rel_type, base_factory = :vm_vmware)
    # temp list of the relationships
    # allows easy access while building
    # can map to the resource to return all the resources created
    rels = Hash.new do |hash, key|
      hash[key] = FactoryBot.create(:relationship,
                                     :resource     => FactoryBot.create(base_factory),
                                     :relationship => rel_type)
    end

    recurse_relationship_tree(tree) do |parent, child|
      rels[child].parent = rels[parent]
      rels[child].save!
    end
    # pull out all values in key order. (0, 1, 2, 3, ...) (unmemoize them on the way out)
    rels.each_with_object({}) { |(n, v), h| h[n] = v.resource.tap(&:unmemoize_all) }
  end

  def recurse_relationship_tree(tree, &block)
    parent   = tree.keys.first
    children = tree[parent]
    children = children.collect { |child| child.kind_of?(Hash) ? recurse_relationship_tree(child, &block) : child }
    children.each { |child| yield parent, child }
    parent
  end

  def assert_parent_child_structure(rel_type, parent, p_rels_count, p_parents, p_children, child, c_rels_count, c_parents, c_children)
    parent.with_relationship_type(rel_type) do
      expect(parent.relationships.length).to eq(p_rels_count)
      expect(parent.parents.length).to eq(p_parents.length)
      expect(parent.parents).to              match_array(p_parents)
      expect(parent.children.length).to eq(p_children.length)
      expect(parent.children).to             match_array(p_children)
    end

    child.with_relationship_type(rel_type) do
      expect(child.relationships.length).to eq(c_rels_count)
      expect(child.parents.length).to eq(c_parents.length)
      expect(child.parents).to              match_array(c_parents)
      expect(child.children.length).to eq(c_children.length)
      expect(child.children).to             match_array(c_children)
    end
  end
end
