package utilities

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

	"github.com/hashicorp/terraform-plugin-framework/diag"
	"github.com/hashicorp/terraform-plugin-framework/types"
	"github.com/hashicorp/terraform-plugin-log/tflog"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/compute"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/kvmx86"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/client"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/kvmvm/models"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/status"
)

func ComputeResourceCheckPresence(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) (*compute.RecordCompute, diag.Diagnostics) {
	tflog.Info(ctx, fmt.Sprintf("ComputeResourceCheckPresence: Get info about compute with ID - %v", plan.ID.ValueString()))

	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourceCheckPresence: Cannot parsed ID lb from state", err.Error())
		return nil, diags
	}

	computeItem, err := c.CloudAPI().Compute().Get(ctx, compute.GetRequest{ComputeID: computeId})
	if err != nil {
		diags.AddError(fmt.Sprintf("ComputeResourceCheckPresence: Cannot get info about compute with ID %v", computeId), err.Error())
		return nil, diags
	}

	return computeItem, nil
}

func ResourceComputePCIDevicesListCheckPresence(ctx context.Context, id uint64, c *client.Client) (*compute.ListPCIDevices, error) {
	tflog.Info(ctx, "Get PCI devices info")

	req := compute.ListPCIDeviceRequest{
		ComputeID: id,
	}

	tflog.Info(ctx, "Check req", map[string]any{"req": req})

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

	tflog.Info(ctx, "Getting PCI devices info, successfully")
	return pciDevicesList, nil
}

func ComputeResourceUserAccessGet(ctx context.Context, computeID uint64, c *client.Client) (*compute.ListUsers, error) {
	tflog.Info(ctx, "Get compute user access info")

	req := compute.UserListRequest{
		ComputeID: computeID,
	}

	tflog.Info(ctx, "Check req", map[string]any{"req": req})

	userList, err := c.CloudAPI().Compute().UserList(ctx, req)
	if err != nil {
		return nil, err
	}

	tflog.Info(ctx, "Getting compute user access info, successfully")
	return userList, nil
}

func ComputeResourcePFWListGet(ctx context.Context, computeID uint64, c *client.Client) (*compute.ListPFWs, error) {
	tflog.Info(ctx, "Get compute pfw list info")

	req := compute.PFWListRequest{
		ComputeID: computeID,
	}

	tflog.Info(ctx, "Check req", map[string]any{"req": req})

	pfwList, err := c.CloudAPI().Compute().PFWList(ctx, req)
	if err != nil {
		return nil, err
	}

	tflog.Info(ctx, "Getting compute pfw list info, successfully")
	return pfwList, nil
}

func ComputeResourceCheckDetachDisks(state *models.ResourceComputeModel, recordCompute *compute.RecordCompute) bool {
	bootDiskId := uint64(state.BootDiskId.ValueInt64())
	extraDiskList := state.ExtraDisks.Elements()
	for _, disk := range recordCompute.Disks {
		if disk.ID == bootDiskId {
			continue
		}
		for _, extraDisk := range extraDiskList {
			if uint64(extraDisk.(types.Int64).ValueInt64()) == disk.ID {
				return false
			}
		}
		return true
	}
	return true
}

func ComputeResourceBootDiskCheckPresence(ctx context.Context, state *models.ResourceComputeModel, c *client.Client) (*compute.ItemComputeDisk, diag.Diagnostics) {
	tflog.Info(ctx, "ComputeResourceBootDiskCheckPresence: Get info about boot disk")

	diags := diag.Diagnostics{}

	recordItemCompute, diags := ComputeResourceCheckPresence(ctx, state, c)
	if diags.HasError() {
		return nil, diags
	}

	bootDisk := &compute.ItemComputeDisk{}
	for _, disk := range recordItemCompute.Disks {
		if disk.Name == "bootdisk" {
			*bootDisk = disk
			break
		}
	}

	return bootDisk, nil
}

