// © Broadcom. All Rights Reserved.
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
// SPDX-License-Identifier: Apache-2.0

package simulator

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"testing"

	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/vim25"
	"github.com/vmware/govmomi/vim25/types"
)

func TestFinderVPX(t *testing.T) {
	ctx := context.Background()

	m := VPX()
	m.Datacenter = 3
	m.Folder = 2
	m.Pool = 1

	defer m.Remove()

	err := m.Create()
	if err != nil {
		t.Fatal(err)
	}

	s := m.Service.NewServer()
	defer s.Close()

	client, err := govmomi.NewClient(ctx, s.URL, true)
	if err != nil {
		t.Fatal(err)
	}

	finder := find.NewFinder(client.Client, false)
	dc, _ := finder.Datacenter(ctx, "/F0/DC1")
	finder.SetDatacenter(dc)

	tests := []struct {
		kind   string
		path   string
		expect int
	}{
		{"ManagedObjectList", "./*", 4},       // /F0/DC1/{vm,host,network,datastore}
		{"ManagedObjectListChildren", ".", 4}, // ""
		{"ManagedObjectList", "/", 1},
		{"ManagedObjectList", "/*", m.Datacenter - m.Folder + m.Folder},
		{"ManagedObjectList", "/DC0", 1},
		{"ManagedObjectList", "/F[01]", 2},
		{"ManagedObjectListChildren", "/*", m.Datacenter + 3},
		{"ManagedObjectListChildren", "/*/*", 19},
		{"ManagedObjectListChildren", "/*/*/*", 31},
		{"FolderList", "/*", m.Folder},
		{"DatacenterList", "/F0/*", 1},
		{"DatacenterList", "/DC0", 1},
		{"VirtualMachineList", "/DC0/vm/*", (m.Host + m.Cluster) * m.Machine},
		{"VirtualMachineList", "F0/DC1_C0_RP1_VM0", 1},
		{"VirtualMachineList", "./DC1_C0_RP1_VM0", 0},
		{"VirtualMachineList", "DC1_C0_RP1_VM0", 1}, // find . -type VirtualMachine -name DC1_C0_RP0_VM0
		{"VirtualAppList", "/DC0/vm/*", 0},
		{"DatastoreList", "/DC0/datastore/*", m.Datastore},
		{"DatastoreList", "./*", 0},
		{"DatastoreList", "./F0/*", m.Datastore},
		{"DatastoreList", "/F*/*/datastore/F*/*", m.Datastore * m.Folder},
		{"DatastoreList", "/F0/DC1/datastore/F0/LocalDS_0", m.Datastore},
		{"DatastoreList", "/F0/DC1/datastore/F0/*", m.Datastore},
		{"DatastoreList", "/F1/DC2/datastore/F1/*", m.Datastore},
		{"DatastoreList", "./LocalDS_0", 0},
		{"DatastoreList", "LocalDS_0", 1}, // find . -type Datastore -name LocalDS_0
		{"DatastoreClusterList", "/DC0/datastore/*", 0},
		{"ComputeResourceList", "/DC0/host/*", m.Host + m.Cluster},
		{"ClusterComputeResourceList", "/DC0/host/*", m.Cluster},
		{"HostSystemList", "/DC0/host/*", m.Host + m.ClusterHost},
		{"HostSystemList", "/F0/DC1/host/F0/*", m.Host + m.ClusterHost},
		{"HostSystemList", "DC1_H0", 1},                    // find . -type HostSystem -name DC1_H0
		{"ComputeResourceList", "DC1_H0", 1},               // find . -type ComputeResource -name DC1_H0
		{"ClusterComputeResourceList", "DC1_C0", 1},        // find . -type ClusterComputeResource -name DC1_H0
		{"NetworkList", "/DC0/network/*", 3 + m.Portgroup}, // VM Network + DSwitch + DSwitch-Uplinks + m.Portgroup
		{"NetworkList", "./*", 1},
		{"NetworkList", "/F0/DC1/network/VM Network", 1},
		{"NetworkList", "/F0/DC1/network/F0/*", 2 + m.Portgroup},
		{"NetworkList", "./F0/DC1_DVPG0", 1},
		{"NetworkList", "./F0/DC1_DVPG0", 1},
		{"NetworkList", "DC1_DVPG0", 1}, // find . -type Network -name DC1_DVPG0
		{"ResourcePoolList", "/F0/DC1/host/F0/*", m.Host + m.Cluster},
		{"ResourcePoolList", "/F0/DC1/host/F0/*/*", m.Host + m.Cluster},
		{"ResourcePoolList", "/DC0/host/*", m.Host + m.Cluster},
		{"ResourcePoolList", "/DC0/host/*/*", m.Host + m.Cluster},
		{"ResourcePoolList", "/DC0/host/DC0_H0/Resources", 1},
		{"ResourcePoolList", "/F1/DC2/host/F1/DC2_C0/Resources", 1},
		{"ResourcePoolList", "Resources", m.Host + m.Cluster},                // find . -type ResourcePool -name Resources
		{"ResourcePoolList", "/F1/DC2/...", m.Pool + m.Host + 1},             // find $path -type ResourcePool
		{"ResourcePoolList", "/F1/DC2/host/...", m.Pool + m.Host + 1},        // find $path -type ResourcePool
		{"ResourcePoolList", "/F1/DC2/host/F1/...", m.Pool + m.Host + 1},     // find $path -type ResourcePool
		{"ResourcePoolList", "/F1/DC2/host/F1/DC2_C0/...", m.Pool + 1},       // find $path -type ResourcePool
		{"ResourcePoolList", "/F1/DC2/host/F1/DC2_C0/Resources/...", m.Pool}, // find $path -type ResourcePool
		{"ResourcePoolList", "F0/DC1_C0", 1},
		{"ResourcePoolList", "DC1_C0_RP1", 1},     // find . -type ResourcePool -name DC1_C0_RP1
		{"", "", 0},                               // unset Datacenter
		{"DatacenterList", "*", m.Datacenter},     // find . -type Datacenter
		{"DatacenterList", "./...", m.Datacenter}, // find . -type Datacenter
		{"DatacenterList", "DC2", 1},              // find . -type Datacenter -name DC2
		{"DatacenterList", "/*", m.Datacenter - m.Folder},
		{"DatacenterList", "/*/*", m.Folder},
		{"DatastoreList", "/F1/DC2/datastore/F1/LocalDS_0", 1},
		{"VirtualMachineList", "DC1_C0_RP1_VM0", 0}, // TODO: recurse all Datacenters?
	}

	f := reflect.ValueOf(finder)
	c := reflect.ValueOf(ctx)

	for i, test := range tests {
		if test.kind == "" {
			finder.SetDatacenter(nil)
			continue
		}

		err = nil

		arg := []reflect.Value{c, reflect.ValueOf(test.path)}
		res := f.MethodByName(test.kind).Call(arg)

		rval := res[0]
		rerr := res[1]

		if rval.Len() != test.expect {
			msg := fmt.Sprintf("expected %d, got %d", test.expect, rval.Len())
			if !rerr.IsNil() {
				msg += fmt.Sprintf(" (%s)", rerr.Interface())
			}
			err = errors.New(msg)

			if !rval.IsNil() {
				for j := 0; j < rval.Len(); j++ {
					t.Logf("%s\n", rval.Index(j).Interface())
				}
			}
		} else if !rerr.IsNil() {
			if test.expect != 0 {
				err = rerr.Interface().(error)
			}
		}

		if err != nil {
			t.Errorf("%d) %s(%s): %s", i, test.kind, test.path, err)
		}
	}
}

