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

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"

	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/compute"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/disks"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"

	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func utilityComputeEnabled(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
	enabled := d.Get("enabled").(bool)

	if enabled {
		req := compute.EnableRequest{
			ComputeID: computeId,
		}

		if _, err := c.CloudBroker().Compute().Enable(ctx, req); err != nil {
			return err
		}
	} else {
		req := compute.DisableRequest{
			ComputeID: computeId,
		}

		if _, err := c.CloudBroker().Compute().Disable(ctx, req); err != nil {
			return err
		}
	}
	log.Debugf("resourceComputeUpdate: enable=%v Compute ID %s after completing its resource configuration", enabled, d.Id())

	return nil
}

func utilityComputeStarted(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	if d.Get("started").(bool) {
		req := compute.StartRequest{
			ComputeID: computeId,
		}
		if altBootId, ok := d.Get("alt_boot_id").(int); ok {
			req.AltBootID = uint64(altBootId)
		}
		if stackId, ok := d.Get("stack_id").(int); ok {
			req.StackID = uint64(stackId)
		}
		if _, err := c.CloudBroker().Compute().Start(ctx, req); err != nil {
			return err
		}
	} else {
		req := compute.StopRequest{
			ComputeID: computeId,
		}
		if force, ok := d.Get("force_stop").(bool); ok {
			req.Force = force
		}
		if depresent, ok := d.Get("depresent").(bool); ok {
			req.Depresent = depresent
		}
		if _, err := c.CloudBroker().Compute().Stop(ctx, req); err != nil {
			return err
		}
	}
	return nil
}

func utilityComputeResize(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	resizeReq := compute.ResizeRequest{
		ComputeID: computeId,
	}
	forceResize, ok := d.GetOk("force_resize")
	if ok {
		resizeReq.Force = forceResize.(bool)
	}

	doUpdate := false

	oldCpu, newCpu := d.GetChange("cpu")
	if oldCpu.(int) > newCpu.(int) && !forceResize.(bool) {
		return fmt.Errorf("сannot resize compute ID %d: enable 'force_resize' to reduce compute vCPUs", computeId)
	}
	if oldCpu.(int) != newCpu.(int) {
		resizeReq.CPU = uint64(newCpu.(int))
		doUpdate = true
	} else {
		resizeReq.CPU = 0
	}

	oldRam, newRam := d.GetChange("ram")
	if oldRam.(int) != newRam.(int) {
		resizeReq.RAM = uint64(newRam.(int))
		doUpdate = true
	} else {
		resizeReq.RAM = 0
	}

	if doUpdate {
		log.Debugf("resourceComputeUpdate: changing CPU %d -> %d and/or RAM %d -> %d",
			oldCpu.(int), newCpu.(int),
			oldRam.(int), newRam.(int))
		_, err := c.CloudBroker().Compute().Resize(ctx, resizeReq)
		if err != nil {
			return err
		}
	}

	return nil
}

func utilityComputeBootDiskResize(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldSize, newSize := d.GetChange("boot_disk_size")
	if oldSize.(int) < newSize.(int) {
		req := compute.DiskResizeRequest{ComputeID: computeId, Size: uint64(newSize.(int))}
		if diskId, ok := d.GetOk("boot_disk_id"); ok {
			req.DiskID = uint64(diskId.(int))

		} else {
			bootDisk, err := utilityComputeBootDiskCheckPresence(ctx, d, m)
			if err != nil {
				return err
			}

			req.DiskID = bootDisk.ID
		}

		log.Debugf("resourceComputeUpdate: compute ID %s, boot disk ID %d resize %d -> %d",
			d.Id(), d.Get("boot_disk_id").(int), oldSize.(int), newSize.(int))

		_, err := c.CloudBroker().Compute().DiskResize(ctx, req)
		if err != nil {
			return err
		}

	} else if oldSize.(int) > newSize.(int) {
		log.Warnf("resourceComputeUpdate: compute ID %s - shrinking boot disk is not allowed", d.Id())
	}

	return nil
}