func CreateResourceCompute(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) (uint64, diag.Diagnostics) {
	tflog.Info(ctx, fmt.Sprintf("CreateResourceCompute: Start create ResourceCompute: name %s", plan.Name.ValueString()))

	diags := diag.Diagnostics{}

	createReqX86 := kvmx86.CreateRequest{Start: false}

	if !plan.Description.IsUnknown() {
		createReqX86.Description = plan.Description.ValueString()
	}

	if !plan.SepId.IsUnknown() {
		createReqX86.SepID = uint64(plan.SepId.ValueInt64())
	}

	if !plan.Pool.IsUnknown() {
		createReqX86.Pool = plan.Pool.ValueString()
	}

	if !plan.IpaType.IsNull() {
		createReqX86.IPAType = plan.IpaType.ValueString()
	}

	if !plan.BootDiskSize.IsNull() {
		createReqX86.BootDisk = uint64(plan.BootDiskSize.ValueInt64())
	}

	if !plan.IS.IsNull() {
		createReqX86.IS = plan.IS.ValueString()
	}

	if !plan.Chipset.IsUnknown() {
		createReqX86.Chipset = plan.Chipset.ValueString()
	}

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

	if !plan.Network.IsNull() {
		networkList := plan.Network.Elements()
		sort.Slice(networkList, func(i, j int) bool {
			weightI := networkList[i].(types.Object).Attributes()["weight"].(types.Int64).ValueInt64()
			weightJ := networkList[j].(types.Object).Attributes()["weight"].(types.Int64).ValueInt64()
			if weightI == 0 {
				return false
			}
			if weightJ == 0 {
				return true
			}
			return weightI < weightJ
		})
		interfaces := make([]kvmx86.Interface, 0)
		for _, elem := range networkList {
			objVal := elem.(types.Object)
			elemMap := objVal.Attributes()
			reqInterface := kvmx86.Interface{
				NetType: strings.ToUpper(elemMap["net_type"].(types.String).ValueString()),
				NetID:   uint64(elemMap["net_id"].(types.Int64).ValueInt64()),
			}
			if reqInterface.NetType == "DPDK" {
				reqInterface.MTU = uint64(elemMap["mtu"].(types.Int64).ValueInt64())
			}
			ipaddr, ipSet := elemMap["ip_address"]
			if ipSet {
				reqInterface.IPAddr = ipaddr.(types.String).ValueString()
			}
			macaddr, macSet := elemMap["mac"]
			if macSet {
				reqInterface.MAC = macaddr.(types.String).ValueString()
			}
			interfaces = append(interfaces, reqInterface)
		}

		createReqX86.Interfaces = interfaces
	}

	if !plan.CloudInit.IsNull() {
		userData := plan.CloudInit.ValueString()
		if userData != "" && userData != "applied" {
			createReqX86.Userdata = strings.TrimSpace(userData)
		}
	}

	driver := strings.ToUpper(plan.Driver.ValueString())

	createReqX86.RGID = uint64(plan.RGID.ValueInt64())
	createReqX86.Name = plan.Name.ValueString()
	createReqX86.CPU = uint64(plan.CPU.ValueInt64())
	createReqX86.RAM = uint64(plan.RAM.ValueInt64())
	createReqX86.Driver = driver

	if !plan.ImageID.IsNull() {
		createReqX86.ImageID = uint64(plan.ImageID.ValueInt64())
	}

	if !plan.WithoutBootDisk.IsNull() {
		createReqX86.WithoutBootDisk = plan.WithoutBootDisk.ValueBool()
	}

	if !plan.CustomFields.IsUnknown() { //CustomFields optional && computed
		val := plan.CustomFields.ValueString()
		val = strings.ReplaceAll(val, "\\", "")
		val = strings.ReplaceAll(val, "\n", "")
		val = strings.ReplaceAll(val, "\t", "")
		val = strings.TrimSpace(val)

		createReqX86.CustomFields = val
	}

	if !plan.NumaAffinity.IsNull() {
		createReqX86.NumaAffinity = strings.ToLower(plan.NumaAffinity.ValueString())
	}

	if plan.CPUPin.ValueBool() {
		createReqX86.CPUPin = true
	}

	if plan.HPBacked.ValueBool() {
		createReqX86.HPBacked = true
	}

	if !plan.PreferredCPU.IsUnknown() {
		preferredCPUList := plan.PreferredCPU.Elements()
		preferredCPU := make([]int64, 0, len(preferredCPUList))
		for _, elem := range preferredCPUList {
			preferredCPU = append(preferredCPU, elem.(types.Int64).ValueInt64())
		}
		createReqX86.PreferredCPU = preferredCPU
	}

	tflog.Info(ctx, fmt.Sprintf("CreateResourceCompute: creating Compute of type KVM VM x86"))
	id, err := c.CloudAPI().KVMX86().Create(ctx, createReqX86)
	if err != nil {
		diags.AddError("CreateResourceCompute: unable to create KVM VP x86", err.Error())
		return 0, diags
	}

	updateReq := compute.UpdateRequest{}

	if !plan.LoaderType.IsNull() {
		updateReq.LoaderType = plan.LoaderType.ValueString()
	}

	if !plan.BootType.IsNull() {
		updateReq.BootType = plan.BootType.ValueString()
	}

	if !plan.HotResize.IsNull() {
		updateReq.HotResize = plan.HotResize.ValueBool()
	}

	if !plan.NetworkInterfaceNaming.IsNull() {
		updateReq.NetworkInterfaceNaming = plan.NetworkInterfaceNaming.ValueString()
	}

	if !plan.LoaderType.IsNull() ||
		!plan.BootType.IsNull() ||
		!plan.HotResize.IsNull() ||
		!plan.NetworkInterfaceNaming.IsNull() {
		updateReq.ComputeID = id
		tflog.Info(ctx, "CreateResourceCompute: creating Compute with LoaderType or BootType or HotResize or NetworkInterfaceNaming")
		_, err = c.CloudAPI().Compute().Update(ctx, updateReq)
		if err != nil {
			diags.AddWarning("CreateResourceCompute: unable to update LoaderType or BootType or HotResize or NetworkInterfaceNaming", err.Error())
		}
	}

	return id, diags

}

