package vcenter

import (
	"context"
	"fmt"
	"log"
	"log/slog"
	"net/url"
	"os"
	"path"

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

type Vcenter struct {
	Logger *slog.Logger
	ctx    context.Context
	client *govmomi.Client
}

type VmProperties struct {
	Vm           mo.VirtualMachine
	ResourcePool string
}

// New creates a new Vcenter with the given logger
func New(logger *slog.Logger) *Vcenter {

	//ctx, cancel := context.WithCancel(context.Background())
	//defer cancel()

	return &Vcenter{
		Logger: logger,
		ctx:    context.Background(),
	}
}

func (v *Vcenter) Login(vUrl string) error {
	var insecure bool

	insecureString := os.Getenv("VCENTER_INSECURE")
	username := os.Getenv("VCENTER_USERNAME")
	password := os.Getenv("VCENTER_PASSWORD")

	// Connect to vCenter
	u, err := soap.ParseURL(vUrl)
	if err != nil {
		log.Fatalf("Error parsing vCenter URL: %s", err)
	}

	u.User = url.UserPassword(username, password)

	/*
		c, err := govmomi.NewClient(ctx, u, insecure)
		if err != nil {
			log.Fatalf("Error connecting to vCenter: %s", err)
		}
	*/

	if insecureString == "true" {
		insecure = true
	}

	c, err := govmomi.NewClient(v.ctx, u, insecure)
	if err != nil {
		v.Logger.Error("Unable to connect to vCenter", "error", err)
		return fmt.Errorf("unable to connect to vCenter : %s", err)
	}

	//defer c.Logout(v.ctx)

	v.client = c

	v.Logger.Debug("successfully connected to vCenter", "url", vUrl, "username", username)

	return nil
}

func (v *Vcenter) Logout() error {
	//v.Logger.Debug("vcenter logging out")

	if v.ctx == nil {
		v.Logger.Warn("Nil context, unable to logout")
		return nil
	}

	if v.client.Valid() {
		//v.Logger.Debug("vcenter client is valid. Logging out")
		return v.client.Logout(v.ctx)
	} else {
		v.Logger.Debug("vcenter client is not valid")
		return nil
	}

}

func (v *Vcenter) FindVMByName(vmName string) ([]mo.VirtualMachine, error) {
	m := view.NewManager(v.client.Client)

	vms, err := m.CreateContainerView(v.ctx, v.client.ServiceContent.RootFolder, []string{"VirtualMachine"}, true)
	if err != nil {
		return nil, err
	}
	defer vms.Destroy(v.ctx)

	var matchingVMs []mo.VirtualMachine

	err = vms.Retrieve(v.ctx, []string{"VirtualMachine"}, []string{"name"}, &matchingVMs)
	if err != nil {
		return nil, err
	}

	// Temporarily just return all VMs
	//return matchingVMs, nil

	var result []mo.VirtualMachine

	for _, vm := range matchingVMs {
		if vm.Name == vmName {
			result = append(result, vm)
		}
	}

	return result, nil
}

func (v *Vcenter) FindVMByID(vmID string) (*VmProperties, error) {

	v.Logger.Debug("searching for vm id", "vm_id", vmID)

	finder := find.NewFinder(v.client.Client, true)

	// List all datacenters
	datacenters, err := finder.DatacenterList(v.ctx, "*")
	if err != nil {
		return nil, fmt.Errorf("failed to list datacenters: %w", err)
	}

	for _, dc := range datacenters {
		// Set the current datacenter
		finder.SetDatacenter(dc)

		// Create a ManagedObjectReference for the VM
		vmRef := types.ManagedObjectReference{
			Type:  "VirtualMachine",
			Value: vmID,
		}

		// Try to find the VM by ID in the current datacenter
		//vm, err := finder.ObjectReference(v.ctx, vmRef)

		var vm mo.VirtualMachine
		err := v.client.RetrieveOne(v.ctx, vmRef, []string{"config", "name"}, &vm)

		if err == nil {
			return &VmProperties{
				//Datacenter: dc.Name(),
				Vm: vm,
			}, nil
		} else if _, ok := err.(*find.NotFoundError); !ok {
			// If the error is not a NotFoundError, return it
			//return nil, fmt.Errorf("failed to retrieve VM with ID %s in datacenter %s: %w", vmID, dc.Name(), err)
			v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_name", dc.Name())
		} else {
			return nil, fmt.Errorf("failed to retrieve VM: %w", err)
		}
	}

	return nil, fmt.Errorf("VM with ID %s not found in any datacenter", vmID)
}

func (v *Vcenter) FindVMByIDWithDatacenter(vmID string, dcID string) (*VmProperties, error) {
	//var dcName string
	var err error
	resourcePool := ""
	v.Logger.Debug("searching for vm id", "vm_id", vmID, "datacenter_id", dcID)

	finder := find.NewFinder(v.client.Client, true)

	// Create a ManagedObjectReference for the datacenter
	dcRef := types.ManagedObjectReference{
		Type:  "Datacenter",
		Value: dcID,
	}

	// Convert the reference to a Datacenter object
	datacenter := object.NewDatacenter(v.client.Client, dcRef)
	if datacenter == nil {
		return nil, fmt.Errorf("Datacenter with id %s not found", dcID)
	}

	// Use finder.SetDatacenter to set the datacenter
	finder.SetDatacenter(datacenter)

	// Create a ManagedObjectReference for the VM
	vmRef := types.ManagedObjectReference{
		Type:  "VirtualMachine",
		Value: vmID,
	}

	var vm mo.VirtualMachine
	//err := v.client.RetrieveOne(v.ctx, vmRef, []string{"config", "name"}, &vm)
	err = v.client.RetrieveOne(v.ctx, vmRef, nil, &vm)
	if err == nil {
		v.Logger.Debug("Found VM")

		// Retrieve the resource pool the VM is in
		if vm.ResourcePool != nil {
			rp := object.NewResourcePool(v.client.Client, *vm.ResourcePool)
			rpName, err := rp.ObjectName(v.ctx)
			if err != nil {
				v.Logger.Error("failed to get resource pool name", "error", err)
			} else {
				v.Logger.Debug("Found resource pool name", "rp_name", rpName)
				resourcePool = rpName
			}

		}

		return &VmProperties{
			//Datacenter: dcName,
			Vm:           vm,
			ResourcePool: resourcePool,
		}, nil
	} else if _, ok := err.(*find.NotFoundError); !ok {
		// If the error is not a NotFoundError, return it
		//return nil, fmt.Errorf("failed to retrieve VM with ID %s in datacenter %s: %w", vmID, dc.Name(), err)
		v.Logger.Debug("Couldn't find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
		return nil, nil
	} else {
		return nil, fmt.Errorf("failed to retrieve VM: %w", err)
	}

	//v.Logger.Info("Unable to find vm in datacenter", "vm_id", vmID, "datacenter_id", dcID)
	//return nil, nil
}

// Helper function to retrieve the full folder path for the VM
func (v *Vcenter) GetVMFolderPath(vm mo.VirtualMachine) (string, error) {
	//finder := find.NewFinder(v.client.Client, true)

	v.Logger.Debug("Commencing vm folder path search")

	// Start from the VM's parent
	parentRef := vm.Parent
	if parentRef == nil {
		return "", fmt.Errorf("no parent found for the VM")
	}

	// Traverse the folder hierarchy to build the full folder path
	folderPath := ""
	v.Logger.Debug("parent is", "parent", parentRef)

	for parentRef.Type != "Datacenter" {
		// Retrieve the parent object
		//parentObj, err := finder.ObjectReference(v.ctx, *parentRef)
		//if err != nil {
		//	return "", fmt.Errorf("failed to find parent object in inventory: %w", err)
		//}

		// Retrieve the folder name

		var parentObj mo.Folder
		err := v.client.RetrieveOne(v.ctx, *parentRef, nil, &parentObj)
		if err != nil {
			v.Logger.Error("Failed to get object for parent reference", "ref", parentRef)
			break
		}

		// Prepend the folder name to the path
		folderPath = path.Join("/", parentObj.Name, folderPath)

		// Move up to the next parent
		//if folder, ok := parentObj.(*object.Folder); ok {
		if parentObj.Parent != nil {
			parentRef = parentObj.Parent
			v.Logger.Debug("Parent uplevel is", "ref", parentRef)
		} else {
			return "", fmt.Errorf("unexpected parent type: %s", parentObj.Reference().Type)
		}
		//break
	}

	return folderPath, nil
}