func utilityComputeUpdateDisks(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	deletedDisks := make([]interface{}, 0)
	addedDisks := make([]interface{}, 0)
	resizedDisks := make([]interface{}, 0)
	renamedDisks := make([]interface{}, 0)
	presentNewDisks := make([]interface{}, 0)
	presentOldDisks := make([]interface{}, 0)

	oldDisks, newDisks := d.GetChange("disks")
	oldConv := oldDisks.([]interface{})
	newConv := newDisks.([]interface{})

	for _, el := range oldConv {
		changeNodes, newEl := isChangeNodesDisk(newConv, el)
		if changeNodes {
			presentNewDisks = append(presentNewDisks, newEl)
			presentOldDisks = append(presentOldDisks, el)
		}
		// !isRenameDisk(newConv, el) && !isResizeDisk(newConv, el) are required in case two or more disks are being created and their disk_id is the same (=0)
		if !isContainsDisk(newConv, el) && !isRenameDisk(newConv, el) && !isResizeDisk(newConv, el) {
			flag := false
			extraDisks := d.Get("extra_disks").(*schema.Set).List()
			delDisk := el.(map[string]interface{})
			delDiskId := delDisk["disk_id"].(int)

			for _, extraDiskId := range extraDisks {
				if extraDiskId.(int) == delDiskId {
					flag = true
					break
				}
			}

			if !flag {
				deletedDisks = append(deletedDisks, el)
			} else {
				log.Debugf("disk %d will not be deleted because it is present in the extra_disks block", delDiskId)
			}
		}
	}

	for _, el := range newConv {
		if !isContainsDisk(oldConv, el) {
			addedDisks = append(addedDisks, el)
		}
		if isResizeDisk(oldConv, el) {
			resizedDisks = append(resizedDisks, el)
		}
		if isRenameDisk(oldConv, el) {
			renamedDisks = append(renamedDisks, el)
		}
	}

	if len(deletedDisks) > 0 {
		stopReq := compute.StopRequest{
			ComputeID: computeId,
			Force:     false,
		}

		if depresent, ok := d.Get("depresent").(bool); ok {
			stopReq.Depresent = depresent
		}

		_, err := c.CloudBroker().Compute().Stop(ctx, stopReq)
		if err != nil {
			return err
		}

		for _, disk := range deletedDisks {
			diskConv := disk.(map[string]interface{})
			if diskConv["disk_type"].(string) == "B" {
				continue
			}

			req := compute.DiskDelRequest{
				ComputeID:   computeId,
				DiskID:      uint64(diskConv["disk_id"].(int)),
				Permanently: diskConv["permanently"].(bool),
			}

			_, err := c.CloudBroker().Compute().DiskDel(ctx, req)
			if err != nil {
				return err
			}
		}
		req := compute.StartRequest{
			ComputeID: computeId,
			AltBootID: 0,
		}
		_, err = c.CloudBroker().Compute().Start(ctx, req)
		if err != nil {
			return err
		}
	}

	if len(addedDisks) > 0 {
		for _, disk := range addedDisks {
			diskConv := disk.(map[string]interface{})
			if diskConv["disk_type"].(string) == "B" {
				continue
			}
			req := compute.DiskAddRequest{
				ComputeID: computeId,
				DiskName:  diskConv["disk_name"].(string),
				Size:      uint64(diskConv["size"].(int)),
			}
			if diskConv["sep_id"].(int) != 0 {
				req.SepID = uint64(diskConv["sep_id"].(int))
			}
			if diskConv["disk_type"].(string) != "" {
				req.DiskType = diskConv["disk_type"].(string)
			}
			if diskConv["pool"].(string) != "" {
				req.Pool = diskConv["pool"].(string)
			}
			if diskConv["desc"].(string) != "" {
				req.Description = diskConv["desc"].(string)
			}
			if diskConv["image_id"].(int) != 0 {
				req.ImageID = uint64(diskConv["image_id"].(int))
			}
			diskID, err := c.CloudBroker().Compute().DiskAdd(ctx, req)
			if err != nil {
				return err
			}
			if nodeIDs, ok := diskConv["node_ids"]; ok {
				presentIDs := nodeIDs.(*schema.Set).List()
				if len(presentIDs) > 0 {
					log.Debugf("resourceComputeUpdate: start presents new disk ID:%d to nodes", diskID)
				}
				for _, presentID := range presentIDs {
					nodeID := uint64(presentID.(int))
					req := disks.PresentRequest{
						DiskID: diskID,
						NodeID: nodeID,
					}
					_, err := c.CloudBroker().Disks().Present(ctx, req)
					if err != nil {
						return err
					}
				}
			}
			if err != nil {
				return err
			}
		}
	}

	if len(resizedDisks) > 0 {
		for _, disk := range resizedDisks {
			diskConv := disk.(map[string]interface{})
			if diskConv["disk_type"].(string) == "B" {
				continue
			}
			req := compute.DiskResizeRequest{
				ComputeID: computeId,
				DiskID:    uint64(diskConv["disk_id"].(int)),
				Size:      uint64(diskConv["size"].(int)),
			}

			_, err := c.CloudBroker().Compute().DiskResize(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	if len(renamedDisks) > 0 {
		for _, disk := range renamedDisks {
			diskConv := disk.(map[string]interface{})

			req := disks.RenameRequest{
				DiskID: uint64(diskConv["disk_id"].(int)),
				Name:   diskConv["disk_name"].(string),
			}

			_, err := c.CloudBroker().Disks().Rename(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	for i := range presentNewDisks {
		newDisk := presentNewDisks[i].(map[string]interface{})
		oldDisk := presentOldDisks[i].(map[string]interface{})
		newArr := newDisk["node_ids"]
		oldArr := oldDisk["node_ids"]
		diskID := uint64(newDisk["disk_id"].(int))
		presentIDs := (newArr.(*schema.Set).Difference(oldArr.(*schema.Set))).List()
		depresentIDs := (oldArr.(*schema.Set).Difference(newArr.(*schema.Set))).List()
		for _, presentID := range presentIDs {
			nodeID := uint64(presentID.(int))

			req := disks.PresentRequest{
				DiskID: diskID,
				NodeID: nodeID,
			}
			log.Debugf("resourceComputeUpdate: start presents disk ID:%d from nodes %d", req.DiskID, req.NodeID)
			_, err := c.CloudBroker().Disks().Present(ctx, req)
			if err != nil {
				return err
			}
		}

		for _, depresentID := range depresentIDs {
			nodeID := uint64(depresentID.(int))

			req := disks.DepresentRequest{
				DiskID: diskID,
				NodeID: nodeID,
			}
			log.Debugf("resourceComputeUpdate: start depresents disk ID:%d from nodes %d", req.DiskID, req.NodeID)
			_, err := c.CloudBroker().Disks().Depresent(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func utilityComputeExtraDisksConfigure(ctx context.Context, d *schema.ResourceData, m interface{}, do_delta bool) error {
	c := m.(*controller.ControllerCfg)

	log.Debugf("utilityComputeExtraDisksConfigure: called for Compute ID %s with do_delta = %t", d.Id(), do_delta)

	old_set, new_set := d.GetChange("extra_disks")

	apiErrCount := 0
	var lastSavedError error

	if !do_delta {
		if new_set.(*schema.Set).Len() < 1 {
			return nil
		}

		for _, disk := range new_set.(*schema.Set).List() {
			computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
			req := compute.DiskAttachRequest{
				ComputeID: computeId,
				DiskID:    uint64(disk.(int)),
			}

			_, err := c.CloudBroker().Compute().DiskAttach(ctx, req)
			if err != nil {
				apiErrCount++
				lastSavedError = err
			}
		}

		if apiErrCount > 0 {
			log.Errorf("utilityComputeExtraDisksConfigure: there were %d error(s) when attaching disks to Compute ID %s. Last error was: %s",
				apiErrCount, d.Id(), lastSavedError)
			return lastSavedError
		}

		return nil
	}

	detach_set := old_set.(*schema.Set).Difference(new_set.(*schema.Set))
	log.Debugf("utilityComputeExtraDisksConfigure: detach set has %d items for Compute ID %s", detach_set.Len(), d.Id())

	if detach_set.Len() > 0 {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

		for _, diskId := range detach_set.List() {
			req := compute.DiskDetachRequest{
				ComputeID: computeId,
				DiskID:    uint64(diskId.(int)),
			}
			_, err := c.CloudBroker().Compute().DiskDetach(ctx, req)
			if err != nil {
				log.Errorf("utilityComputeExtraDisksConfigure: failed to detach disk ID %d from Compute ID %s: %s", diskId.(int), d.Id(), err)
				apiErrCount++
				lastSavedError = err
			}
		}

	}

	attach_set := new_set.(*schema.Set).Difference(old_set.(*schema.Set))
	log.Debugf("utilityComputeExtraDisksConfigure: attach set has %d items for Compute ID %s", attach_set.Len(), d.Id())
	for _, diskId := range attach_set.List() {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.DiskAttachRequest{
			ComputeID: computeId,
			DiskID:    uint64(diskId.(int)),
		}

		_, err := c.CloudBroker().Compute().DiskAttach(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeExtraDisksConfigure: failed to attach disk ID %d to Compute ID %s: %s", diskId.(int), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	if apiErrCount > 0 {
		log.Errorf("utilityComputeExtraDisksConfigure: there were %d error(s) when managing disks of Compute ID %s. Last error was: %s",
			apiErrCount, d.Id(), lastSavedError)
		return lastSavedError
	}

	return nil
}

func utilityComputeCheckPresence(ctx context.Context, d *schema.ResourceData, m interface{}) (*compute.RecordCompute, error) {
	c := m.(*controller.ControllerCfg)
	req := compute.GetRequest{}

	if d.Id() != "" {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req.ComputeID = computeId
	} else {
		req.ComputeID = uint64(d.Get("compute_id").(int))
	}

	res, err := c.CloudBroker().Compute().Get(ctx, req)
	if err != nil {
		return nil, err
	}

	return res, nil
}

func utilityComputePCIDevicesList(ctx context.Context, d *schema.ResourceData, m interface{}) (*compute.ListPCIDevices, error) {
	c := m.(*controller.ControllerCfg)
	req := compute.ListPCIDeviceRequest{}

	if d.Id() != "" {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req.ComputeID = computeId
	} else {
		req.ComputeID = uint64(d.Get("compute_id").(int))
	}

	res, err := c.CloudBroker().Compute().ListPCIDevice(ctx, req)
	if err != nil {
		return nil, err
	}

	return res, nil
}

func findBootDisk(disks compute.ListDisks) *compute.ItemDisk {
	for _, disk := range disks {
		if disk.Type == "B" {
			return &disk
		}
	}
	return &compute.ItemDisk{}
}

func networkSubresIPAddreDiffSupperss(key, oldVal, newVal string, d *schema.ResourceData) bool {
	if newVal != "" && newVal != oldVal {
		log.Debugf("networkSubresIPAddreDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=FALSE", key, oldVal, newVal)
		return false
	}
	log.Debugf("networkSubresIPAddreDiffSupperss: key=%s, oldVal=%q, newVal=%q -> suppress=TRUE", key, oldVal, newVal)
	return true // suppress difference
}

func utilityComputeNetworksConfigure(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	oldSet, newSet := d.GetChange("network")

	oldList := oldSet.(*schema.Set).List()
	newList := newSet.(*schema.Set).List()

	detachMap, changeIpMap, attachMap := differenceNetwork(oldList, newList)

	apiErrCount := 0
	var lastSavedError error

	log.Debugf("utilityComputeNetworksConfigure: detach set has %d items for Compute ID %s", len(detachMap), d.Id())
	for _, netData := range detachMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.NetDetachRequest{
			ComputeID: computeId,
			IPAddr:    netData["ip_address"].(string),
			MAC:       netData["mac"].(string),
		}

		_, err := c.CloudBroker().Compute().NetDetach(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to detach net ID %d of type %s from Compute ID %s: %s",
				netData["net_id"].(int), netData["net_type"].(string), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	log.Debugf("utilityComputeNetworksConfigure: changeIp set has %d items for Compute ID %s", len(changeIpMap), d.Id())
	for _, netData := range changeIpMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.ChangeIPRequest{
			ComputeID: computeId,
			NetType:   netData["net_type"].(string),
			NetID:     uint64(netData["net_id"].(int)),
			IPAddr:    netData["ip_address"].(string),
		}

		_, err := c.CloudBroker().Compute().ChangeIP(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to change net ID %d of type %s from Compute ID %s: %s",
				netData["net_id"].(int), netData["net_type"].(string), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	needStart := false

	oldLibvirtSet, newLibvirtSet := d.GetChange("libvirt_settings")
	addedLibvirtSettings := (newLibvirtSet.(*schema.Set).Difference(oldLibvirtSet.(*schema.Set))).List()
	libvirtSettingsMap := addAttachedNetwork(addedLibvirtSettings, newLibvirtSet.(*schema.Set).List(), attachMap)

	if oldSet.(*schema.Set).Len() == len(detachMap) || oldSet.(*schema.Set).Len() == 0 || len(libvirtSettingsMap) > 0 || hasDPDKnetwork(attachMap) {
		if err := utilityComputeStop(ctx, d, m); err != nil {
			apiErrCount++
			lastSavedError = err
		}
		if start := d.Get("started"); start.(bool) {
			needStart = true
		}
	}

	sort.Slice(attachMap, func(i, j int) bool {
		weightI := attachMap[i]["weight"].(int)
		weightJ := attachMap[j]["weight"].(int)
		if weightI == 0 {
			return false
		}
		if weightJ == 0 {
			return true
		}
		return weightI < weightJ
	})
	log.Debugf("utilityComputeNetworksConfigure: attach set has %d items for Compute ID %s", len(attachMap), d.Id())
	for _, netData := range attachMap {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		req := compute.NetAttachRequest{
			ComputeID: computeId,
			NetType:   netData["net_type"].(string),
			NetID:     uint64(netData["net_id"].(int)),
		}

		if req.NetType == "DPDK" {
			req.MTU = uint64(netData["mtu"].(int))
		}

		if netData["ip_address"].(string) != "" {
			req.IPAddr = netData["ip_address"].(string)
		}

		_, err := c.CloudBroker().Compute().NetAttach(ctx, req)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to attach net ID %d of type %s to Compute ID %s: %s",
				netData["net_id"].(int), netData["net_type"].(string), d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}
	}

	if len(libvirtSettingsMap) > 0 {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		computeRec, err := utilityComputeCheckPresence(ctx, d, m)
		if err != nil {
			log.Errorf("utilityComputeNetworksConfigure: failed to read information about compute with ID %s: %s",
				d.Id(), err)
			apiErrCount++
			lastSavedError = err
		}

		if computeRec != nil {
			log.Debugf("utilityComputeNetworksConfigure: libvirt virtio set has %d items for Compute ID %s", len(attachMap), d.Id())
			for _, libvirtSetting := range libvirtSettingsMap {
				netType := libvirtSetting["net_type"].(string)
				netId := uint64(libvirtSetting["net_id"].(int))
				var mac string
				for _, iface := range computeRec.Interfaces {
					if iface.NetID == netId && iface.NetType == netType {
						mac = iface.MAC
						break
					}
				}
				log.Debugf("utilityComputeNetworksConfigure: Configure libvirt virtio interface parameters on Network with type %s and id %d", netType, netId)
				req := compute.SetNetConfigRequest{
					ComputeID:   computeId,
					MAC:         mac,
					TXMode:      libvirtSetting["txmode"].(string),
					IOEventFD:   libvirtSetting["ioeventfd"].(string),
					EventIDx:    libvirtSetting["event_idx"].(string),
					Queues:      uint64(libvirtSetting["queues"].(int)),
					RXQueueSize: uint64(libvirtSetting["rx_queue_size"].(int)),
					TXQueueSize: uint64(libvirtSetting["tx_queue_size"].(int)),
				}

				_, err := c.CloudBroker().Compute().SetNetConfig(ctx, req)
				if err != nil {
					log.Errorf("utilityComputeNetworksConfigure: failed to set net config to net ID %d of type %s to Compute ID %s: %s",
						netId, netType, d.Id(), err)
					apiErrCount++
					lastSavedError = err
				}
			}
		}
	}

	if needStart {
		computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
		if numErr, err := utilityComputeStart(ctx, computeId, m); err != nil {
			apiErrCount += numErr
			lastSavedError = err
		}
	}

	if apiErrCount > 0 {
		log.Errorf("utilityComputeNetworksConfigure: there were %d error(s) when managing networks of Compute ID %s. Last error was: %s",
			apiErrCount, d.Id(), lastSavedError)
		return lastSavedError
	}

	return nil
}

func differenceNetwork(oldList, newList []interface{}) (detachMap, changeIpMap, attachMap []map[string]interface{}) {
	attachMap = make([]map[string]interface{}, 0)
	changeIpMap = make([]map[string]interface{}, 0)
	detachMap = make([]map[string]interface{}, 0)
	for _, oldNetwork := range oldList {
		oldMap := oldNetwork.(map[string]interface{})
		found := false
		for _, newNetwork := range newList {
			newMap := newNetwork.(map[string]interface{})
			if newMap["net_type"] == oldMap["net_type"] && newMap["net_id"] == oldMap["net_id"] && newMap["weight"] == oldMap["weight"] && (newMap["mtu"] == oldMap["mtu"] || newMap["mtu"].(int) == 0) {
				if (newMap["net_type"].(string) == "EXTNET" || newMap["net_type"].(string) == "VINS") && (newMap["ip_address"] != oldMap["ip_address"] && newMap["ip_address"].(string) != "") {
					changeIpMap = append(changeIpMap, newMap)
					found = true
					break
				} else if newMap["ip_address"] == oldMap["ip_address"] || newMap["ip_address"].(string) == "" {
					found = true
					break
				}
			}
		}
		if found {
			continue
		}
		detachMap = append(detachMap, oldMap)
	}

	for _, newNetwork := range newList {
		newMap := newNetwork.(map[string]interface{})
		found := false
		for _, oldNetwork := range oldList {
			oldMap := oldNetwork.(map[string]interface{})
			if newMap["net_type"] == oldMap["net_type"] && newMap["net_id"] == oldMap["net_id"] && newMap["weight"] == oldMap["weight"] && (newMap["mtu"] == oldMap["mtu"] || newMap["mtu"].(int) == 0) {
				if newMap["ip_address"] == oldMap["ip_address"] || newMap["ip_address"].(string) == "" ||
					((newMap["net_type"].(string) == "EXTNET" || newMap["net_type"].(string) == "VINS") &&
						newMap["ip_address"] != oldMap["ip_address"] && newMap["ip_address"].(string) != "") {
					found = true
					break
				}
			}
		}
		if found {
			continue
		}
		attachMap = append(attachMap, newMap)
	}

	return
}

func hasDPDKnetwork(networkAttachMap []map[string]interface{}) bool {
	for _, elem := range networkAttachMap {
		if elem["net_type"].(string) == "DPDK" {
			return true
		}
	}
	return false
}

// addAttachedNetwork adds libvirt_settings of attached networks to the libvirt_settings settings list
func addAttachedNetwork(addedLibvirtSettings []interface{}, newLibvirtSettings []interface{}, networkAttachMap []map[string]interface{}) (addedLibvirtSettingsMap []map[string]interface{}) {
	addedLibvirtSettingsMap = make([]map[string]interface{}, 0)

	for _, attach := range networkAttachMap {
		found := false
		for _, elem := range addedLibvirtSettings {
			addedLVSMap := elem.(map[string]interface{})
			if attach["net_id"] == addedLVSMap["net_id"] && attach["net_type"] == addedLVSMap["net_type"] {
				found = true
				break
			}
		}
		if found {
			continue
		}
		for _, elem := range newLibvirtSettings {
			newVirtSettingMap := elem.(map[string]interface{})
			if attach["net_id"] == newVirtSettingMap["net_id"] && attach["net_type"] == newVirtSettingMap["net_type"] {
				addedLibvirtSettingsMap = append(addedLibvirtSettingsMap, newVirtSettingMap)
				found = true
				break
			}
		}
	}

	for _, elem := range addedLibvirtSettings {
		addedLVSMap := elem.(map[string]interface{})
		addedLibvirtSettingsMap = append(addedLibvirtSettingsMap, addedLVSMap)
	}

	return
}

func utilityComputeUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	req := compute.UpdateRequest{
		ComputeID: computeId,
	}

	if d.HasChange("name") {
		req.Name = d.Get("name").(string)
	}
	if d.HasChange("description") {
		req.Description = d.Get("description").(string)
	}
	if d.HasChange("numa_affinity") {
		req.NumaAffinity = d.Get("numa_affinity").(string)
	}
	if d.HasChange("cpu_pin") {
		req.CPUPin = d.Get("cpu_pin").(bool)
	}
	if d.HasChange("hp_backed") {
		req.HPBacked = d.Get("hp_backed").(bool)
	}

	if d.HasChange("chipset") {
		req.Chipset = d.Get("chipset").(string)
	}

	// Note bene: numa_affinity, cpu_pin and hp_backed are not allowed to be changed for compute in STARTED tech status.
	// If STARTED, we need to stop it before update
	var isStopRequired bool
	if d.HasChanges("numa_affinity", "cpu_pin", "hp_backed") && d.Get("started").(bool) {
		isStopRequired = true
	}
	if isStopRequired {
		stopReq := compute.StopRequest{
			ComputeID: computeId,
			Force:     false,
		}
		if depresent, ok := d.Get("depresent").(bool); ok {
			stopReq.Depresent = depresent
		}
		if _, err := c.CloudBroker().Compute().Stop(ctx, stopReq); err != nil {
			return err
		}
	}

	// perform update
	if _, err := c.CloudBroker().Compute().Update(ctx, req); err != nil {
		return err
	}

	// If used to be STARTED, we need to start it after update
	if isStopRequired {
		if _, err := c.CloudBroker().Compute().Start(ctx, compute.StartRequest{ComputeID: computeId}); err != nil {
			return err
		}
	}

	return nil
}

func utilityComputeUpdateAffinityLabel(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	affinityLabel := d.Get("affinity_label").(string)
	if affinityLabel == "" {
		req := compute.AffinityLabelRemoveRequest{
			ComputeIDs: []uint64{computeId},
		}

		_, err := c.CloudBroker().Compute().AffinityLabelRemove(ctx, req)
		if err != nil {
			return err
		}
	}

	req := compute.AffinityLabelSetRequest{
		ComputeIDs:    []uint64{computeId},
		AffinityLabel: affinityLabel,
	}

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

	return nil
}

func utilityComputeUpdateAffinityRules(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	deletedAR := make([]interface{}, 0)
	addedAR := make([]interface{}, 0)

	oldAR, newAR := d.GetChange("affinity_rules")
	oldConv := oldAR.([]interface{})
	newConv := newAR.([]interface{})

	if len(newConv) == 0 {
		req := compute.AffinityRulesClearRequest{
			ComputeIDs: []uint64{computeId},
		}

		_, err := c.CloudBroker().Compute().AffinityRulesClear(ctx, req)
		if err != nil {
			return err
		}
	} else {
		for _, el := range oldConv {
			if !isContainsAR(newConv, el) {
				deletedAR = append(deletedAR, el)
			}
		}
		for _, el := range newConv {
			if !isContainsAR(oldConv, el) {
				addedAR = append(addedAR, el)
			}
		}

		if len(deletedAR) > 0 {
			for _, ar := range deletedAR {
				arConv := ar.(map[string]interface{})
				req := compute.AffinityRuleRemoveRequest{
					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().AffinityRuleRemove(ctx, req)
				if err != nil {
					return err
				}
			}
		}
		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 {
					return err
				}
			}
		}
	}

	return nil
}

func utilityComputeUpdateAntiAffinityRules(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	deletedAR := make([]interface{}, 0)
	addedAR := make([]interface{}, 0)

	oldAR, newAR := d.GetChange("anti_affinity_rules")
	oldConv := oldAR.([]interface{})
	newConv := newAR.([]interface{})

	if len(newConv) == 0 {
		req := compute.AntiAffinityRulesClearRequest{
			ComputeIDs: []uint64{computeId},
		}

		_, err := c.CloudBroker().Compute().AntiAffinityRulesClear(ctx, req)
		if err != nil {
			return err
		}
	} else {
		for _, el := range oldConv {
			if !isContainsAR(newConv, el) {
				deletedAR = append(deletedAR, el)
			}
		}
		for _, el := range newConv {
			if !isContainsAR(oldConv, el) {
				addedAR = append(addedAR, el)
			}
		}

		if len(deletedAR) > 0 {
			for _, ar := range deletedAR {
				arConv := ar.(map[string]interface{})
				req := compute.AntiAffinityRuleRemoveRequest{
					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().AntiAffinityRuleRemove(ctx, req)
				if err != nil {
					return err
				}
			}
		}
		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 {
					return err
				}
			}
		}
	}

	return nil
}

func utilityComputeUpdatePciDevices(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)
	oldSet, newSet := d.GetChange("pci_devices")
	deletedDevices := (oldSet.(*schema.Set).Difference(newSet.(*schema.Set))).List()
	if len(deletedDevices) > 0 {
		for _, ar := range deletedDevices {
			arConv := ar.(int)
			req := compute.DetachPCIDeviceRequest{
				ComputeID: computeId,
				DeviceID:  uint64(arConv),
			}
			_, err := c.CloudBroker().Compute().DetachPciDevice(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	added := (newSet.(*schema.Set).Difference(oldSet.(*schema.Set))).List()
	if len(added) > 0 {
		for _, ar := range added {
			arConv := ar.(int)
			req := compute.AttachPCIDeviceRequest{
				ComputeID: computeId,
				DeviceID:  uint64(arConv),
			}

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

	return nil
}

func utilityComputeUpdateTags(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldSet, newSet := d.GetChange("tags")
	deletedTags := (oldSet.(*schema.Set).Difference(newSet.(*schema.Set))).List()
	if len(deletedTags) > 0 {
		for _, tagInterface := range deletedTags {
			tagItem := tagInterface.(map[string]interface{})
			req := compute.TagRemoveRequest{
				ComputeIDs: []uint64{computeId},
				Key:        tagItem["key"].(string),
			}

			_, err := c.CloudBroker().Compute().TagRemove(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	addedTags := (newSet.(*schema.Set).Difference(oldSet.(*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 {
				return err
			}
		}
	}

	return nil
}

func utilityComputeUpdatePFW(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldSet, newSet := d.GetChange("port_forwarding")
	deletedPfws := (oldSet.(*schema.Set).Difference(newSet.(*schema.Set))).List()
	if len(deletedPfws) > 0 {
		for _, pfwInterface := range deletedPfws {
			pfwItem := pfwInterface.(map[string]interface{})
			req := compute.PFWDelRequest{
				ComputeID:       computeId,
				PublicPortStart: uint64(pfwItem["public_port_start"].(int)),
				Proto:           pfwItem["proto"].(string),
				RuleID:          uint64(pfwItem["rule_id"].(int)),
			}
			if pfwItem["local_port"].(int) != 0 {
				req.LocalBasePort = uint64(pfwItem["local_port"].(int))
			}
			if pfwItem["public_port_end"].(int) == -1 {
				req.PublicPortEnd = req.PublicPortStart
			} else {
				req.PublicPortEnd = uint64(pfwItem["public_port_end"].(int))
			}

			_, err := c.CloudBroker().Compute().PFWDel(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	addedPfws := (newSet.(*schema.Set).Difference(oldSet.(*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)),
				PublicPortEnd:   int64(pfwItem["public_port_end"].(int)),
				Proto:           pfwItem["proto"].(string),
			}
			if pfwItem["local_port"].(int) != 0 {
				req.LocalBasePort = uint64(pfwItem["local_port"].(int))
			}

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

	return nil
}

func utilityComputeRestore(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	restoreReq := compute.RestoreRequest{ComputeID: computeId}

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

	if _, ok := d.GetOk("enabled"); ok {
		if err := utilityComputeEnabled(ctx, d, m); err != nil {
			return err
		}
	}

	if _, ok := d.GetOk("started"); ok {
		if err := utilityComputeStarted(ctx, d, m); err != nil {
			return err
		}
	}

	return nil
}

func utilityComputeUpdateUserAccess(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldSet, newSet := d.GetChange("user_access")
	deletedUserAcess := (oldSet.(*schema.Set).Difference(newSet.(*schema.Set))).List()
	if len(deletedUserAcess) > 0 {
		for _, userAcessInterface := range deletedUserAcess {
			userAccessItem := userAcessInterface.(map[string]interface{})
			req := compute.UserRevokeRequest{
				ComputeID: computeId,
				Username:  userAccessItem["username"].(string),
			}

			_, err := c.CloudBroker().Compute().UserRevoke(ctx, req)
			if err != nil {
				return err
			}
		}
	}

	addedUserAccess := (newSet.(*schema.Set).Difference(oldSet.(*schema.Set))).List()
	if len(addedUserAccess) > 0 {
		for _, userAccessInterface := range addedUserAccess {
			userAccessItem := userAccessInterface.(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 {
				return err
			}
		}
	}

	return nil
}

func utilityComputeUpdateSnapshot(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldSet, newSet := d.GetChange("snapshot")
	deletedSnapshots := (oldSet.(*schema.Set).Difference(newSet.(*schema.Set))).List()
	if len(deletedSnapshots) > 0 {
		for _, snapshotInterface := range deletedSnapshots {
			snapshotItem := snapshotInterface.(map[string]interface{})
			req := compute.SnapshotDeleteRequest{
				ComputeID: computeId,
				Label:     snapshotItem["label"].(string),
			}

			asyncMode, ok := d.GetOk("snapshot_delete_async")
			if ok && asyncMode.(bool) {
				_, err := c.CloudBroker().Compute().SnapshotDeleteAsync(ctx, req)
				if err != nil {
					return err
				}
			} else {
				_, err := c.CloudBroker().Compute().SnapshotDelete(ctx, req)
				if err != nil {
					return err
				}
			}
		}
	}

	addedSnapshots := (newSet.(*schema.Set).Difference(oldSet.(*schema.Set))).List()
	if len(addedSnapshots) > 0 {
		for _, snapshotInterface := range addedSnapshots {
			snapshotItem := snapshotInterface.(map[string]interface{})
			req := compute.SnapshotCreateRequest{
				ComputeID: computeId,
				Label:     snapshotItem["label"].(string),
			}

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

	return nil
}

func utilityComputeRollback(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	if rollback, ok := d.GetOk("rollback"); ok {
		req := compute.StopRequest{
			ComputeID: computeId,
			Force:     false,
		}
		if depresent, ok := d.Get("depresent").(bool); ok {
			req.Depresent = depresent
		}

		_, err := c.CloudBroker().Compute().Stop(ctx, req)
		if err != nil {
			return err
		}

		rollbackInterface := rollback.(*schema.Set).List()[0]
		rollbackItem := rollbackInterface.(map[string]interface{})

		rollbackReq := compute.SnapshotRollbackRequest{
			ComputeID: computeId,
			Label:     rollbackItem["label"].(string),
		}

		_, err = c.CloudBroker().Compute().SnapshotRollback(ctx, rollbackReq)
		if err != nil {
			return err
		}

		startReq := compute.StartRequest{ComputeID: computeId}

		log.Debugf("utilityComputeRollback: starting compute %d", computeId)

		_, err = c.CloudBroker().Compute().Start(ctx, startReq)
		if err != nil {
			return err
		}
	}

	return nil
}

func utilityComputeUpdateCD(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldSet, newSet := d.GetChange("cd")
	deletedCd := (oldSet.(*schema.Set).Difference(newSet.(*schema.Set))).List()
	if len(deletedCd) > 0 {
		req := compute.CDEjectRequest{
			ComputeID: computeId,
		}

		_, err := c.CloudBroker().Compute().CDEject(ctx, req)
		if err != nil {
			return err
		}
	}

	addedCd := (newSet.(*schema.Set).Difference(oldSet.(*schema.Set))).List()
	if len(addedCd) > 0 {
		cdItem := addedCd[0].(map[string]interface{})
		req := compute.CDInsertRequest{
			ComputeID: computeId,
			CDROMID:   uint64(cdItem["cdrom_id"].(int)),
		}

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

func utilityComputePinToStack(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldPin, newPin := d.GetChange("pin_to_stack")
	if oldPin.(bool) && !newPin.(bool) {
		req := compute.UnpinFromStackRequest{
			ComputeID: computeId,
		}

		_, err := c.CloudBroker().Compute().UnpinFromStack(ctx, req)
		if err != nil {
			return err
		}
	}
	if !oldPin.(bool) && newPin.(bool) {
		req := compute.PinToStackRequest{
			ComputeID:     computeId,
			TargetStackID: uint64(d.Get("target_stack_id").(int)),
		}

		if force, ok := d.Get("force_pin").(bool); ok {
			req.Force = force
		}
		_, err := c.CloudBroker().Compute().PinToStack(ctx, req)
		if err != nil {
			return err
		}
	}
	return nil
}

func utilityComputePause(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldPause, newPause := d.GetChange("pause")
	if oldPause.(bool) && !newPause.(bool) {
		req := compute.ResumeRequest{
			ComputeID: computeId,
		}
		_, err := c.CloudBroker().Compute().Resume(ctx, req)
		if err != nil {
			return err
		}
	}
	if !oldPause.(bool) && newPause.(bool) {
		req := compute.PauseRequest{
			ComputeID: computeId,
		}

		_, err := c.CloudBroker().Compute().Pause(ctx, req)
		if err != nil {
			return err
		}
	}
	return nil
}

func utilityComputeReset(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldReset, newReset := d.GetChange("reset")
	if !oldReset.(bool) && newReset.(bool) {
		req := compute.ResetRequest{
			ComputeID: computeId,
		}
		_, err := c.CloudBroker().Compute().Reset(ctx, req)
		if err != nil {
			return err
		}
	}
	return nil
}

func utilityComputeUpdateImage(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	oldImage, newImage := d.GetChange("image_id")
	stopReq := compute.StopRequest{
		ComputeID: computeId,
		Force:     false,
	}
	if depresent, ok := d.Get("depresent").(bool); ok {
		stopReq.Depresent = depresent
	}

	_, err := c.CloudBroker().Compute().Stop(ctx, stopReq)
	if err != nil {
		return err
	}

	if oldImage.(int) != newImage.(int) {
		req := compute.RedeployRequest{
			ComputeID: computeId,
			ImageID:   uint64(newImage.(int)),
		}

		if diskSize, ok := d.GetOk("boot_disk_size"); ok {
			req.DiskSize = uint64(diskSize.(int))
		}
		if dataDisks, ok := d.GetOk("data_disks"); ok {
			req.DataDisks = dataDisks.(string)
		}
		if autoStart, ok := d.GetOk("auto_start"); ok {
			req.AutoStart = autoStart.(bool)
		}
		if forceStop, ok := d.GetOk("force_stop"); ok {
			req.ForceStop = forceStop.(bool)
		}

		_, err := c.CloudBroker().Compute().Redeploy(ctx, req)
		if err != nil {
			return err
		}
	}
	return nil
}

func utilityComputeUpdateCustomFields(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	computeId, _ := strconv.ParseUint(d.Id(), 10, 64)

	val := d.Get("custom_fields").(string)
	val = strings.ReplaceAll(val, "\\", "")
	val = strings.ReplaceAll(val, "\n", "")
	val = strings.ReplaceAll(val, "\t", "")
	val = strings.TrimSpace(val)

	if len(val) > 0 {
		req := compute.SetCustomFieldsRequest{
			ComputeID:    computeId,
			CustomFields: val,
		}

		_, err := c.CloudBroker().Compute().SetCustomFields(ctx, req)
		if err != nil {
			return err
		}
	} else {
		req := compute.DeleteCustomFieldsRequest{
			ComputeID: computeId,
		}

		_, err := c.CloudBroker().Compute().DeleteCustomFields(ctx, req)
		if err != nil {
			return err
		}
	}
	return nil
}

func utilityComputeStop(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	req := compute.StopRequest{
		Force: true,
	}
	req.ComputeID = uint64(d.Get("compute_id").(int))
	if depresent, ok := d.Get("depresent").(bool); ok {
		req.Depresent = depresent
	}

	log.Debugf("utilityComputeStop: stopping compute %d", req.ComputeID)
	_, err := c.CloudBroker().Compute().Stop(ctx, req)
	if err != nil {
		return err
	}
	return nil
}

func utilityComputeStart(ctx context.Context, computeID uint64, m interface{}) (int, error) {
	c := m.(*controller.ControllerCfg)
	startReq := compute.StartRequest{ComputeID: computeID}

	log.Debugf("utilityComputeStart: starting compute %d", computeID)
	_, err := c.CloudBroker().Compute().Start(ctx, startReq)
	if err != nil {
		return 1, err
	}
	return 0, nil
}

func utilityComputeCreatePresentDisk(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	var errs error
	diskList := d.Get("disks")
	presentArr := make([]interface{}, 0)
	for _, elem := range diskList.([]interface{}) {
		diskVal := elem.(map[string]interface{})
		presentArr = append(presentArr, diskVal["node_ids"])
	}

	computeRec, err := utilityComputeCheckPresence(ctx, d, m)
	bootDisk := findBootDisk(computeRec.Disks)
	if err != nil {
		errs = errors.Join(err)
	}
	computeDisksIDs := getComputeDiskIDs(computeRec.Disks, d.Get("disks").([]interface{}), d.Get("extra_disks").(*schema.Set).List(), bootDisk.ID)

	for i, diskID := range computeDisksIDs {
		if len(presentArr) <= i || presentArr[i] == nil {
			continue
		}
		presentIDs := presentArr[i].(*schema.Set).List()

		if len(presentIDs) > 0 {
			log.Debugf("resourceComputeCreate: start presents disk ID:%d to nodes", diskID)
		}

		for _, presentID := range presentIDs {
			nodeID := uint64(presentID.(int))

			req := disks.PresentRequest{
				DiskID: diskID.(uint64),
				NodeID: nodeID,
			}

			_, err := c.CloudBroker().Disks().Present(ctx, req)
			if err != nil {
				errs = errors.Join(err)
			}
		}
	}

	return errs
}

func isResizeDisk(els []interface{}, el interface{}) bool {
	for _, elOld := range els {
		elOldConv := elOld.(map[string]interface{})
		elConv := el.(map[string]interface{})
		if elOldConv["disk_id"].(int) == elConv["disk_id"].(int) &&
			elOldConv["size"].(int) != elConv["size"].(int) {
			return true
		}
	}
	return false
}

func isRenameDisk(els []interface{}, el interface{}) bool {
	for _, elOld := range els {
		elOldConv := elOld.(map[string]interface{})
		elConv := el.(map[string]interface{})
		if elOldConv["disk_id"].(int) == elConv["disk_id"].(int) &&
			elOldConv["disk_name"].(string) != elConv["disk_name"].(string) {
			return true
		}
	}
	return false
}

func isContainsDisk(els []interface{}, el interface{}) bool {
	for _, elOld := range els {
		elOldConv := elOld.(map[string]interface{})
		elConv := el.(map[string]interface{})
		if elOldConv["disk_id"].(int) == elConv["disk_id"].(int) {
			return true
		}
	}
	return false
}

// isChangeNodesDisk get slice of new disks values and current value disk,
// if need change nodes on disk returns true and new disk value, else return false and nil
func isChangeNodesDisk(els []interface{}, elOld interface{}) (bool, interface{}) {
	for _, elNew := range els {
		elNewConv := elNew.(map[string]interface{})
		elOldConv := elOld.(map[string]interface{})
		if elOldConv["disk_id"].(int) == elNewConv["disk_id"].(int) {
			newArr := elNewConv["node_ids"]
			oldArr := elOldConv["node_ids"]
			presentID := (newArr.(*schema.Set).Difference(oldArr.(*schema.Set))).List()
			depresentID := (oldArr.(*schema.Set).Difference(newArr.(*schema.Set))).List()
			if len(presentID) > 0 || len(depresentID) > 0 {
				return true, elNew
			}
			return false, nil
		}
	}
	return false, nil
}

func isContainsAR(els []interface{}, el interface{}) bool {
	for _, elOld := range els {
		elOldConv := elOld.(map[string]interface{})
		elConv := el.(map[string]interface{})
		if elOldConv["key"].(string) == elConv["key"].(string) &&
			elOldConv["value"].(string) == elConv["value"].(string) &&
			elOldConv["mode"].(string) == elConv["mode"].(string) &&
			elOldConv["topology"].(string) == elConv["topology"].(string) &&
			elOldConv["policy"].(string) == elConv["policy"].(string) {
			return true
		}
	}
	return false
}

func getComputeDiskIDs(disksList compute.ListDisks, disksBlocks, extraDisks []interface{}, bootDiskId uint64) []interface{} {
	res := make([]interface{}, 0)

	if len(disksBlocks) == 0 {
		return res
	}

	sort.Slice(disksList, func(i, j int) bool {
		return disksList[i].ID < disksList[j].ID
	})

	for _, disk := range disksList {
		if disk.ID == bootDiskId || findInExtraDisks(uint(disk.ID), extraDisks) { //skip main bootdisk and extraDisks
			continue
		}

		res = append(res, disk.ID)
	}

	return res
}