func CleanupResourceCompute(ctx context.Context, computeId uint64, c *client.Client) {
	tflog.Info(ctx, fmt.Sprintf("CleanupResourceCompute: Start delete ResourceCompute: id %d", computeId))
	req := compute.DeleteRequest{
		ComputeID:   computeId,
		Permanently: true,
		DetachDisks: true,
	}

	if _, err := c.CloudAPI().Compute().Delete(ctx, req); err != nil {
		tflog.Error(ctx, fmt.Sprintf("CleanupResourceCompute: could not delete compute after failed creation: %v", err))
	}
}

func ComputeResourceExtraDiskCreate(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeExtraDiskCreate: Start added extra disk(s) from compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})

	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("Cannot parsed ID compute from plan", err.Error())
		return diags
	}

	extraDiskList := plan.ExtraDisks.Elements()
	for _, disk := range extraDiskList {
		diskId := disk.(types.Int64).ValueInt64()
		req := compute.DiskAttachRequest{
			ComputeID: computeId,
			DiskID:    uint64(diskId),
		}
		tflog.Info(ctx, "ComputeExtraDiskCreate: Start attach disk to compute with ID", map[string]any{"compute_id": plan.ID.ValueString(), "disk_id": diskId})
		_, err = c.CloudAPI().Compute().DiskAttach(ctx, req)

		if err != nil {
			diags.AddError(fmt.Sprintf("Cannot attach disk with ID - %d", diskId), err.Error())
		}
	}

	return diags
}

