//go:build windows
// +build windows

package hypervctl

import (
	"errors"
	"fmt"

	"github.com/containers/libhvee/pkg/wmiext"
)

const (
	HyperVNamespace                = "root\\virtualization\\v2"
	VirtualSystemManagementService = "Msvm_VirtualSystemManagementService"
	MsvmComputerSystem             = "Msvm_ComputerSystem"
)

// https://learn.microsoft.com/en-us/windows/win32/hyperv_v2/msvm-computersystem

type VirtualMachineManager struct {
}

func NewVirtualMachineManager() *VirtualMachineManager {
	return &VirtualMachineManager{}
}

func NewLocalHyperVService() (*wmiext.Service, error) {
	service, err := wmiext.NewLocalService(HyperVNamespace)
	if err != nil {
		return nil, translateCommonHyperVWmiError(err)
	}

	return service, nil
}

func (vmm *VirtualMachineManager) GetAll() ([]*VirtualMachine, error) {
	// Fetch through settings to avoid locale sensitive properties
	const wql = "Select * From Msvm_VirtualSystemSettingData Where VirtualSystemType = 'Microsoft:Hyper-V:System:Realized'"

	var service *wmiext.Service
	var err error
	if service, err = NewLocalHyperVService(); err != nil {
		return []*VirtualMachine{}, err
	}
	defer service.Close()

	var enum *wmiext.Enum
	if enum, err = service.ExecQuery(wql); err != nil {
		return nil, err
	}
	defer enum.Close()
	var vms []*VirtualMachine

	for {
		settings, err := enum.Next()
		if err != nil {
			return vms, err
		}

		// Finished iterating
		if settings == nil {
			break
		}

		vm, err := vmm.findVMFromSettings(service, settings)
		settings.Close()
		if err != nil {
			return vms, err
		}

		vms = append(vms, vm)
	}

	return vms, nil
}

func (vmm *VirtualMachineManager) Exists(name string) (bool, error) {
	vms, err := vmm.GetAll()
	if err != nil {
		return false, err
	}
	for _, i := range vms {
		// TODO should case be honored or ignored?
		if i.Name == name {
			return true, nil
		}
	}
	return false, nil
}

// GetMachine is a stub to lookup and get settings for a VM
func (vmm *VirtualMachineManager) GetMachine(name string) (*VirtualMachine, error) {
	return vmm.getMachine(name)
}

// GetMachineExists looks for a machine defined in hyperv and returns it if it exists
func (vmm *VirtualMachineManager) GetMachineExists(name string) (bool, *VirtualMachine, error) {
	vm, err := vmm.getMachine(name)
	if err != nil {
		if errors.Is(err, wmiext.ErrNoResults) {
			return false, nil, nil
		}
		return false, nil, err
	}
	return true, vm, nil
}

// getMachine looks up a single VM by name
func (vmm *VirtualMachineManager) getMachine(name string) (*VirtualMachine, error) {
	const wql = "Select * From Msvm_VirtualSystemSettingData Where VirtualSystemType = 'Microsoft:Hyper-V:System:Realized' And ElementName='%s'"

	vm := &VirtualMachine{}
	var service *wmiext.Service
	var err error

	if service, err = NewLocalHyperVService(); err != nil {
		return vm, err
	}
	defer service.Close()

	var enum *wmiext.Enum
	if enum, err = service.ExecQuery(fmt.Sprintf(wql, name)); err != nil {
		return nil, err
	}
	defer enum.Close()

	settings, err := service.FindFirstInstance(fmt.Sprintf(wql, name))
	if err != nil {
		if errors.Is(err, wmiext.ErrNoResults) {
			return nil, err
		}
		return vm, fmt.Errorf("could not find virtual machine %q: %w", name, err)
	}
	defer settings.Close()

	return vmm.findVMFromSettings(service, settings)
}

func (vmm *VirtualMachineManager) findVMFromSettings(service *wmiext.Service, settings *wmiext.Instance) (*VirtualMachine, error) {
	path, err := settings.Path()
	if err != nil {
		return nil, err
	}

	vm := &VirtualMachine{vmm: vmm}
	err = service.FindFirstRelatedObject(path, MsvmComputerSystem, vm)

	return vm, err
}

func (*VirtualMachineManager) CreateVhdxFile(path string, maxSize uint64) error {
	var service *wmiext.Service
	var err error
	if service, err = NewLocalHyperVService(); err != nil {
		return err
	}
	defer service.Close()

	settings := &VirtualHardDiskSettings{}
	settings.Format = 3
	settings.MaxInternalSize = maxSize
	settings.Type = 3
	settings.Path = path

	instance, err := service.CreateInstance("Msvm_VirtualHardDiskSettingData", settings)
	if err != nil {
		return err
	}
	defer instance.Close()
	settingsStr := instance.GetCimText()

	imms, err := service.GetSingletonInstance("Msvm_ImageManagementService")
	if err != nil {
		return err
	}
	defer imms.Close()

	var job *wmiext.Instance
	var ret int32
	err = imms.BeginInvoke("CreateVirtualHardDisk").
		In("VirtualDiskSettingData", settingsStr).
		Execute().
		Out("Job", &job).
		Out("ReturnValue", &ret).
		End()

	if err != nil {
		return fmt.Errorf("failed to create vhdx: %w", err)
	}

	return waitVMResult(ret, service, job, "failed to create vhdx", nil)
}

// GetSummaryInformation returns the live VM summary information for all virtual machines.
// The requestedFields parameter controls which fields of summary information are populated.
// SummaryRequestCommon and SummaryRequestNearAll provide predefined combinations for this
// parameter.
func (vmm *VirtualMachineManager) GetSummaryInformation(requestedFields SummaryRequestSet) ([]SummaryInformation, error) {
	return vmm.getSummaryInformation("", requestedFields)
}

func (vmm *VirtualMachineManager) getSummaryInformation(settingsPath string, requestedFields SummaryRequestSet) ([]SummaryInformation, error) {
	var service *wmiext.Service
	var err error
	if service, err = NewLocalHyperVService(); err != nil {
		return nil, err
	}
	defer service.Close()

	vsms, err := service.GetSingletonInstance(VirtualSystemManagementService)
	if err != nil {
		return nil, err
	}
	defer vsms.Close()

	var summary []SummaryInformation

	inv := vsms.BeginInvoke("GetSummaryInformation").
		In("RequestedInformation", []uint(requestedFields))

	if len(settingsPath) > 0 {
		inv.In("SettingData", []string{settingsPath})
	}

	err = inv.Execute().
		Out("SummaryInformation", &summary).
		End()

	if err != nil {
		return nil, err
	}

	return summary, nil
}
