/*
Copyright (c) 2019-2023 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
Nikita Sorokin, <nesorokin@basistech.ru>

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.
*/

/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.

Source code: https://repository.basistech.ru/BASIS/terraform-provider-decort

Please see README.md to learn where to place source code so that it
builds seamlessly.

Documentation: https://repository.basistech.ru/BASIS/terraform-provider-decort/wiki
*/

package kvmvm

import (
	"context"
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/compute"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/kvmx86"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/dc"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/status"
)

func resourceComputeCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceComputeCreate: called for Compute name %q, RG ID %d", d.Get("name").(string), d.Get("rg_id").(int))
	c := m.(*controller.ControllerCfg)

	createReqX86 := kvmx86.CreateRequest{}

	if diags := checkParamsExistence(ctx, d, c); diags != nil {
		return diags
	}

	if desc, ok := d.GetOk("description"); ok {
		createReqX86.Description = desc.(string)
	}

	if sepID, ok := d.GetOk("sep_id"); ok {
		createReqX86.SEPID = uint64(sepID.(int))
	}

	if pool, ok := d.GetOk("pool"); ok {
		createReqX86.Pool = pool.(string)
	}

	if nodeID, ok := d.GetOk("node_id"); ok {
		createReqX86.NodeID = uint64(nodeID.(int))
	}

	if bootSize, ok := d.GetOk("boot_disk_size"); ok {
		createReqX86.BootDisk = uint64(bootSize.(int))
	}

	if bootDiskCache, ok := d.GetOk("boot_disk_cache"); ok {
		createReqX86.BootDiskCache = bootDiskCache.(string)
	}

	if zoneID, ok := d.GetOk("zone_id"); ok {
		createReqX86.ZoneID = uint64(zoneID.(int))
	}

	if bootDiskBLKDiscard, ok := d.GetOk("boot_disk_blk_discard"); ok {
		createReqX86.BootDiskBLKDiscard = bootDiskBLKDiscard.(bool)
	}

	createReqX86.Interfaces = make([]kvmx86.Interface, 0)

	if networks, ok := d.GetOk("network"); ok {
		if networks.(*schema.Set).Len() > 0 {
			ns := networks.(*schema.Set).List()
			sort.Slice(ns, func(i, j int) bool {
				weightI := ns[i].(map[string]interface{})["weight"].(int)
				weightJ := ns[j].(map[string]interface{})["weight"].(int)
				if weightI == 0 {
					return false
				}
				if weightJ == 0 {
					return true
				}
				return weightI < weightJ
			})
			interfacesX86 := make([]kvmx86.Interface, 0)
			for _, elem := range ns {
				netInterfaceVal := elem.(map[string]interface{})

				reqInterface := kvmx86.Interface{
					NetType: netInterfaceVal["net_type"].(string),
					NetID:   uint64(netInterfaceVal["net_id"].(int)),
				}

				if enabledNetwork(d.GetRawConfig().GetAttr("network"), reqInterface.NetID, reqInterface.NetType) {
					reqInterface.Enabled = netInterfaceVal["enabled"].(bool)
				}

				if reqInterface.NetType == "DPDK" || reqInterface.NetType == "EXTNET" || reqInterface.NetType == "TRUNK" {
					reqInterface.MTU = uint64(netInterfaceVal["mtu"].(int))
				}

				if reqInterface.NetType == "DPDK" || reqInterface.NetType == "VFNIC" {
					if netMask, netMaskSet := netInterfaceVal["net_mask"]; netMaskSet {
						reqInterface.NetMask = uint64(netMask.(int))
					}
				}

				ipaddr, ipSet := netInterfaceVal["ip_address"]
				if ipSet {
					reqInterface.IPAddr = ipaddr.(string)
				}

				macaddr, macSet := netInterfaceVal["mac"]
				if macSet {
					reqInterface.MAC = macaddr.(string)
				}

				sdnID, sdnSet := netInterfaceVal["sdn_interface_id"]
				if sdnSet {
					reqInterface.SDNInterfaceID = sdnID.(string)
				}

				interfacesX86 = append(interfacesX86, reqInterface)
			}
			createReqX86.Interfaces = interfacesX86

		}
	}

	if disks, ok := d.GetOk("disks"); ok {
		disksX86 := make([]kvmx86.DataDisk, 0)

		for _, elem := range disks.([]interface{}) {
			diskVal := elem.(map[string]interface{})
			reqDataDisk := kvmx86.DataDisk{
				DiskName:        diskVal["disk_name"].(string),
				Size:            uint64(diskVal["size"].(int)),
				StoragePolicyID: uint64(diskVal["storage_policy_id"].(int)),
			}
			if sepId, ok := diskVal["sep_id"]; ok {
				reqDataDisk.SepID = uint64(sepId.(int))
			}
			if pool, ok := diskVal["pool"]; ok {
				reqDataDisk.Pool = pool.(string)
			}
			if desc, ok := diskVal["desc"]; ok {
				reqDataDisk.Description = desc.(string)
			}
			if imageID, ok := diskVal["image_id"]; ok {
				reqDataDisk.ImageID = uint64(imageID.(int))
			}
			if cache, ok := diskVal["cache"]; ok {
				reqDataDisk.Cache = cache.(string)
			}
			if blkDiscard, ok := diskVal["blk_discard"]; ok {
				reqDataDisk.BLKDiscard = blkDiscard.(bool)
			}
			disksX86 = append(disksX86, reqDataDisk)
		}

		createReqX86.DataDisks = disksX86

	}

	if cloudInit, ok := d.GetOk("cloud_init"); ok {
		userdata := cloudInit.(string)
		if userdata != "" && userdata != "applied" {
			createReqX86.Userdata = strings.TrimSpace(userdata)
		}
	}

	var computeId uint64

	createReqX86.RGID = uint64(d.Get("rg_id").(int))
	createReqX86.Name = d.Get("name").(string)
	createReqX86.CPU = uint64(d.Get("cpu").(int))
	createReqX86.RAM = uint64(d.Get("ram").(int))
	createReqX86.StoragePolicyID = uint64(d.Get("storage_policy_id").(int))

	if image, ok := d.GetOk("image_id"); ok {
		createReqX86.ImageID = uint64(image.(int))
	}
	if withoutBootDisk, ok := d.GetOk("without_boot_disk"); ok {
		createReqX86.WithoutBootDisk = withoutBootDisk.(bool)
	}

	if custom_fields, ok := d.GetOk("custom_fields"); ok {
		val := custom_fields.(string)
		val = strings.ReplaceAll(val, "\\", "")
		val = strings.ReplaceAll(val, "\n", "")
		val = strings.ReplaceAll(val, "\t", "")
		val = strings.TrimSpace(val)

		createReqX86.CustomField = val
	}

	if numaAffinity, ok := d.GetOk("numa_affinity"); ok {
		createReqX86.NumaAffinity = numaAffinity.(string)
	}
	createReqX86.CPUPin = d.Get("cpu_pin").(bool)
	createReqX86.HPBacked = d.Get("hp_backed").(bool)
	createReqX86.Chipset = d.Get("chipset").(string)

	if preferredCPU, ok := d.GetOk("preferred_cpu"); ok {
		preferredList := preferredCPU.([]interface{})
		if len(preferredList) > 0 {
			for _, v := range preferredList {
				cpuNum := v.(int)
				createReqX86.PreferredCPU = append(createReqX86.PreferredCPU, int64(cpuNum))
			}
		}
	}

	if osVersion, ok := d.GetOk("os_version"); ok {
		createReqX86.OSVersion = osVersion.(string)
	}

	log.Debugf("resourceComputeCreate: creating Compute of type KVM VM x86")
	var apiResp uint64
	var err error
	if d.Get("create_blank").(bool) {
		log.Debugf("resourceComputeCreate: using createBlank endpoint")
		createBlankReq := kvmx86.CreateBlankRequest{
			RGID:            createReqX86.RGID,
			Name:            createReqX86.Name,
			CPU:             createReqX86.CPU,
			RAM:             createReqX86.RAM,
			StoragePolicyID: createReqX86.StoragePolicyID,
			WithoutBootDisk: createReqX86.WithoutBootDisk,
			BootDisk:        createReqX86.BootDisk,
			SEPID:           createReqX86.SEPID,
			Pool:            createReqX86.Pool,
			DataDisks:       createReqX86.DataDisks,
			Interfaces:      createReqX86.Interfaces,
			Description:     createReqX86.Description,
			Chipset:         createReqX86.Chipset,
			PreferredCPU:    createReqX86.PreferredCPU,
			ZoneID:          createReqX86.ZoneID,
			OSVersion:       createReqX86.OSVersion,
		}
		apiResp, err = c.CloudBroker().KVMX86().CreateBlank(ctx, createBlankReq)
	} else {
		apiResp, err = c.CloudBroker().KVMX86().Create(ctx, createReqX86)
	}

	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId(strconv.FormatUint(apiResp, 10))
	computeId = apiResp

	warnings := dc.Warnings{}

	simpleCompRec, err := utilityComputeCheckPresence(ctx, d, m)
	if err != nil {
		warnings.Add(err)
	}

	cleanup := false
	defer func() {
		if cleanup {
			req := compute.DeleteRequest{
				ComputeID:   computeId,
				Permanently: true,
				DetachDisks: true,
			}

			if _, err := c.CloudBroker().Compute().Delete(ctx, req); err != nil {
				log.Errorf("resourceComputeCreate: could not delete compute after failed creation: %v", err)
			}

			d.SetId("")
		}
	}()

	log.Debugf("resourceComputeCreate: new simple Compute ID %d, name %s created", computeId, d.Get("name").(string))

	updateReq := compute.UpdateRequest{}

	loaderType, loaderTypeOk := d.GetOk("loader_type")
	bootType, bootTypeOk := d.GetOk("boot_type")
	hotResize, hotResizeOk := d.GetOkExists("hot_resize")
	networkInterfaceNaming, networkInterfaceNamingOk := d.GetOk("network_interface_naming")
	weight, weightOk := d.GetOk("weight")

	if loaderTypeOk {
		updateReq.LoaderType = loaderType.(string)
	}

	if bootTypeOk {
		updateReq.BootType = bootType.(string)
	}

	if hotResizeOk {
		updateReq.HotResize = hotResize.(bool)
	}

	if networkInterfaceNamingOk {
		updateReq.NetworkInterfaceNaming = networkInterfaceNaming.(string)
	}

	if weightOk {
		updateReq.Weight = uint64(weight.(int))
	}

	if loaderTypeOk || bootTypeOk || hotResizeOk || networkInterfaceNamingOk || weightOk {
		log.Debugf("resourceComputeCreate: change loaderType or bootType or hotResize or networkInterfaceNaming or weight on ComputeID: %d", computeId)
		updateReq.ComputeID = computeId
		_, err := c.CloudBroker().Compute().Update(ctx, updateReq)
		if err != nil {
			warnings.Add(err)
		}
	}

	if ars, ok := d.GetOk("pci_devices"); ok {
		log.Debugf("resourceComputeCreate: add pci devices on ComputeID: %d", computeId)
		addedPciDevices := ars.(*schema.Set).List()
		if len(addedPciDevices) > 0 {
			for _, v := range addedPciDevices {
				devicesConv := v.(int)
				req := compute.AttachPCIDeviceRequest{
					ComputeID: computeId,
					DeviceID:  uint64(devicesConv),
				}

				_, err := c.CloudBroker().Compute().AttachPCIDevice(ctx, req)
				if err != nil {
					warnings.Add(err)
				}
			}
		}
	}

	extraDisks, ok := d.GetOk("extra_disks")
	if ok && extraDisks.(*schema.Set).Len() > 0 {
		log.Debugf("resourceComputeCreate: calling utilityComputeExtraDisksConfigure to attach %d extra disk(s)", extraDisks.(*schema.Set).Len())
		err := utilityComputeExtraDisksConfigure(ctx, d, m, false)
		if err != nil {
			log.Errorf("resourceComputeCreate: error when attaching extra disk(s) to a new Compute ID %d: %v", computeId, err)
			cleanup = true
			return diag.FromErr(err)
		}
	}

	if !cleanup {

		if enabled, ok := d.GetOk("enabled"); ok {
			if enabled.(bool) {
				req := compute.EnableRequest{ComputeID: computeId}
				log.Debugf("resourceComputeCreate: enable=%t Compute ID %d after completing its resource configuration", enabled, computeId)
				if _, err := c.CloudBroker().Compute().Enable(ctx, req); err != nil {
					warnings.Add(err)
				}
			} else {
				req := compute.DisableRequest{ComputeID: computeId}
				log.Debugf("resourceComputeCreate: enable=%t Compute ID %d after completing its resource configuration", enabled, computeId)
				if _, err := c.CloudBroker().Compute().Disable(ctx, req); err != nil {
					warnings.Add(err)
				}
			}
		}

		if libvirtSettings, ok := d.GetOk("libvirt_settings"); ok {
			if libvirtSettings.(*schema.Set).Len() > 0 {
				lvs := libvirtSettings.(*schema.Set).List()
				for _, elem := range lvs {
					netLibvirtMap := elem.(map[string]interface{})

					netType := netLibvirtMap["net_type"].(string)
					netId := uint64(netLibvirtMap["net_id"].(int))
					var mac string
					for _, iface := range simpleCompRec.Interfaces {
						if iface.NetID == netId && iface.NetType == netType {
							mac = iface.MAC
							break
						}
					}
					if mac == "" {
						warnings.Add(fmt.Errorf("add  libvirt virtio: Network with type %s and id %d is not connected to the compute %d", netType, netId, computeId))
						continue
					}
					log.Debugf("resourceComputeCreate: Configure libvirt virtio interface parameters on Network with type %s and id %d", netType, netId)
					req := compute.SetNetConfigRequest{
						ComputeID:   computeId,
						MAC:         mac,
						TXMode:      netLibvirtMap["txmode"].(string),
						IOEventFD:   netLibvirtMap["ioeventfd"].(string),
						EventIDx:    netLibvirtMap["event_idx"].(string),
						Queues:      uint64(netLibvirtMap["queues"].(int)),
						RXQueueSize: uint64(netLibvirtMap["rx_queue_size"].(int)),
						TXQueueSize: uint64(netLibvirtMap["tx_queue_size"].(int)),
					}

					_, err := c.CloudBroker().Compute().SetNetConfig(ctx, req)
					if err != nil {
						warnings.Add(err)
					}
				}
			}
		}

		if secGroups, ok := d.GetOk("security_groups"); ok {
			if secGroups.(*schema.Set).Len() > 0 {
				sgl := secGroups.(*schema.Set).List()
				for _, elem := range sgl {
					secGroupsMap := elem.(map[string]interface{})

					netType := secGroupsMap["net_type"].(string)
					netId := uint64(secGroupsMap["net_id"].(int))
					var mac string
					for _, iface := range simpleCompRec.Interfaces {
						if iface.NetID == netId && iface.NetType == netType {
							mac = iface.MAC
							break
						}
					}
					if mac == "" {
						warnings.Add(fmt.Errorf("add security groups: Network with type %s and id %d is not connected to the compute %d", netType, netId, computeId))
						continue
					}
					secGroupsIDs := make([]uint64, 0)
					for _, id := range secGroupsMap["security_groups"].(*schema.Set).List() {
						secGroupsIDs = append(secGroupsIDs, uint64(id.(int)))
					}
					log.Debugf("resourceComputeCreate: Configure security groups interface parameters on Network with type %s and id %d", netType, netId)
					req := compute.ChangeSecGroupsRequest{
						ComputeID:       computeId,
						Interface:       mac,
						SecGroups:       secGroupsIDs,
						EnableSecGroups: secGroupsMap["enable_secgroups"].(bool),
					}

					_, err := c.CloudBroker().Compute().ChangeSecGroups(ctx, req)
					if err != nil {
						warnings.Add(err)
					}
				}
			}
		}

		if start, ok := d.GetOk("started"); ok && start.(bool) {
			req := compute.StartRequest{ComputeID: computeId}
			if nodeID, ok := d.GetOk("node_id"); ok {
				req.NodeID = uint64(nodeID.(int))
			}
			if altBootID, ok := d.Get("alt_boot_id").(int); ok {
				req.AltBootID = uint64(altBootID)
			}
			log.Debugf("resourceComputeCreate: starting Compute ID %d after completing its resource configuration", computeId)
			if _, err := c.CloudBroker().Compute().Start(ctx, req); err != nil {
				warnings.Add(err)
			}
		} else if ok && !start.(bool) {
			req := compute.StopRequest{ComputeID: computeId}
			log.Debugf("resourceComputeCreate: stoping Compute ID %d after completing its resource configuration", computeId)
			if _, err := c.CloudBroker().Compute().Stop(ctx, req); err != nil {
				warnings.Add(err)
			}
		}

		if pin, ok := d.GetOk("pin_to_node"); ok && pin.(bool) {
			start, _ := d.GetOk("started")
			_, nodeOk := d.GetOk("node_id")

			if !start.(bool) && !nodeOk {
				warnings.Add(errors.New("cannot pin to node a VM, that is not started and node_id is not set"))
			} else {
				req := compute.PinToNodeRequest{
					ComputeID:    computeId,
					TargetNodeID: uint64(d.Get("node_id").(int)),
				}

				if force, ok := d.Get("force_pin").(bool); ok {
					req.Force = force
				}

				if autoStart, ok := d.Get("auto_start_w_node").(bool); ok {
					req.AutoStart = autoStart
				}

				_, err := c.CloudBroker().Compute().PinToNode(ctx, req)
				if err != nil {
					warnings.Add(err)
				}
			}
		}

		if affinityLabel, ok := d.GetOk("affinity_label"); ok {
			req := compute.AffinityLabelSetRequest{
				ComputeIDs: []uint64{
					computeId,
				},
				AffinityLabel: affinityLabel.(string),
			}

			_, err := c.CloudBroker().Compute().AffinityLabelSet(ctx, req)
			if err != nil {
				warnings.Add(err)
			}
		}

		if ars, ok := d.GetOk("affinity_rules"); ok {
			log.Debugf("resourceComputeCreate: Create affinity rules on ComputeID: %d", computeId)
			addedAR := ars.([]interface{})
			if len(addedAR) > 0 {
				for _, ar := range addedAR {
					arConv := ar.(map[string]interface{})
					req := compute.AffinityRuleAddRequest{
						ComputeIDs: []uint64{computeId},
						Topology:   arConv["topology"].(string),
						Policy:     arConv["policy"].(string),
						Mode:       arConv["mode"].(string),
						Key:        arConv["key"].(string),
						Value:      arConv["value"].(string),
					}

					_, err := c.CloudBroker().Compute().AffinityRuleAdd(ctx, req)
					if err != nil {
						warnings.Add(err)
					}
				}
			}
		}

		if ars, ok := d.GetOk("anti_affinity_rules"); ok {
			log.Debugf("resourceComputeCreate: Create anti affinity rules on ComputeID: %d", computeId)
			addedAR := ars.([]interface{})
			if len(addedAR) > 0 {
				for _, ar := range addedAR {
					arConv := ar.(map[string]interface{})
					req := compute.AntiAffinityRuleAddRequest{
						ComputeIDs: []uint64{computeId},
						Topology:   arConv["topology"].(string),
						Policy:     arConv["policy"].(string),
						Mode:       arConv["mode"].(string),
						Key:        arConv["key"].(string),
						Value:      arConv["value"].(string),
					}

					_, err := c.CloudBroker().Compute().AntiAffinityRuleAdd(ctx, req)
					if err != nil {
						warnings.Add(err)
					}
				}
			}
		}
		if tags, ok := d.GetOk("tags"); ok {
			log.Debugf("resourceComputeCreate: Create tags on ComputeID: %d", computeId)
			addedTags := tags.(*schema.Set).List()
			if len(addedTags) > 0 {
				for _, tagInterface := range addedTags {
					tagItem := tagInterface.(map[string]interface{})
					req := compute.TagAddRequest{
						ComputeIDs: []uint64{computeId},
						Key:        tagItem["key"].(string),
						Value:      tagItem["value"].(string),
					}

					_, err := c.CloudBroker().Compute().TagAdd(ctx, req)
					if err != nil {
						warnings.Add(err)
					}
				}
			}
		}

		if pfws, ok := d.GetOk("port_forwarding"); ok {
			log.Debugf("resourceComputeCreate: Create port farwarding on ComputeID: %d", computeId)
			addedPfws := pfws.(*schema.Set).List()
			if len(addedPfws) > 0 {
				for _, pfwInterface := range addedPfws {
					pfwItem := pfwInterface.(map[string]interface{})
					req := compute.PFWAddRequest{
						ComputeID:       computeId,
						PublicPortStart: uint64(pfwItem["public_port_start"].(int)),
						Proto:           pfwItem["proto"].(string),
					}
					if pfwItem["local_port"].(int) != 0 {
						req.LocalBasePort = uint64(pfwItem["local_port"].(int))
					}
					if int64(pfwItem["public_port_end"].(int)) != 0 {
						req.PublicPortEnd = int64(pfwItem["public_port_end"].(int))
					}

					pwfId, err := c.CloudBroker().Compute().PFWAdd(ctx, req)
					if err != nil {
						warnings.Add(err)
					}
					d.Set("rule_id", pwfId)
				}
			}
		}

		if userAcess, ok := d.GetOk("user_access"); ok {
			log.Debugf("resourceComputeCreate: Create user access on ComputeID: %d", computeId)
			usersAcess := userAcess.(*schema.Set).List()
			if len(usersAcess) > 0 {
				for _, userAcessInterface := range usersAcess {
					userAccessItem := userAcessInterface.(map[string]interface{})
					req := compute.UserGrantRequest{
						ComputeID:  computeId,
						Username:   userAccessItem["username"].(string),
						AccessType: userAccessItem["access_type"].(string),
					}

					_, err := c.CloudBroker().Compute().UserGrant(ctx, req)
					if err != nil {
						warnings.Add(err)
					}
				}
			}
		}

		if snapshotList, ok := d.GetOk("snapshot"); ok {
			log.Debugf("resourceComputeCreate: Create snapshot on ComputeID: %d", computeId)
			snapshots := snapshotList.(*schema.Set).List()
			if len(snapshots) > 0 {
				for _, snapshotInterface := range snapshots {
					snapshotItem := snapshotInterface.(map[string]interface{})
					req := compute.SnapshotCreateRequest{
						ComputeID: computeId,
						Label:     snapshotItem["label"].(string),
					}

					_, err := c.CloudBroker().Compute().SnapshotCreate(ctx, req)
					if err != nil {
						warnings.Add(err)
					}
				}
			}
		}

		if cdtList, ok := d.GetOk("cd"); ok {
			log.Debugf("resourceComputeCreate: Create cd on ComputeID: %d", computeId)
			cds := cdtList.(*schema.Set).List()
			if len(cds) > 0 {
				snapshotItem := cds[0].(map[string]interface{})
				req := compute.CDInsertRequest{
					ComputeID: computeId,
					CDROMID:   uint64(snapshotItem["cdrom_id"].(int)),
				}

				_, err := c.CloudBroker().Compute().CDInsert(ctx, req)
				if err != nil {
					warnings.Add(err)
				}
			}
		}

		if !d.Get("pin_to_node").(bool) && d.Get("auto_start_w_node").(bool) {
			req := compute.UpdateRequest{
				ComputeID: computeId,
				AutoStart: d.Get("auto_start_w_node").(bool),
				CPUPin:    d.Get("cpu_pin").(bool),
				HPBacked:  d.Get("hp_backed").(bool),
			}
			_, err := c.CloudBroker().Compute().Update(ctx, req)
			if err != nil {
				warnings.Add(err)
			}
		}

		if d.Get("pause").(bool) {
			req := compute.PauseRequest{
				ComputeID: computeId,
			}
			_, err := c.CloudBroker().Compute().Pause(ctx, req)
			if err != nil {
				warnings.Add(err)
			}
		}

		if _, ok := d.GetOk("disks"); ok {
			err := utilityComputeCreatePresentDisk(ctx, d, m)
			if err != nil {
				warnings.Add(err)
			}
			if err := utilityComputeCreateBlockSize(ctx, d, m); err != nil {
				warnings.Add(err)
			}
			if err := utilityComputeCreateIOTune(ctx, d, m); err != nil {
				warnings.Add(err)
			}
		}

		if readOnly, ok := d.GetOk("read_only"); ok {
			if readOnly.(bool) {
				if err := utilityComputeUpdateReadOnly(ctx, d, m); err != nil {
					warnings.Add(err)
				}
			}
		}

	}

	log.Debugf("resourceComputeCreate: new Compute ID %d, name %s creation sequence complete", computeId, d.Get("name").(string))

	// We may reuse dataSourceComputeRead here as we maintain similarity
	// between Compute resource and Compute data source schemas
	// Compute read function will also update resource ID on success, so that Terraform
	// will know the resource exists

	return append(resourceComputeRead(ctx, d, m), warnings.Get()...)
}

func resourceComputeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceComputeRead: called for Compute name %s, RG ID %d",
		d.Get("name").(string), d.Get("rg_id").(int))

	// c := m.(*controller.ControllerCfg)

	computeRec, err := utilityComputeCheckPresence(ctx, d, m)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	pciList, err := utilityComputePCIDevicesList(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}

	hasChanged := false

	switch computeRec.Status {
	case status.Deleted:
		// restoreReq := compute.RestoreRequest{ComputeID: computeRec.ID}
		// enableReq := compute.EnableRequest{ComputeID: computeRec.ID}

		// _, err := c.CloudBroker().Compute().Restore(ctx, restoreReq)
		// if err != nil {
		// 	return diag.FromErr(err)
		// }

		// _, err = c.CloudBroker().Compute().Enable(ctx, enableReq)
		// if err != nil {
		// 	return diag.FromErr(err)
		// }

		// hasChanged = true
	case status.Destroyed:
		d.SetId("")
		return diag.Errorf("The resource cannot be read because it has been destroyed")
		// return resourceComputeCreate(ctx, d, m)
	case status.Disabled:
		log.Debugf("The compute is in status: %s, troubles may occur with update. Please, enable compute first.", computeRec.Status)
	case status.Redeploying:
	case status.Deleting:
	case status.Destroying:
		return diag.Errorf("The compute is in progress with status: %s", computeRec.Status)
	case status.Modeled:
		return diag.Errorf("The compute is in status: %s, please, contact support for more information", computeRec.Status)
	}

	if hasChanged {
		computeRec, err = utilityComputeCheckPresence(ctx, d, m)
		if err != nil {
			d.SetId("")
			return diag.FromErr(err)
		}

		pciList, err = utilityComputePCIDevicesList(ctx, d, m)
		if err != nil {
			return diag.FromErr(err)
		}
	}

	d.SetId(strconv.FormatUint(computeRec.ID, 10))

	if err = flattenCompute(d, computeRec, pciList); err != nil {
		return diag.FromErr(err)
	}

	log.Debugf("resourceComputeRead: after flattenCompute: Compute ID %s, name %q, RG ID %d",
		d.Id(), d.Get("name").(string), d.Get("rg_id").(int))

	return nil
}

func resourceComputeUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceComputeUpdate: called for Compute ID %s / name %s, RGID %d",
		d.Id(), d.Get("name").(string), d.Get("rg_id").(int))

	c := m.(*controller.ControllerCfg)

	if diags := checkParamsExistence(ctx, d, c); diags != nil {
		return diags
	}

	computeRec, err := utilityComputeCheckPresence(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}

	// check compute statuses
	switch computeRec.Status {
	case status.Deleted:
		if restore, ok := d.GetOk("restore"); ok && restore.(bool) {
			if err := utilityComputeRestore(ctx, d, m); err != nil {
				return diag.FromErr(err)
			}
		}
	case status.Destroyed:
		d.SetId("")
		return resourceComputeCreate(ctx, d, m)
	case status.Disabled:
		log.Debugf("The compute is in status: %s, may troubles can be occured with update. Please, enable compute first.", computeRec.Status)
	case status.Redeploying:
	case status.Deleting:
	case status.Destroying:
		return diag.Errorf("The compute is in progress with status: %s", computeRec.Status)
	case status.Modeled:
		return diag.Errorf("The compute is in status: %s, please, contant the support for more information", computeRec.Status)
	}
	warnings := dc.Warnings{}

	if d.HasChange("enabled") {
		if err := utilityComputeEnabled(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("started") {
		if err := utilityComputeStarted(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChanges("cpu", "ram") {
		if err := utilityComputeResize(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("boot_disk_size") {
		if err := utilityComputeBootDiskResize(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("boot_disk_cache") {
		if err := utilityComputeUpdateBootDiskCache(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("boot_disk_blk_discard") {
		if err := utilityComputeUpdateBootDiskBLKDiscard(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("extra_disks") {
		err := utilityComputeExtraDisksConfigure(ctx, d, m, true) // pass do_delta = true to apply changes, if any
		if err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("pin_to_node") {
		if err := utilityComputePinToNode(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChanges("description",
		"name",
		"numa_affinity",
		"cpu_pin",
		"hp_backed",
		"chipset",
		"auto_start_w_node",
		"preferred_cpu",
		"loader_type",
		"boot_type",
		"hot_resize",
		"network_interface_naming",
		"os_version",
		"weight") {
		if err := utilityComputeUpdate(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChanges("network", "libvirt_settings") {
		err = utilityComputeNetworksConfigure(ctx, d, m) // pass do_delta = true to apply changes, if any
		if err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("security_groups") {
		err = utilityComputeSecGroupsConfigure(ctx, d, m)
		if err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("disks") {
		if err := utilityComputeUpdateDisks(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("affinity_label") {
		if err := utilityComputeUpdateAffinityLabel(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("affinity_rules") {
		if err := utilityComputeUpdateAffinityRules(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("anti_affinity_rules") {
		if err := utilityComputeUpdateAntiAffinityRules(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("tags") {
		if err := utilityComputeUpdateTags(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("port_forwarding") {
		if err := utilityComputeUpdatePFW(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("user_access") {
		if err := utilityComputeUpdateUserAccess(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("snapshot") {
		if err := utilityComputeUpdateSnapshot(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("rollback") {
		if err := utilityComputeRollback(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("cd") {
		if err := utilityComputeUpdateCD(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("pause") {
		if err := utilityComputePause(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("reset") {
		if err := utilityComputeReset(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("image_id") {
		if err := utilityComputeUpdateImage(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("custom_fields") {
		if err := utilityComputeUpdateCustomFields(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("pci_devices") {
		if err := utilityComputeUpdatePciDevices(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("zone_id") {
		if err := utilityComputeUpdateZoneID(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("read_only") {
		if err := utilityComputeUpdateReadOnly(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

	return append(resourceComputeRead(ctx, d, m), warnings.Get()...)
}

func resourceComputeDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	// NOTE: this function destroys target Compute instance "permanently", so
	// there is no way to restore it.
	// If compute being destroyed has some extra disks attached, they are
	// detached from the compute
	log.Debugf("resourceComputeDelete: called for Compute name %s, RG ID %d",
		d.Get("name").(string), d.Get("rg_id").(int))

	c := m.(*controller.ControllerCfg)
	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	req := compute.DeleteRequest{
		ComputeID:   computeId,
		Permanently: d.Get("permanently").(bool),
		DetachDisks: d.Get("detach_disks").(bool),
	}

	if _, err := c.CloudBroker().Compute().Delete(ctx, req); err != nil {
		return diag.FromErr(err)
	}

	d.SetId("")

	return nil
}

func ResourceCompute() *schema.Resource {
	return &schema.Resource{
		SchemaVersion: 2,

		CustomizeDiff: func(ctx context.Context, diff *schema.ResourceDiff, i interface{}) error {
			if diff.HasChanges() || diff.HasChanges("chipset", "pin_to_node", "auto_start_w_node", "libvirt_settings", "network", "affinity_rules", "anti_affinity_rules",
				"extra_disks", "tags", "port_forwarding", "user_access", "snapshot", "pci_devices", "preferred_cpu", "security_groups") {
				diff.SetNewComputed("updated_time")
				diff.SetNewComputed("updated_by")
			}
			if diff.HasChanges("pin_to_node") {
				diff.SetNewComputed("pinned")
			}
			if diff.HasChanges("started") {
				diff.SetNewComputed("tech_status")
				diff.SetNewComputed("updated_time")
				diff.SetNewComputed("updated_by")
			}
			return nil
		},

		CreateContext: resourceComputeCreate,
		ReadContext:   resourceComputeRead,
		UpdateContext: resourceComputeUpdate,
		DeleteContext: resourceComputeDelete,

		Importer: &schema.ResourceImporter{
			StateContext: schema.ImportStatePassthroughContext,
		},

		Timeouts: &schema.ResourceTimeout{
			Create:  &constants.Timeout600s,
			Read:    &constants.Timeout300s,
			Update:  &constants.Timeout300s,
			Delete:  &constants.Timeout300s,
			Default: &constants.Timeout300s,
		},

		Schema: resourceComputeSchemaMake(),
		StateUpgraders: []schema.StateUpgrader{
			{
				Type:    resourceComputeResourceV1().CoreConfigSchema().ImpliedType(),
				Upgrade: resourceCompueteStateUpgradeV1,
				Version: 1,
			},
		},
	}
}
