package utilities

import (
	"context"
	"fmt"
	"strconv"

	"github.com/hashicorp/terraform-plugin-framework/diag"
	"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
	"github.com/hashicorp/terraform-plugin-log/tflog"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/account"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/client"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/account/models"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/ic"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/status"
)

// AccountResourceCheckPresence checks if account with accountId exists
func AccountResourceCheckPresence(ctx context.Context, accountId uint64, c *client.Client) (*account.RecordAccount, error) {
	tflog.Info(ctx, fmt.Sprintf("AccountResourceCheckPresence: Get info about resource with ID - %v", accountId))

	accountRecord, err := c.CloudBroker().Account().Get(ctx, account.GetRequest{AccountID: accountId})
	if err != nil {
		return nil, fmt.Errorf("AccountResourceCheckPresence: cannot get info about resource with error: %w", err)
	}

	tflog.Info(ctx, "AccountResourceCheckPresence: response from CloudBroker().Account().Get", map[string]any{"account_id": accountId, "response": accountRecord})

	return accountRecord, err
}

// AccountReadStatus loads account resource by its id, gets it current status. Performs restore and enable if needed for
// Deleted status.
// In case of failure returns errors.
func AccountReadStatus(ctx context.Context, state *models.ResourceAccountModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "AccountReadStatus: Read status resource with ID", map[string]any{"account_id": state.Id.ValueString()})

	diags := diag.Diagnostics{}

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

	recordAccount, err := AccountResourceCheckPresence(ctx, accountId, c)
	if err != nil {
		diags.AddError("AccountReadStatus: Unable to Read account before status check", err.Error())
		return diags
	}

	// check resource status
	switch recordAccount.Status {
	case status.Disabled:
		tflog.Info(ctx, "The account is in status Disabled, troubles may occur with update. Please, enable account first.")

	case status.Deleted:
		restore := state.Restore.ValueBool()
		if state.Restore.IsNull() {
			restore = true
		} // default true
		if restore {
			// attempt to restore account
			tflog.Info(ctx, "AccountReadStatus: account with status.Deleted is being read, attempt to restore it", map[string]any{
				"account_id": accountId,
				"status":     recordAccount.Status})
			diags.Append(RestoreAccount(ctx, accountId, c)...)
			if diags.HasError() {
				tflog.Error(ctx, "AccountReadStatus: cannot restore account")
				return diags
			}
			tflog.Info(ctx, "AccountReadStatus: account restored successfully", map[string]any{"account_id": accountId})
		} else {
			tflog.Info(ctx, "AccountReadStatus: account is i status Deleted but restore is not specified")
		}

	case status.Destroyed:
		diags.AddError(
			"AccountReadStatus: Account is in status Destroyed",
			fmt.Sprintf("the resource with account_id %d cannot be read or updated because it has been destroyed", accountId),
		)
		return diags

	case status.Destroying:
		diags.AddError(
			"AccountReadStatus: Account is in progress with status Destroying",
			fmt.Sprintf("the resource with account_id %d cannot be read or updated because it is currently being destroyed", accountId),
		)
		return diags
	}

	return nil
}