func TestFinderESX(t *testing.T) {
	ctx := context.Background()

	m := ESX()

	defer m.Remove()

	err := m.Create()
	if err != nil {
		t.Fatal(err)
	}

	s := m.Service.NewServer()
	defer s.Close()

	client, err := govmomi.NewClient(ctx, s.URL, true)
	if err != nil {
		t.Fatal(err)
	}

	finder := find.NewFinder(client.Client, false)

	dc, err := finder.DefaultDatacenter(ctx)
	if err != nil {
		t.Fatal(err)
	}

	finder.SetDatacenter(dc)

	f := reflect.ValueOf(finder)
	c := reflect.ValueOf(ctx)

	tests := []string{"Folder", "Datastore", "ComputeResource", "HostSystem", "Datastore", "Network"}

	for _, test := range tests {
		res := f.MethodByName("Default" + test).Call([]reflect.Value{c})
		if !res[1].IsNil() {
			t.Fatalf("%s: %s", test, res[1].Interface())
		}

		// test find by moref
		ref := res[0].Interface().(object.Reference).Reference()
		o, err := finder.Element(ctx, ref)
		if err != nil {
			t.Fatal(err)
		}

		if o.Object.Reference() != ref {
			t.Errorf("%s", ref)
		}
	}
}

func TestFinderDefaultHostVPX(t *testing.T) {
	ctx := context.Background()

	m := VPX()
	m.Folder = 1

	defer m.Remove()

	err := m.Create()
	if err != nil {
		t.Fatal(err)
	}

	s := m.Service.NewServer()
	defer s.Close()

	client, err := govmomi.NewClient(ctx, s.URL, true)
	if err != nil {
		t.Fatal(err)
	}

	finder := find.NewFinder(client.Client, false)
	dc, _ := finder.Datacenter(ctx, "/F0/DC0")
	finder.SetDatacenter(dc)

	hostf, _ := finder.Folder(ctx, dc.InventoryPath+"/host/F0")

	folders, err := dc.Folders(ctx)
	if err != nil {
		t.Fatal(err)
	}

	f, err := folders.HostFolder.CreateFolder(ctx, "MyHosts")
	if err != nil {
		t.Fatal(err)
	}

	// 2-levels (MyHosts/F0) deep under the DC host folder: /F0/DC0/host/MyHosts/F0/DC0_C0/DC0_C0_H0
	task, _ := f.MoveInto(ctx, []types.ManagedObjectReference{hostf.Reference()})
	if err = task.Wait(ctx); err != nil {
		t.Fatal(err)
	}

	_, err = finder.HostSystemOrDefault(ctx, "")
	if err == nil {
		t.Fatal("expected error")
	}

	_, ok := err.(*find.DefaultMultipleFoundError)
	if !ok {
		t.Errorf("unexpected error type=%T", err)
	}
}

func TestFinderDestroyedParentResourcePool(t *testing.T) {
	Test(func(ctx context.Context, c *vim25.Client) {
		finder := find.NewFinder(c)

		rp, err := finder.ResourcePool(ctx, "/DC0/host/DC0_C0/Resources")
		if err != nil {
			t.Fatal(err)
		}

		foo, err := rp.Create(ctx, "foo", types.DefaultResourceConfigSpec())
		if err != nil {
			t.Fatal(err)
		}

		bar, err := foo.Create(ctx, "bar", types.DefaultResourceConfigSpec())
		if err != nil {
			t.Fatal(err)
		}

		task, err := foo.Destroy(ctx)
		if err != nil {
			t.Fatal(err)
		}
		if err := task.WaitEx(ctx); err != nil {
			t.Fatal(err)
		}

		if _, err := finder.Element(ctx, bar.Reference()); err != nil {
			t.Fatal(err)
		}
	})
}
