/*
Copyright (c) 2017 VMware, Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

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/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", "/*/*/*", 25},
		{"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)
	}
}