func ComputeResourceEnableDisable(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeEnableDisable: Start enabled/disabled compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeEnableDisable: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	enable := plan.Enabled.ValueBool()

	tflog.Info(ctx, "ComputeEnableDisable: compute to be enabled/disabled", map[string]any{
		"compute_id": computeId,
		"enable":     plan.Enabled.ValueBool()})

	if enable {
		_, err = c.CloudAPI().Compute().Enable(ctx, compute.EnableRequest{ComputeID: computeId})
		if err != nil {
			diags.AddWarning(
				"ComputeEnableDisable: cannot enable compute", err.Error(),
			)
			return diags
		}
	}

	if !enable {
		_, err = c.CloudAPI().Compute().Disable(ctx, compute.DisableRequest{ComputeID: computeId})
		if err != nil {
			diags.AddWarning(
				"ComputeEnableDisable: cannot disable compute", err.Error(),
			)
			return diags
		}
	}

	tflog.Info(ctx, "ComputeEnableDisable: compute is successfully enabled/disabled", map[string]any{"compute_id": computeId, "enable": enable})
	return nil
}

func ComputeResourceStartStop(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeStartStop: Start started/stopped compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeStartStop: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	started := plan.Started.ValueBool()

	tflog.Info(ctx, "ComputeStartStop: compute to be started/stopped", map[string]any{
		"compute_id": computeId,
		"started":    started})

	if started {
		_, err = c.CloudAPI().Compute().Start(ctx, compute.StartRequest{ComputeID: computeId})
		if err != nil {
			diags.AddWarning(
				"ComputeStartStop: cannot start compute", err.Error(),
			)
			return diags
		}
	}

	if !started {
		_, err = c.CloudAPI().Compute().Stop(ctx, compute.StopRequest{ComputeID: computeId})
		if err != nil {
			diags.AddWarning(
				"ComputeStartStop: cannot stop compute", err.Error(),
			)
			return diags
		}
	}

	tflog.Info(ctx, "ComputeStartStop: compute is successfully started/stopped", map[string]any{"compute_id": computeId, "started": started})
	return nil
}

