package common

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/hashicorp/packer/builder/vsphere/driver"
	"github.com/hashicorp/packer/helper/multistep"
)

func TestHardwareConfig_Prepare(t *testing.T) {
	tc := []struct {
		name           string
		config         *HardwareConfig
		fail           bool
		expectedErrMsg string
	}{
		{
			name:   "Validate empty config",
			config: &HardwareConfig{},
			fail:   false,
		},
		{
			name: "Validate RAMReservation RAMReserveAll cannot be used together",
			config: &HardwareConfig{
				RAMReservation: 2,
				RAMReserveAll:  true,
			},
			fail:           true,
			expectedErrMsg: "'RAM_reservation' and 'RAM_reserve_all' cannot be used together",
		},
		{
			name: "Invalid firmware",
			config: &HardwareConfig{
				Firmware: "invalid",
			},
			fail:           true,
			expectedErrMsg: "'firmware' must be '', 'bios', 'efi' or 'efi-secure'",
		},
		{
			name: "Validate 'bios' firmware",
			config: &HardwareConfig{
				Firmware: "bios",
			},
			fail: false,
		},
		{
			name: "Validate 'efi' firmware",
			config: &HardwareConfig{
				Firmware: "efi",
			},
			fail: false,
		},
		{
			name: "Validate 'efi-secure' firmware",
			config: &HardwareConfig{
				Firmware: "efi-secure",
			},
			fail: false,
		},
	}
	for _, c := range tc {
		t.Run(c.name, func(t *testing.T) {
			errs := c.config.Prepare()
			if c.fail {
				if len(errs) == 0 {
					t.Fatalf("Config preprare should fail")
				}
				if errs[0].Error() != c.expectedErrMsg {
					t.Fatalf("Expected error message: %s but was '%s'", c.expectedErrMsg, errs[0].Error())
				}
			} else {
				if len(errs) != 0 {
					t.Fatalf("Config preprare should not fail")
				}
			}
		})
	}
}

func TestStepConfigureHardware_Run(t *testing.T) {
	tc := []struct {
		name            string
		step            *StepConfigureHardware
		action          multistep.StepAction
		configureError  error
		configureCalled bool
		hardwareConfig  *driver.HardwareConfig
	}{
		{
			name:            "Configure hardware",
			step:            basicStepConfigureHardware(),
			action:          multistep.ActionContinue,
			configureError:  nil,
			configureCalled: true,
			hardwareConfig:  driverHardwareConfigFromConfig(basicStepConfigureHardware().Config),
		},
		{
			name:            "Don't configure hardware when config is empty",
			step:            &StepConfigureHardware{Config: &HardwareConfig{}},
			action:          multistep.ActionContinue,
			configureError:  nil,
			configureCalled: false,
		},
		{
			name:            "Halt when configure return error",
			step:            basicStepConfigureHardware(),
			action:          multistep.ActionHalt,
			configureError:  errors.New("failed to configure"),
			configureCalled: true,
			hardwareConfig:  driverHardwareConfigFromConfig(basicStepConfigureHardware().Config),
		},
	}
	for _, c := range tc {
		t.Run(c.name, func(t *testing.T) {
			state := basicStateBag(nil)
			vmMock := new(driver.VirtualMachineMock)
			vmMock.ConfigureError = c.configureError
			state.Put("vm", vmMock)

			action := c.step.Run(context.TODO(), state)
			if action != c.action {
				t.Fatalf("expected action '%v' but actual action was '%v'", c.action, action)
			}
			if vmMock.ConfigureCalled != c.configureCalled {
				t.Fatalf("expecting vm.Configure called to %t but was %t", c.configureCalled, vmMock.ConfigureCalled)
			}
			if diff := cmp.Diff(vmMock.ConfigureHardwareConfig, c.hardwareConfig); diff != "" {
				t.Fatalf("wrong driver.HardwareConfig: %s", diff)
			}

			err, ok := state.GetOk("error")
			containsError := c.configureError != nil
			if containsError != ok {
				t.Fatalf("Contain error - expecting %t but was %t", containsError, ok)
			}
			if containsError {
				if !strings.Contains(err.(error).Error(), c.configureError.Error()) {
					t.Fatalf("Destroy should fail with error message '%s' but failed with '%s'", c.configureError.Error(), err.(error).Error())
				}
			}
		})
	}
}

func basicStepConfigureHardware() *StepConfigureHardware {
	return &StepConfigureHardware{
		Config: &HardwareConfig{
			CPUs:           1,
			CpuCores:       1,
			CPUReservation: 1,
			CPULimit:       4000,
			RAM:            1024,
			RAMReserveAll:  true,
			Firmware:       "efi-secure",
			ForceBIOSSetup: true,
		},
	}
}

func driverHardwareConfigFromConfig(config *HardwareConfig) *driver.HardwareConfig {
	return &driver.HardwareConfig{
		CPUs:                config.CPUs,
		CpuCores:            config.CpuCores,
		CPUReservation:      config.CPUReservation,
		CPULimit:            config.CPULimit,
		RAM:                 config.RAM,
		RAMReservation:      config.RAMReservation,
		RAMReserveAll:       config.RAMReserveAll,
		NestedHV:            config.NestedHV,
		CpuHotAddEnabled:    config.CpuHotAddEnabled,
		MemoryHotAddEnabled: config.MemoryHotAddEnabled,
		VideoRAM:            config.VideoRAM,
		VGPUProfile:         config.VGPUProfile,
		Firmware:            config.Firmware,
		ForceBIOSSetup:      config.ForceBIOSSetup,
	}
}