package vspherecontroller

import (
	"context"
	"crypto/tls"
	"fmt"

	"github.com/openshift/vmware-vsphere-csi-driver-operator/pkg/operator/vclib"
	"github.com/vmware/govmomi"
	"github.com/vmware/govmomi/find"
	"github.com/vmware/govmomi/simulator"
	"github.com/vmware/govmomi/vim25/types"
	"gopkg.in/gcfg.v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/legacy-cloud-providers/vsphere"
)

const (
	defaultModel  = "testdata/default"
	defaultVMPath = "/DC0/vm/"
)

type simulatedVM struct {
	name, uuid string
}

var (
	// Virtual machines generated by vSphere simulator. UUIDs look generated, but they're stable.
	defaultVMs = []simulatedVM{
		{"DC0_H0_VM0", "265104de-1472-547c-b873-6dc7883fb6cb"},
		{"DC0_H0_VM1", "39365506-5a0a-5fd0-be10-9586ad53aaad"},
	}
	defaultHostId = "host-24" // Generated by vcsim
)

func setupSimulator(modelDir string) (*vclib.VSphereConnection, func(), error) {
	model := simulator.Model{}
	err := model.Load(modelDir)
	if err != nil {
		return nil, nil, err
	}
	model.Service.TLS = new(tls.Config)

	s := model.Service.NewServer()
	client, err := connectToSimulator(s)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to connect to the similator: %s", err)
	}
	conn := &vclib.VSphereConnection{
		Config: simulatorConfig(),
		Client: client,
	}

	cleanup := func() {
		s.Close()
		model.Remove()
	}
	return conn, cleanup, nil
}

func connectToSimulator(s *simulator.Server) (*govmomi.Client, error) {
	client, err := govmomi.NewClient(context.TODO(), s.URL, true)
	if err != nil {
		return nil, err
	}
	return client, nil
}

func node(name string, modifiers ...func(*v1.Node)) *v1.Node {
	n := &v1.Node{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1.NodeSpec{
			ProviderID: "",
		},
	}
	for _, modifier := range modifiers {
		modifier(n)
	}
	return n
}

func withProviderID(id string) func(*v1.Node) {
	return func(node *v1.Node) {
		node.Spec.ProviderID = id
	}
}

func defaultNodes() []*v1.Node {
	var nodes []*v1.Node
	for _, vm := range defaultVMs {
		node := node(vm.name, withProviderID("vsphere://"+vm.uuid))
		nodes = append(nodes, node)
	}
	return nodes
}

func simulatorConfig() *vsphere.VSphereConfig {
	var cfg vsphere.VSphereConfig
	// Configuration that corresponds to the simulated vSphere
	data := `[Global]
secret-name = "vsphere-creds"
secret-namespace = "kube-system"
insecure-flag = "1"

[Workspace]
server = "localhost"
datacenter = "DC0"
default-datastore = "LocalDS_0"
folder = "/DC0/vm"

[VirtualCenter "dc0"]
datacenters = "DC0"
`
	err := gcfg.ReadStringInto(&cfg, data)
	if err != nil {
		panic(err)
	}
	return &cfg
}

func customizeVCenterVersion(version string, apiVersion string, conn *vclib.VSphereConnection) {
	conn.Client.Client.ServiceContent.About.Version = version
	conn.Client.Client.ServiceContent.About.ApiVersion = apiVersion
	fmt.Printf("customize vcenter version")
}

func setHWVersion(conn *vclib.VSphereConnection, node *v1.Node, hardwareVersion string) error {
	err := customizeVM(conn, node, &types.VirtualMachineConfigSpec{
		ExtraConfig: []types.BaseOptionValue{
			&types.OptionValue{
				Key: "SET.config.version", Value: hardwareVersion,
			},
		}})
	return err
}

func customizeVM(conn *vclib.VSphereConnection, node *v1.Node, spec *types.VirtualMachineConfigSpec) error {
	finder := find.NewFinder(conn.Client.Client, true)
	vm, err := finder.VirtualMachine(context.TODO(), defaultVMPath+node.Name)
	if err != nil {
		return err
	}

	task, err := vm.Reconfigure(context.TODO(), *spec)
	if err != nil {
		return err
	}

	err = task.Wait(context.TODO())
	return err
}

func customizeHostVersion(hostSystemId string, version string) error {
	hsRef := simulator.Map.Get(types.ManagedObjectReference{
		Type:  "HostSystem",
		Value: hostSystemId,
	})
	if hsRef == nil {
		return fmt.Errorf("can't find HostSystem %s", hostSystemId)
	}

	hs := hsRef.(*simulator.HostSystem)
	hs.Config.Product.Version = version
	hs.Config.Product.ApiVersion = version
	return nil
}