func ComputeResourceAffinityLabel(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeAffinityLabel: Start added affinity label to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeAffinityLabel: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	req := compute.AffinityLabelSetRequest{
		ComputeID:     computeId,
		AffinityLabel: plan.AffinityLabel.ValueString(),
	}

	_, err = c.CloudAPI().Compute().AffinityLabelSet(ctx, req)
	if err != nil {
		diags.AddWarning(
			"ComputeAffinityLabel: Unable to AffinityLabelAdd for Compute",
			err.Error(),
		)
		return diags
	}

	tflog.Info(ctx, "ComputeAffinityLabel: affinity label is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourceAffinityRules(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeAffinityRules: Start added affinity rules to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeAffinityRules: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	itemsAffinityRules := make([]models.ItemResourceRulesModel, 0, len(plan.AffinityRules.Elements()))
	diags = plan.AffinityRules.ElementsAs(ctx, &itemsAffinityRules, false)
	if diags.HasError() {
		diags.AddWarning(
			fmt.Sprintf("ComputeAffinityRules: Unable to set affinity rules for compute %d", computeId),
			"cannot populate itemsAffinityRules with plan.AffinityRules List elements",
		)
		return diags
	}

	for _, itemAffinityRules := range itemsAffinityRules {
		req := compute.AffinityRuleAddRequest{
			ComputeID: computeId,
			Topology:  strings.ToLower(itemAffinityRules.Topology.ValueString()),
			Policy:    strings.ToUpper(itemAffinityRules.Policy.ValueString()),
			Mode:      strings.ToUpper(itemAffinityRules.Mode.ValueString()),
			Key:       itemAffinityRules.Key.ValueString(),
			Value:     itemAffinityRules.Value.ValueString(),
		}

		tflog.Info(ctx, "ComputeAffinityRules: before calling CloudAPI().Compute().AffinityRuleAdd", map[string]any{"compute_id": computeId, "req": req})
		res, err := c.CloudAPI().Compute().AffinityRuleAdd(ctx, req)
		tflog.Info(ctx, "ComputeAffinityRules: response from CloudAPI().Compute().AffinityRuleAdd", map[string]any{"compute_id": computeId, "response": res})
		if err != nil {
			diags.AddWarning(
				"ComputeAffinityRules: Unable to AffinityRuleAdd for Compute",
				err.Error(),
			)
			return diags
		}
	}

	if diags.WarningsCount() != 0 {
		return diags
	}

	tflog.Info(ctx, "ComputeAffinityRules: affinity rules is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourceAntiAffinityRules(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeAntiAffinityRules: Start added anti affinity rules to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeAntiAffinityRules: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	itemsAntiAffinityRules := make([]models.ItemResourceRulesModel, 0, len(plan.AntiAffinityRules.Elements()))
	diags = plan.AntiAffinityRules.ElementsAs(ctx, &itemsAntiAffinityRules, false)
	if diags.HasError() {
		diags.AddWarning(
			fmt.Sprintf("ComputeAntiAffinityRules: Unable to set anti affinity rules for compute %d", computeId),
			"cannot populate itemsAntiAffinityRules with plan.AntiAffinityRules List elements",
		)
		return diags
	}

	for _, itemAntiAffinityRules := range itemsAntiAffinityRules {
		req := compute.AntiAffinityRuleAddRequest{
			ComputeID: computeId,
			Topology:  strings.ToLower(itemAntiAffinityRules.Topology.ValueString()),
			Policy:    strings.ToUpper(itemAntiAffinityRules.Policy.ValueString()),
			Mode:      strings.ToUpper(itemAntiAffinityRules.Mode.ValueString()),
			Key:       itemAntiAffinityRules.Key.ValueString(),
			Value:     itemAntiAffinityRules.Value.ValueString(),
		}

		tflog.Info(ctx, "ComputeAntiAffinityRules: before calling CloudAPI().Compute().AntiAffinityRuleAdd", map[string]any{"compute_id": computeId, "req": req})
		res, err := c.CloudAPI().Compute().AntiAffinityRuleAdd(ctx, req)
		tflog.Info(ctx, "ComputeAntiAffinityRules: response from CloudAPI().Compute().AntiAffinityRuleAdd", map[string]any{"compute_id": computeId, "response": res})
		if err != nil {
			diags.AddWarning(
				"ComputeAntiAffinityRules: Unable to AntiAffinityRuleAdd for Compute",
				err.Error(),
			)
			return diags
		}
	}

	if diags.WarningsCount() != 0 {
		return diags
	}

	tflog.Info(ctx, "ComputeAntiAffinityRules: anti affinity rules is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourceTags(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourceTags: Start added tags to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourceTags: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	for _, tags := range plan.Tags.Elements() {
		req := compute.TagAddRequest{ComputeID: computeId}
		objTag := tags.(types.Object)
		tagMap := objTag.Attributes()
		req.Key = tagMap["key"].(types.String).ValueString()
		req.Value = tagMap["value"].(types.String).ValueString()
		tflog.Info(ctx, "ComputeResourceTags: before calling CloudAPI().Compute().TagAdd", map[string]any{"compute_id": computeId, "req": req})
		res, err := c.CloudAPI().Compute().TagAdd(ctx, req)
		tflog.Info(ctx, "ComputeResourceTags: response from CloudAPI().Compute().TagAdd", map[string]any{"compute_id": computeId, "response": res})
		if err != nil {
			diags.AddWarning(
				"ComputeResourceTags: Unable to add tags for Compute",
				err.Error(),
			)
		}
	}
	if diags.WarningsCount() != 0 {
		return diags
	}

	tflog.Info(ctx, "ComputeResourceTags: tags is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourcePortForwarding(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourcePortForwarding: Start added port forwarding to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourcePortForwarding: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	itemsPortForwarding := make([]models.ItemPortForfardingModel, 0, len(plan.PortForwarding.Elements()))
	diags = plan.PortForwarding.ElementsAs(ctx, &itemsPortForwarding, false)
	if diags.HasError() {
		diags.AddWarning(
			fmt.Sprintf("ComputeResourcePortForwarding: Unable to set port forwading rules for compute %d", computeId),
			"cannot populate itemsPortForwarding with plan.PortForwarding List elements",
		)
		return diags
	}

	for _, itemPortForwarding := range itemsPortForwarding {
		req := compute.PFWAddRequest{
			ComputeID:       computeId,
			PublicPortStart: uint64(itemPortForwarding.PublicPortStart.ValueInt64()),
			Proto:           strings.ToLower(itemPortForwarding.Proto.ValueString()),
		}
		if itemPortForwarding.PublicPortEnd.ValueInt64() != 0 {
			req.PublicPortEnd = itemPortForwarding.PublicPortEnd.ValueInt64()
		}
		if itemPortForwarding.LocalPort.ValueInt64() != 0 {
			req.LocalBasePort = uint64(itemPortForwarding.LocalPort.ValueInt64())
		}

		tflog.Info(ctx, "ComputeResourcePortForwarding: before calling CloudAPI().Compute().PFWAdd", map[string]any{"compute_id": computeId, "req": req})
		res, err := c.CloudAPI().Compute().PFWAdd(ctx, req)
		tflog.Info(ctx, "ComputeResourcePortForwarding: response from CloudAPI().Compute().PFWAdd", map[string]any{"compute_id": computeId, "response": res})
		if err != nil {
			diags.AddWarning(
				"ComputeResourcePortForwarding: Unable to add pfw for Compute",
				err.Error(),
			)
		}
	}

	if diags.WarningsCount() != 0 {
		return diags
	}

	tflog.Info(ctx, "ComputeResourcePortForwarding: port forwarding rules is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourceUserAccess(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourceUserAccess: Start added user(s) access to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourceUserAccess: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	for _, userAccess := range plan.UserAccess.Elements() {
		req := compute.UserGrantRequest{ComputeID: computeId}
		usersTag := userAccess.(types.Object)
		usersMap := usersTag.Attributes()
		req.Username = usersMap["username"].(types.String).ValueString()
		req.AccessType = usersMap["access_type"].(types.String).ValueString()
		tflog.Info(ctx, "ComputeResourceUserAccess: before calling CloudAPI().Compute().UserGrant", map[string]any{"compute_id": computeId, "req": req})
		res, err := c.CloudAPI().Compute().UserGrant(ctx, req)
		tflog.Info(ctx, "ComputeResourceUserAccess: response from CloudAPI().Compute().UserGrant", map[string]any{"compute_id": computeId, "response": res})
		if err != nil {
			diags.AddWarning(
				"ComputeResourceUserAccess: Unable to add user access for Compute",
				err.Error(),
			)
		}
	}
	if diags.WarningsCount() != 0 {
		return diags
	}

	tflog.Info(ctx, "ComputeResourceUserAccess: user(s) access is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourceSnapshot(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourceSnapshot: Start added snapshot to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourceSnapshot: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	for _, snapshots := range plan.Snapshot.Elements() {
		req := compute.SnapshotCreateRequest{ComputeID: computeId}
		snapshotTag := snapshots.(types.Object)
		snapshotMap := snapshotTag.Attributes()
		req.Label = snapshotMap["label"].(types.String).ValueString()
		tflog.Info(ctx, "ComputeResourceSnapshot: before calling CloudAPI().Compute().SnapshotCreate", map[string]any{"compute_id": computeId, "req": req})
		res, err := c.CloudAPI().Compute().SnapshotCreate(ctx, req)
		tflog.Info(ctx, "ComputeResourceSnapshot: response from CloudAPI().Compute().SnapshotCreate", map[string]any{"compute_id": computeId, "response": res})
		if err != nil {
			diags.AddWarning(
				"ComputeResourceSnapshot: Unable to add snapshot for Compute",
				err.Error(),
			)
		}
	}
	if diags.WarningsCount() != 0 {
		return diags
	}

	tflog.Info(ctx, "ComputeResourceSnapshot: snapshot(s) is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourcePCIDevice(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourcePCIDevice: Start added PCI devices(s) to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourcePCIDevice: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	pciDeviceList := plan.PCIDevices.Elements()
	for _, pciDevice := range pciDeviceList {
		pciDeviceId := pciDevice.(types.Int64).ValueInt64()
		req := compute.AttachPCIDeviceRequest{
			ComputeID: computeId,
			DeviceID:  uint64(pciDeviceId),
		}
		tflog.Info(ctx, "ComputeResourcePCIDevice: Start attach PCI device to compute with ID", map[string]any{"compute_id": plan.ID.ValueString(), "pci_device_id": pciDeviceId})
		res, err := c.CloudAPI().Compute().AttachPCIDevice(ctx, req)
		tflog.Info(ctx, "ComputeResourceSnapshot: response from CloudAPI().Compute().AttachPCIDevice", map[string]any{"compute_id": computeId, "response": res})
		if err != nil {
			diags.AddWarning(
				"ComputeResourceSnapshot: Unable to add PCI device for Compute",
				err.Error(),
			)
		}
	}
	if diags.WarningsCount() != 0 {
		return diags
	}

	tflog.Info(ctx, "ComputeResourcePCIDevice: PCI devices(s) is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourceCDInsert(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourceCDInsert: Start added cd to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourceSnapshot: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	req := compute.CDInsertRequest{ComputeID: computeId}
	req.CDROMID = uint64(plan.CDImageID.ValueInt64())
	tflog.Info(ctx, "ComputeResourceCDInsert: before calling CloudAPI().Compute().CDInsert", map[string]any{"compute_id": computeId, "req": req})
	res, err := c.CloudAPI().Compute().CDInsert(ctx, req)
	tflog.Info(ctx, "ComputeResourceCDInsert: response from CloudAPI().Compute().CDInsert", map[string]any{"compute_id": computeId, "response": res})
	if err != nil {
		diags.AddWarning(
			"ComputeResourceCDInsert: Unable to add cd for Compute",
			err.Error(),
		)
		return diags
	}

	tflog.Info(ctx, "ComputeResourceCDInsert: cd(s) is successfully added", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourcePinToStack(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourcePinToStack: Start pin to stack compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourcePinToStack: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	req := compute.PinToStackRequest{
		ComputeID: computeId,
		AutoStart: plan.AutoStartWithNode.ValueBool(),
	}

	_, err = c.CloudAPI().Compute().PinToStack(ctx, req)
	if err != nil {
		diags.AddWarning(
			"ComputeResourcePinToStack: cannot pin to stack compute", err.Error(),
		)
		return diags
	}

	tflog.Info(ctx, "ComputeResourcePinToStack: compute is successfully pin to stack", map[string]any{"compute_id": computeId})
	return nil
}

func ComputeResourcePause(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourcePause: Start pause compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourcePause: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	_, err = c.CloudAPI().Compute().Pause(ctx, compute.PauseRequest{ComputeID: computeId})
	if err != nil {
		diags.AddWarning(
			"ComputeResourcePause: cannot pause compute", err.Error(),
		)
		return diags
	}

	tflog.Info(ctx, "ComputeResourcePause: compute is successfully pause", map[string]any{"compute_id": computeId})
	return nil
}
func ComputeReadStatus(ctx context.Context, state *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeReadStatus: Read status compute with ID", map[string]any{"rg_id": state.ID.ValueString()})

	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(state.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeReadStatus: Cannot parse resource group ID from state", err.Error())
		return diags
	}

	recordCompute, diags := ComputeResourceCheckPresence(ctx, state, c)
	if diags.HasError() {
		return diags
	}

	tflog.Info(ctx, "ComputeReadStatus: compute values before status check", map[string]any{
		"compute_id":    computeId,
		"recordCompute": recordCompute})

	// check resource status
	switch recordCompute.Status {
	case status.Modeled:
		diags.AddError(
			"Compute is in status Modeled",
			"please, contact support for more information",
		)
		return diags
	case status.Deleted:
		tflog.Info(ctx, "ComputeReadStatus: compute with status.Deleted is being checked", map[string]any{
			"compute_id": computeId,
			"status":     recordCompute.Status})
		// restore and enable compute in case it is required
		if state.Restore.ValueBool() { // default true or user set-up true
			diags.Append(RestoreCompute(ctx, computeId, c)...)
			if diags.HasError() {
				tflog.Error(ctx, "ComputeReadStatus: cannot restore compute")
				return diags
			}
			tflog.Info(ctx, "ComputeReadStatus: compute restored successfully", map[string]any{"compute_id": computeId})
			if !state.ExtraDisks.IsNull() && ComputeResourceCheckDetachDisks(state, recordCompute) {
				diags.Append(ComputeResourceExtraDiskCreate(ctx, state, c)...)
				if diags.HasError() {
					tflog.Error(ctx, "ComputeReadStatus: error when attaching extra disk(s) to a Compute ")
					return diags
				}
			}
			if state.Enabled.ValueBool() { // default true or user set-up true
				diags.Append(ComputeResourceEnableDisable(ctx, state, c)...)
				if diags.HasError() {
					tflog.Error(ctx, "ComputeReadStatus: Unable to enable compute")
					return diags
				}
				tflog.Info(ctx, "ComputeReadStatus: compute enabled successfully", map[string]any{"compute_id": computeId})
				if state.Started.ValueBool() {
					diags.Append(ComputeResourceStartStop(ctx, state, c)...)
					if diags.HasError() {
						tflog.Error(ctx, "ComputeReadStatus: Unable to start compute")
						return diags
					}
				}
			}
		}
	case status.Destroyed:
		diags.AddError(
			"ComputeReadStatus: compute is in status Destroyed",
			fmt.Sprintf("the resource with id %d cannot be read because it has been destroyed", computeId),
		)
		return diags
	}

	return nil
}

func RestoreCompute(ctx context.Context, computeId uint64, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	restoreReq := compute.RestoreRequest{ComputeID: computeId}

	tflog.Info(ctx, "RestoreCompute: before calling CloudAPI().Compute().Restore", map[string]any{"compute_id": computeId, "req": restoreReq})

	res, err := c.CloudAPI().Compute().Restore(ctx, restoreReq)
	if err != nil {
		diags.AddError(
			"RestoreCompute: cannot restore compute",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "RestoreCompute: response from CloudAPI().Compute().Restore", map[string]any{"compute_id": computeId, "response": res})

	return nil
}

func ComputeResourceAutoStartWithNode(ctx context.Context, plan *models.ResourceComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "ComputeResourceAutoStartWithNode: Start set auto start with node to compute with ID", map[string]any{"compute_id": plan.ID.ValueString()})
	diags := diag.Diagnostics{}

	computeId, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("ComputeResourceAutoStartWithNode: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	req := compute.UpdateRequest{
		ComputeID: computeId,
		AutoStart: plan.AutoStartWithNode.ValueBool(),
		CPUPin:    plan.CPUPin.ValueBool(),
		HPBacked:  plan.HPBacked.ValueBool(),
	}

	_, err = c.CloudAPI().Compute().Update(ctx, req)
	if err != nil {
		diags.AddWarning(
			"ComputeResourceAutoStartWithNode: cannot update compute", err.Error(),
		)
		return diags
	}

	tflog.Info(ctx, "ComputeResourceAutoStartWithNode: auto start with node set is successfully", map[string]any{"compute_id": computeId})
	return nil
}