// RestoreAccount performs account Restore request.
// Returns error in case of failures.
func RestoreAccount(ctx context.Context, accountId uint64, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	restoreReq := account.RestoreRequest{
		AccountID: accountId,
	}

	tflog.Info(ctx, "RestoreAccount: before calling CloudBroker().Account().Restore", map[string]any{"account_id": accountId, "req": restoreReq})

	res, err := c.CloudBroker().Account().Restore(ctx, restoreReq)
	if err != nil {
		diags.AddError(
			"RestoreAccount: cannot restore account",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "RestoreAccount: response from CloudBroker().Account().Restore", map[string]any{"account_id": accountId, "response": res})

	return nil
}

// EnableDisableAccount performs account Enable/Disable request.
// Returns error in case of failures.
func EnableDisableAccount(ctx context.Context, accountId uint64, enable bool, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start EnableDisableAccount", map[string]any{"account_id": accountId})

	diags := diag.Diagnostics{}

	if enable {
		tflog.Info(ctx, "EnableDisableAccount: before calling CloudBroker().Account().Enable", map[string]any{"account_id": accountId})
		res, err := c.CloudBroker().Account().Enable(ctx, account.EnableRequest{AccountID: accountId})
		if err != nil {
			diags.AddError(
				"EnableDisableAccount: cannot enable account",
				err.Error(),
			)
			return diags
		}
		tflog.Info(ctx, "EnableDisableAccount: response from CloudBroker().Account().Enable", map[string]any{"account_id": accountId, "response": res})

		return nil
	}

	tflog.Info(ctx, "EnableDisableAccount: before calling CloudBroker().Account().Disable", map[string]any{"account_id": accountId})
	res, err := c.CloudBroker().Account().Disable(ctx, account.DisableRequest{AccountID: accountId})
	if err != nil {
		diags.AddError(
			"EnableDisableAccount: cannot disable account",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "EnableDisableAccount: response from CloudBroker().Account().Disable", map[string]any{"account_id": accountId, "response": res})

	return nil
}

func UtilityAccountCreate(ctx context.Context, plan *models.ResourceAccountModel, c *client.Client) (diag.Diagnostics, *uint64) {
	tflog.Info(ctx, "Start UtilityAccountCreate", map[string]any{"account_id": plan.AccountID})
	diags := diag.Diagnostics{}

	req := account.CreateRequest{
		Name:     plan.AccountName.ValueString(),
		Username: plan.Username.ValueString(),
	}

	if !plan.EmailAddress.IsUnknown() {
		req.EmailAddress = plan.EmailAddress.ValueString()
	}

	if !plan.SendAccessEmails.IsUnknown() {
		req.SendAccessEmails = plan.SendAccessEmails.ValueBool()
	}

	if !plan.UniqPools.IsUnknown() {
		var uniqPools []string
		diags.Append(plan.UniqPools.ElementsAs(ctx, &uniqPools, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "UtilityAccountCreate: cannot populate UniqPools with plan.UniqPools object element")
			return diags, nil
		}
		req.UniqPools = uniqPools
	}

	if !plan.ResourceLimits.IsUnknown() {
		var resourceLimitsPlan models.ResourceLimitsInAccountResourceModel
		diags.Append(plan.ResourceLimits.As(ctx, &resourceLimitsPlan, basetypes.ObjectAsOptions{})...)
		if diags.HasError() {
			tflog.Error(ctx, "UtilityAccountCreate: cannot populate ResourceLimits with plan.ResourceLimits object element")
			return diags, nil
		}

		if resourceLimitsPlan.CUM.ValueFloat64() == 0 {
			req.MaxMemoryCapacity = -1
		} else {
			req.MaxMemoryCapacity = int64(resourceLimitsPlan.CUM.ValueFloat64())
		}

		if resourceLimitsPlan.CUD.ValueFloat64() == 0 {
			req.MaxVDiskCapacity = -1
		} else {
			req.MaxVDiskCapacity = int64(resourceLimitsPlan.CUD.ValueFloat64())
		}

		if resourceLimitsPlan.CUC.ValueFloat64() == 0 {
			req.MaxCPUCapacity = -1
		} else {
			req.MaxCPUCapacity = int64(resourceLimitsPlan.CUC.ValueFloat64())
		}

		if resourceLimitsPlan.CUI.ValueFloat64() == 0 {
			req.MaxNumPublicIP = -1
		} else {
			req.MaxNumPublicIP = int64(resourceLimitsPlan.CUI.ValueFloat64())
		}

		if resourceLimitsPlan.CUNP.ValueFloat64() == 0 {
			req.MaxNetworkPeerTransfer = -1
		} else {
			req.MaxNetworkPeerTransfer = int64(resourceLimitsPlan.CUNP.ValueFloat64())
		}

		if resourceLimitsPlan.GPUUnits.ValueFloat64() == 0 {
			req.GPUUnits = -1
		} else {
			req.GPUUnits = int64(resourceLimitsPlan.GPUUnits.ValueFloat64())
		}
	}

	accountId, err := c.CloudBroker().Account().Create(ctx, req)
	if err != nil {
		diags.AddError("UtilityAccountCreate: Unable to create account",
			err.Error())
		return diags, nil
	}

	if !plan.Users.IsUnknown() {
		usersPlan := make([]models.UsersModel, 0, len(plan.Users.Elements()))

		diagsI := plan.Users.ElementsAs(ctx, &usersPlan, true)
		if diagsI.HasError() {
			tflog.Error(ctx, "UtilityAccountCreate: cannot populate usersPlan with plan.Users list elements")
			diags.AddWarning("UtilityAccountCreate: cannot populate usersPlan with plan.Users list elements",
				fmt.Sprintf("%v", diagsI))
			return diags, nil
		}
		for _, v := range usersPlan {
			req := account.AddUserRequest{
				AccountID:  accountId,
				Username:   v.UserID.ValueString(),
				AccessType: v.AccessType.ValueString(),
			}

			_, err := c.CloudBroker().Account().AddUser(ctx, req)
			if err != nil {
				diags.AddWarning("UtilityAccountCreate: Unable to add users",
					err.Error())
				return diags, nil
			}
		}
	}

	if !plan.CPUAllocationParameter.IsUnknown() {
		req := account.SetCPUAllocationParameterRequest{
			AccountID:   accountId,
			StrictLoose: plan.CPUAllocationParameter.ValueString(),
		}
		_, err := c.CloudBroker().Account().SetCPUAllocationParameter(ctx, req)
		if err != nil {
			diags.AddWarning("UtilityAccountCreate: Unable to set CPUAllocationParameter ",
				err.Error())
			return diags, nil
		}
	}

	if !plan.CPUAllocationRatio.IsUnknown() {
		req := account.SetCPUAllocationRatioRequest{
			AccountID: accountId,
			Ratio:     plan.CPUAllocationRatio.ValueFloat64(),
		}

		_, err := c.CloudBroker().Account().SetCPUAllocationRatio(ctx, req)
		if err != nil {
			diags.AddWarning("UtilityAccountCreate: Unable to set CPUAllocationRatio ",
				err.Error())
			return diags, nil
		}
	}

	if !plan.Enable.IsUnknown() && !plan.Enable.ValueBool() {
		_, err := c.CloudBroker().Account().Disable(ctx, account.DisableRequest{
			AccountID: accountId,
		})
		if err != nil {
			diags.AddWarning("UtilityAccountCreate: Unable to disable account",
				err.Error())
			return diags, nil
		}
	}

	if !plan.AvailableTemplates.IsUnknown() {
		diagsI := UtilityAccountAvailiableTemplatesUpdate(ctx, plan, plan, true, c)
		if diagsI.HasError() {
			tflog.Error(ctx, "UtilityAccountCreate: error with utilityAccountAvailiableTemplatesUpdate")
			diags.AddWarning("UtilityAccountCreate: cannot populate usersPlan with plan.Users list elements", fmt.Sprintf("%v", diagsI))
			return diags, nil
		}
	}
	tflog.Info(ctx, "End UtilityAccountCreate", map[string]any{"account_id": plan.AccountID.ValueInt64()})
	return diags, &accountId
}

// UpdateAccount updates disk data: account_name, resource_limits, send_access_emails.
// Returns error in case of failures.
func UpdateAccount(ctx context.Context, accountId uint64, plan, state *models.ResourceAccountModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start UpdateAccount", map[string]any{"account_id": accountId})

	var diags diag.Diagnostics
	var updateNeeded bool

	updateReq := account.UpdateRequest{
		AccountID: accountId,
	}

	// check if account_name was changed
	if !plan.AccountName.Equal(state.AccountName) {
		updateReq.Name = plan.AccountName.ValueString()
		updateNeeded = true
	}

	// check if resource_limits were changed
	if !plan.ResourceLimits.Equal(state.ResourceLimits) && !plan.ResourceLimits.IsUnknown() {
		tflog.Info(ctx, "UpdateAccount: new ResourceLimits specified", map[string]any{"account_id": accountId})
		var resourceLimitsPlan models.ResourceLimitsInAccountResourceModel
		diags.Append(plan.ResourceLimits.As(ctx, &resourceLimitsPlan, basetypes.ObjectAsOptions{})...)
		if diags.HasError() {
			tflog.Error(ctx, "UpdateAccount: cannot populate ResourceLimits with plan.ResourceLimits object element")
			return diags
		}

		if resourceLimitsPlan.CUM.ValueFloat64() == 0 {
			updateReq.MaxMemoryCapacity = -1
		} else {
			updateReq.MaxMemoryCapacity = int64(resourceLimitsPlan.CUM.ValueFloat64())
		}

		if resourceLimitsPlan.CUD.ValueFloat64() == 0 {
			updateReq.MaxVDiskCapacity = -1
		} else {
			updateReq.MaxVDiskCapacity = int64(resourceLimitsPlan.CUD.ValueFloat64())
		}

		if resourceLimitsPlan.CUC.ValueFloat64() == 0 {
			updateReq.MaxCPUCapacity = -1
		} else {
			updateReq.MaxCPUCapacity = int64(resourceLimitsPlan.CUC.ValueFloat64())
		}

		if resourceLimitsPlan.CUI.ValueFloat64() == 0 {
			updateReq.MaxNumPublicIP = -1
		} else {
			updateReq.MaxNumPublicIP = int64(resourceLimitsPlan.CUI.ValueFloat64())
		}

		if resourceLimitsPlan.CUNP.ValueFloat64() == 0 {
			updateReq.MaxNetworkPeerTransfer = -1
		} else {
			updateReq.MaxNetworkPeerTransfer = int64(resourceLimitsPlan.CUNP.ValueFloat64())
		}

		if resourceLimitsPlan.GPUUnits.ValueFloat64() == 0 {
			updateReq.GPUUnits = -1
		} else {
			updateReq.GPUUnits = int64(resourceLimitsPlan.GPUUnits.ValueFloat64())
		}

		updateNeeded = true
	}

	// check if send_access_emails was changed
	if !plan.SendAccessEmails.Equal(state.SendAccessEmails) && !plan.SendAccessEmails.IsNull() {
		updateReq.SendAccessEmails = plan.SendAccessEmails.ValueBool()
		updateNeeded = true
	}

	if !updateNeeded {
		tflog.Info(ctx, "UpdateAccount: no general account update is needed because neither account_name, nor resource_limits, nor send_access_emails were changed.", map[string]any{
			"account_id": plan.Id.ValueString(),
		})
		return nil
	}

	// perform account update
	tflog.Info(ctx, "UpdateAccount: before calling CloudBroker().Account().Update", map[string]any{
		"account_id": accountId,
		"req":        updateReq,
	})
	res, err := c.CloudBroker().Account().Update(ctx, updateReq)
	if err != nil {
		diags.AddError("UpdateAccount: Unable to update account",
			err.Error())
		return diags
	}
	tflog.Info(ctx, "UpdateAccount: response from CloudBroker().Account().Update", map[string]any{
		"account_id": accountId,
		"response":   res})

	return nil
}

// AddDeleteUsersAccount adds/deletes users to/from account.
// In case of failure returns errors.
func AddDeleteUsersAccount(ctx context.Context, accountId uint64, plan, state *models.ResourceAccountModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start AddDeleteUsersAccount: new users specified", map[string]any{"account_id": accountId})
	diags := diag.Diagnostics{}

	usersPlan := make([]models.UsersModel, 0, len(plan.Users.Elements()))
	diags.Append(plan.Users.ElementsAs(ctx, &usersPlan, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "AddDeleteUsersAccount: cannot populate usersPlan with plan.Users list elements")
		return diags
	}

	usersState := make([]models.UsersModel, 0, len(state.Users.Elements()))
	diags.Append(state.Users.ElementsAs(ctx, &usersState, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "AddDeleteUsersAccount: cannot populate usersState with state.Users list elements")
		return diags
	}

	// define users to be deleted, added and updated
	var deletedUsers, addedUsers, updatedUsers []models.UsersModel
	for _, user := range usersState {
		if !containsUser(usersPlan, user) {
			deletedUsers = append(deletedUsers, user)
		}
	}
	for _, user := range usersPlan {
		if !containsUser(usersState, user) {
			addedUsers = append(addedUsers, user)
		} else if isChangedUser(usersState, user) {
			updatedUsers = append(updatedUsers, user)
		}
	}

	// delete users
	if len(deletedUsers) == 0 {
		tflog.Info(ctx, "AddDeleteUsersAccount: no users need to be deleted", map[string]any{"account_id": accountId})
	}
	if len(deletedUsers) > 0 {
		tflog.Info(ctx, "AddDeleteUsersAccount: users need to be deleted", map[string]any{
			"accountId":    accountId,
			"deletedUsers": deletedUsers})

		for _, user := range deletedUsers {
			delUserReq := account.DeleteUserRequest{
				AccountID: accountId,
				UserName:  user.UserID.ValueString(),
			}

			tflog.Info(ctx, "AddDeleteUsersAccount: before calling CloudBroker().Account().DeleteUser", map[string]any{"account_id": accountId, "req": delUserReq})
			res, err := c.CloudBroker().Account().DeleteUser(ctx, delUserReq)
			tflog.Info(ctx, "AddDeleteUsersAccount: response from CloudBroker().Account().DeleteUser", map[string]any{"account_id": accountId, "response": res})
			if err != nil {
				diags.AddError(
					"AddDeleteUsersAccount: can not delete user from account",
					err.Error())
			}
		}
	}

	// add users
	if len(addedUsers) == 0 {
		tflog.Info(ctx, "AddDeleteUsersAccount: no users needs to be added", map[string]any{"account_id": accountId})
	}
	if len(addedUsers) > 0 {
		tflog.Info(ctx, "AddDeleteUsersAccount: users need to be added", map[string]any{"account_id": accountId})

		for _, user := range addedUsers {
			addUserReq := account.AddUserRequest{
				AccountID:  accountId,
				Username:   user.UserID.ValueString(),
				AccessType: user.AccessType.ValueString(),
			}

			tflog.Info(ctx, "AddDeleteUsersAccount: before calling CloudBroker().Account().AddUser", map[string]any{
				"account_id": accountId,
				"addUserReq": addUserReq})
			res, err := c.CloudBroker().Account().AddUser(ctx, addUserReq)
			if err != nil {
				diags.AddError("AddDeleteUsersAccount: Unable to add users to account",
					err.Error())
			}
			tflog.Info(ctx, "AddDeleteUsersAccount: response from CloudBroker().Account().AddUser", map[string]any{
				"account_id": accountId,
				"response":   res})
		}
	}

	// update users
	if len(updatedUsers) == 0 {
		tflog.Info(ctx, "AddDeleteUsersAccount: no users needs to be updated", map[string]any{"account_id": accountId})
	}
	if len(updatedUsers) > 0 {
		tflog.Info(ctx, "AddDeleteUsersAccount: users need to be updated", map[string]any{"account_id": accountId})

		for _, user := range updatedUsers {
			updUserReq := account.UpdateUserRequest{
				AccountID:  accountId,
				UserID:     user.UserID.ValueString(),
				AccessType: user.AccessType.ValueString(),
			}

			tflog.Info(ctx, "AddDeleteUsersAccount: before calling CloudBroker().Account().UpdateUser", map[string]any{
				"account_id":   accountId,
				"updatedUsers": updatedUsers})
			res, err := c.CloudBroker().Account().UpdateUser(ctx, updUserReq)
			if err != nil {
				diags.AddError("AddDeleteUsersAccount: Unable to update users",
					err.Error())
			}
			tflog.Info(ctx, "AddDeleteUsersAccount: response from CloudBroker().Account().UpdateUser", map[string]any{
				"account_id": accountId,
				"response":   res})
		}
	}

	return diags
}

func containsUser(users []models.UsersModel, target models.UsersModel) bool {
	for _, user := range users {
		if target.UserID == user.UserID {
			return true
		}
	}
	return false
}

func isChangedUser(users []models.UsersModel, target models.UsersModel) bool {
	for _, user := range users {
		if user.UserID.Equal(target.UserID) && !user.AccessType.Equal(target.AccessType) {
			return true
		}
	}
	return false
}

func UtilityAccountCPUParameterUpdate(ctx context.Context, accountID uint64, plan *models.ResourceAccountModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start utilityAccountCPUParameterUpdate", map[string]any{"account_id": plan.AccountID})
	diags := diag.Diagnostics{}

	_, err := c.CloudBroker().Account().SetCPUAllocationParameter(ctx, account.SetCPUAllocationParameterRequest{
		AccountID:   accountID,
		StrictLoose: plan.CPUAllocationParameter.ValueString(),
	})

	if err != nil {
		diags.AddError("utilityAccountCPUParameterUpdate: Unable to update CPUAllocationParameter",
			err.Error())
		return diags
	}

	return diags
}

func UtilityAccountCPURatioUpdate(ctx context.Context, accountID uint64, plan *models.ResourceAccountModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start utilityAccountCPURatioUpdate", map[string]any{"account_id": plan.AccountID})
	diags := diag.Diagnostics{}

	_, err := c.CloudBroker().Account().SetCPUAllocationRatio(ctx, account.SetCPUAllocationRatioRequest{
		AccountID: accountID,
		Ratio:     plan.CPUAllocationRatio.ValueFloat64(),
	})
	if err != nil {
		diags.AddError("utilityAccountCPURatioUpdate: Unable to update CPUAllocationRatio",
			err.Error())
		return diags
	}
	return diags
}

func UtilityAccountAvailiableTemplatesUpdate(ctx context.Context, state, plan *models.ResourceAccountModel, afterCreate bool, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start utilityAccountAvailiableTemplatesUpdate", map[string]any{"account_id": plan.AccountID})
	diags := diag.Diagnostics{}

	if afterCreate {

		imageIds := make([]uint64, 0, len(plan.AvailableTemplates.Elements()))

		diags.Append(plan.AvailableTemplates.ElementsAs(ctx, &imageIds, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "UpdateAccount: cannot populate AvailableTemplates with plan.AvailableTemplates object element")
			return diags
		}

		if len(imageIds) == 0 {
			diags.AddError(
				"you have not been granted access to any images",
				"len(imageIds) == 0",
			)
			return diags
		}
		tflog.Error(ctx, "you have not been granted access to any images")
		if err := ic.ExistImages(ctx, imageIds, c); err != nil {
			diags.AddError(
				fmt.Sprintf("can not grant access for available templates: %s", err.Error()),
				err.Error(),
			)
			return diags
		}
		tflog.Error(ctx, fmt.Sprint(imageIds))

		req := account.GrantAccessTemplatesRequest{
			AccountID: uint64(state.AccountID.ValueInt64()),
			ImageIDs:  imageIds,
		}

		_, err := c.CloudBroker().Account().GrantAccessTemplates(ctx, req)
		if err != nil {
			diags.AddError(
				fmt.Sprintf("can not grant access for available templates: %s", err.Error()),
				err.Error(),
			)
			return diags
		}

		return diags
	}

	var oldSet, newSet []int
	diags.Append(plan.AvailableTemplates.ElementsAs(ctx, &newSet, true)...)
	diags.Append(state.AvailableTemplates.ElementsAs(ctx, &oldSet, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "UtilityAccountCreate: cannot populate newSet or oldSet with AvailableTemplates")
		return diags
	}

	revokeAT := setDifference(oldSet, newSet)
	if len(revokeAT) > 0 {
		imageIds := make([]uint64, 0, len(revokeAT))

		for _, imageId := range revokeAT {
			imageIds = append(imageIds, imageId)
		}

		if err := ic.ExistImages(ctx, imageIds, c); err != nil {
			diags.AddError(fmt.Sprintf("can not grant access for available templates: %s", err), err.Error())
			return diags
		}

		req := account.RevokeAccessTemplatesRequest{
			AccountID: uint64(state.AccountID.ValueInt64()),
			ImageIDs:  imageIds,
		}

		_, err := c.CloudBroker().Account().RevokeAccessTemplates(ctx, req)
		if err != nil {
			diags.AddError("UtilityAccountCreate: error with RevokeAccessTemplates", err.Error())
			return diags
		}
	}

	addedAT := setDifference(newSet, oldSet)
	if len(addedAT) > 0 {
		imageIds := make([]uint64, 0, len(addedAT))

		imageIds = append(imageIds, addedAT...)

		if err := ic.ExistImages(ctx, imageIds, c); err != nil {
			diags.AddError(fmt.Sprintf("can grant access for available templates: %s", err.Error()), err.Error())
			return diags
		}

		req := account.GrantAccessTemplatesRequest{
			AccountID: uint64(plan.AccountID.ValueInt64()),
			ImageIDs:  imageIds,
		}

		_, err := c.CloudBroker().Account().GrantAccessTemplates(ctx, req)
		if err != nil {
			diags.AddError("UtilityAccountCreate: error with GrantAccessTemplates", err.Error())
			return diags
		}
	}

	return diags
}

func setDifference(set, check []int) []uint64 {
	mapCheck := make(map[int]struct{})
	for _, id := range check {
		mapCheck[id] = struct{}{}
	}
	var diff []uint64
	for _, id := range set {
		if _, ok := mapCheck[id]; !ok {
			diff = append(diff, uint64(id))
		}
	}
	return diff
}

func UtilityAccountComputeFeaturesUpdate(ctx context.Context, accountID uint64, plan *models.ResourceAccountModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	var compFeatures []string

	diags.Append(plan.ComputeFeatures.ElementsAs(ctx, &compFeatures, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "utilityAccountComputeFeaturesUpdate: cannot populate compFeatures with plan.ComputeFeatures object element")
		return diags
	}

	req := account.UpdateComputeFeaturesRequest{
		AccountID:       accountID,
		ComputeFeatures: compFeatures,
	}

	_, err := c.CloudBroker().Account().UpdateComputeFeatures(ctx, req)
	if err != nil {
		diags.AddError("utilityAccountComputeFeaturesUpdate: error with CloudBroker().Account().UpdateComputeFeatures", err.Error())
		return diags
	}

	return diags
}
