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

import (
	"context"
	"fmt"
	"strconv"

	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/rg"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/dc"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/status"

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

func resourceResgroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceResgroupCreate: called for RG name %s, account ID %d",
		d.Get("rg_name").(string), d.Get("account_id").(int))
	c := m.(*controller.ControllerCfg)

	accountId := uint64(d.Get("account_id").(int))
	gid := uint64(d.Get("gid").(int))
	req := rg.CreateRequest{
		AccountID: accountId,
		GID:       gid,
		Name:      d.Get("rg_name").(string),
	}

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

	if resLimits, ok := d.GetOk("resource_limits"); ok {
		resLimits := resLimits.([]interface{})[0]
		resLimitsConv := resLimits.(map[string]interface{})
		if resLimitsConv["cu_m"] != nil {
			maxMemCap := int64(resLimitsConv["cu_m"].(float64))
			if maxMemCap == 0 {
				req.MaxMemoryCapacity = -1
			} else {
				req.MaxMemoryCapacity = maxMemCap
			}
		}
		if resLimitsConv["cu_dm"] != nil {
			maxDiskCap := int64(resLimitsConv["cu_dm"].(float64))
			if maxDiskCap == 0 {
				req.MaxVDiskCapacity = -1
			} else {
				req.MaxVDiskCapacity = maxDiskCap
			}
		}
		if resLimitsConv["cu_c"] != nil {
			maxCPUCap := int64(resLimitsConv["cu_c"].(float64))
			if maxCPUCap == 0 {
				req.MaxCPUCapacity = -1
			} else {
				req.MaxCPUCapacity = maxCPUCap
			}
		}
		if resLimitsConv["cu_i"] != nil {
			maxNumPublicIP := int64(resLimitsConv["cu_i"].(float64))
			if maxNumPublicIP == 0 {
				req.MaxNumPublicIP = -1
			} else {
				req.MaxNumPublicIP = maxNumPublicIP
			}
		}
	}

	if owner, ok := d.GetOk("owner"); ok {
		req.Owner = owner.(string)
	}

	if _, ok := d.GetOk("def_net"); !ok {
		if defNetType, ok := d.GetOk("def_net_type"); ok {
			if defNetType.(string) == "PRIVATE" {
				return diag.Errorf("resourceResgroupCreate: cannot create RG with def_net_type=\"PRIVATE\": no ViNSes exist in a newly created RG. Use def_net_type=\"PRIVATE\" only when updating an existing RG that already contains a ViNS")
			}
			req.DefNet = defNetType.(string)
		}
	} else {
		req.DefNet = "NONE"
	}

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

	if extNetId, ok := d.GetOk("ext_net_id"); ok {
		req.ExtNetID = uint64(extNetId.(int))
	}

	if extIp, ok := d.GetOk("ext_ip"); ok {
		req.ExtIP = extIp.(string)
	}

	if uniqPools, ok := d.GetOk("uniq_pools"); ok {
		uniqPools := uniqPools.([]interface{})

		for _, pool := range uniqPools {
			req.UniqPools = append(req.UniqPools, pool.(string))
		}
	}

	if sdnAccessGroupID, ok := d.GetOk("sdn_access_group_id"); ok {
		req.SDNAccessGroupID = sdnAccessGroupID.(string)
	}

	if storagePolicies, ok := d.GetOk("storage_policy"); ok {
		var id uint64
		var limit int

		if storagePolicies.(*schema.Set).Len() > 0 {
			spList := storagePolicies.(*schema.Set).List()
			for _, spInterface := range spList {
				sps := spInterface.(map[string]interface{})
				id = uint64(sps["id"].(int))
				limit = sps["limit"].(int)

				spModel := rg.StoragePolicy{
					ID:    id,
					Limit: limit,
				}

				req.StoragePolicies = append(req.StoragePolicies, spModel)
			}
		}
	}

	rgID, err := c.CloudBroker().RG().Create(ctx, req)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	d.SetId(strconv.FormatUint(rgID, 10))
	d.Set("rg_id", rgID)

	w := dc.Warnings{}

	// compute_features is not set up during create request and is updated after create on purpose:
	// 1. to support configuration as final state in resource group,
	// 2. not to support compute_features inheritance from account.
	// other implementation may result in inconsistency between user configuration and state after rg creation.
	// note that call for compute features updated required even if compute_features is null in configuration, in order not ot inherit from account.
	if err := resourceRGChangeComputeFeatures(ctx, d, m); err != nil {
		w.Add(err)
	}

	if _, ok := d.GetOk("access"); ok {
		if errs := resourceRGAccessGrant(ctx, d, m); len(errs) != 0 {
			for _, err := range errs {
				w.Add(err)
			}
		}
	}

	if defNet, ok := d.GetOk("def_net"); ok {
		if defNet.(*schema.Set).Len() > 0 {
			defNetList := defNet.(*schema.Set).List()
			defNetItem := defNetList[0].(map[string]interface{})
			if defNetItem["net_type"].(string) == "PRIVATE" {
				return diag.Errorf("resourceResgroupCreate: cannot create RG with def_net net_type=\"PRIVATE\": no ViNSes exist in a newly created RG. Use net_type=\"PRIVATE\" in def_net block only when updating an existing RG that already contains a ViNS")
			}
		}
		if err := resourceRGSetDefNet(ctx, d, m); err != nil {
			w.Add(err)
		}
	}

	if _, ok := d.GetOk("cpu_allocation_parameter"); ok {
		if err := resourceRGSetCPUAllocationParameter(ctx, d, m); err != nil {
			w.Add(err)
		}
	}

	if _, ok := d.GetOk("cpu_allocation_ratio"); ok {
		if err := resourceRGSetCPUAllocationRatio(ctx, d, m); err != nil {
			w.Add(err)
		}
	}

	enable, ok := d.GetOk("enable")
	if ok && !enable.(bool) {
		_, err := c.CloudBroker().RG().Disable(ctx, rg.DisableRequest{RGID: rgID})
		if err != nil {
			w.Add(err)
		}
	}

	return append(resourceResgroupRead(ctx, d, m), w.Get()...)
}

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

	//c := m.(*controller.ControllerCfg)
	rgData, err := utilityResgroupCheckPresence(ctx, d, m)
	if err != nil {
		d.SetId("") // ensure ID is empty
		return diag.FromErr(err)
	}

	hasChanged := false

	switch rgData.Status {
	case status.Modeled:
		return diag.Errorf("The resource group is in status: %s, please, contact support for more information", rgData.Status)
	case status.Created:
	case status.Enabled:
	case status.Deleted:
		//restoreReq := rg.RestoreRequest{
		//	RGID:   rgData.ID,
		//	Reason: "automatic restore of resource by terraform",
		//}
		//enableReq := rg.EnableRequest{
		//	RGID:   rgData.ID,
		//	Reason: "automatic enable of resource by terraform",
		//}
		//
		//log.Debugf("restoring RG")
		//_, err := c.CloudBroker().RG().Restore(ctx, restoreReq)
		//if err != nil {
		//	return diag.FromErr(err)
		//}
		//
		//log.Debugf("enabling RG")
		//_, err = c.CloudBroker().RG().Enable(context.Background(), enableReq)
		//if err != nil {
		//	return diag.FromErr(err)
		//}
		//
		//hasChanged = true
	case status.Deleting:
	case status.Destroyed:
		d.SetId("")
		return diag.Errorf("The resource cannot be read because it has been destroyed")
		//return resourceResgroupCreate(ctx, d, m)
	case status.Destroying:
	case status.Disabled:
	case status.Disabling:
	case status.Enabled:
	case status.Enabling:
	}

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

	flattenResourceRG(d, rgData)

	return nil
}

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

	c := m.(*controller.ControllerCfg)
	accountId := uint64(d.Get("account_id").(int))
	gid := uint64(d.Get("gid").(int))

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

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

	hasChanged := false

	switch rgData.Status {
	case status.Modeled:
	case status.Created:
	case status.Enabled:
	case status.Deleted:
		restore, ok := d.GetOk("restore")
		if ok && restore.(bool) {
			restoreReq := rg.RestoreRequest{
				RGID: rgData.ID,
			}

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

			hasChanged = true
		}

		enable, ok := d.GetOk("enable")
		if ok && enable.(bool) {
			enableReq := rg.EnableRequest{
				RGID: rgData.ID,
			}

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

			hasChanged = true
		}
	case status.Deleting:
	case status.Destroyed:
		d.SetId("")
		return diag.Errorf("The resource cannot be updated because it has been destroyed")
		//return resourceResgroupCreate(ctx, d, m)
	case status.Destroying:
	case status.Disabled:
	case status.Disabling:
	case status.Enabled:
	case status.Enabling:
	}

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

	/* NOTE: we do not allow changing the following attributes of an existing RG via terraform:
	   - def_net_type
	   - ext_net_id
	   - ext_ip

	   The following code fragment checks if any of these have been changed and generates error.
	*/
	for _, attr := range []string{"def_net_type", "ext_ip"} {
		attrNew, attrOld := d.GetChange(attr)
		if attrNew.(string) != attrOld.(string) {
			return diag.FromErr(fmt.Errorf("resourceResgroupUpdate: RG ID %s: changing %s for existing RG is not allowed", d.Id(), attr))
		}
	}

	attrNew, attrOld := d.GetChange("ext_net_id")
	if attrNew.(int) != attrOld.(int) {
		return diag.FromErr(fmt.Errorf("resourceResgroupUpdate: RG ID %s: changing ext_net_id for existing RG is not allowed", d.Id()))
	}

	doGeneralUpdate := false

	req := rg.UpdateRequest{
		RGID: rgData.ID,
	}

	if d.HasChange("rg_name") {
		req.Name = d.Get("rg_name").(string)
		doGeneralUpdate = true
	}

	if d.HasChange("resource_limits") {
		if _, ok := d.GetOk("resource_limits"); ok {
			resLimits := d.Get("resource_limits").([]interface{})[0]
			resLimitsConv := resLimits.(map[string]interface{})
			if resLimitsConv["cu_m"] != nil {
				maxMemCap := int64(resLimitsConv["cu_m"].(float64))
				if maxMemCap == 0 {
					req.MaxMemoryCapacity = -1
				} else {
					req.MaxMemoryCapacity = maxMemCap
				}
			}
			if resLimitsConv["cu_dm"] != nil {
				maxDiskCap := int64(resLimitsConv["cu_dm"].(float64))
				if maxDiskCap == 0 {
					req.MaxVDiskCapacity = -1
				} else {
					req.MaxVDiskCapacity = maxDiskCap
				}
			}
			if resLimitsConv["cu_c"] != nil {
				maxCPUCap := int64(resLimitsConv["cu_c"].(float64))
				if maxCPUCap == 0 {
					req.MaxCPUCapacity = -1
				} else {
					req.MaxCPUCapacity = maxCPUCap
				}
			}
			if resLimitsConv["cu_i"] != nil {
				maxNumPublicIP := int64(resLimitsConv["cu_i"].(float64))
				if maxNumPublicIP == 0 {
					req.MaxNumPublicIP = -1
				} else {
					req.MaxNumPublicIP = maxNumPublicIP
				}
			}

			doGeneralUpdate = true
		}
	}

	if d.HasChange("description") {
		req.Description = d.Get("description").(string)
		doGeneralUpdate = true
	}

	if d.HasChange("storage_policy") {
		needUpdate, updateEl, err := utilityRGUpdateStPolicy(ctx, d, m)
		if err != nil {
			return diag.FromErr(err)
		}
		if needUpdate {
			// spList := storagePolicies.(*schema.Set).List()
			var id uint64
			var limit int
			for _, spInterface := range updateEl {
				// sps := spInterface.(map[string]interface{})
				id = uint64(spInterface["id"].(int))
				limit = spInterface["limit"].(int)

				spModel := rg.StoragePolicy{
					ID:    id,
					Limit: limit,
				}

				req.StoragePolicies = append(req.StoragePolicies, spModel)
			}
			doGeneralUpdate = needUpdate
		}
	}

	if d.HasChange("uniq_pools") {
		uniqPools := d.Get("uniq_pools").([]interface{})
		if len(uniqPools) == 0 {
			req.ClearUniqPools = true
		}
		for _, pool := range uniqPools {
			req.UniqPools = append(req.UniqPools, pool.(string))
		}

		doGeneralUpdate = true
	}

	if doGeneralUpdate {
		log.Debugf("resourceResgroupUpdate: detected delta between new and old RG specs - updating the RG")
		_, err := c.CloudBroker().RG().Update(ctx, req)
		if err != nil {
			return diag.FromErr(err)
		}
	} else {
		log.Debugf("resourceResgroupUpdate: no difference between old and new state - no update on the RG will be done")
	}

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

	if d.HasChange("def_net") {
		oldDefNet, newDefNet := d.GetChange("def_net")
		if newDefNet.(*schema.Set).Len() > 0 {
			defNetList := newDefNet.(*schema.Set).List()
			defNetItem := defNetList[0].(map[string]interface{})
			netType := defNetItem["net_type"].(string)
			netID := uint64(defNetItem["net_id"].(int))

			if netType == "PRIVATE" {
				if netID == 0 {
					if len(rgData.VINS) == 0 {
						return diag.Errorf("resourceResgroupUpdate: cannot set def_net net_type=\"PRIVATE\" for RG ID %d: no ViNSes exist in this RG", rgData.ID)
					}
				} else {
					found := false
					for _, vinsID := range rgData.VINS {
						if vinsID == netID {
							found = true
							break
						}
					}
					if !found {
						return diag.Errorf("resourceResgroupUpdate: cannot set def_net net_type=\"PRIVATE\" for RG ID %d: ViNS ID %d is not found in this RG", rgData.ID, netID)
					}
				}
			}
		}
		if oldDefNet.(*schema.Set).Len() > 0 {
			_, err := c.CloudBroker().RG().RemoveDefNet(ctx, rg.RemoveDefNetRequest{RGID: rgData.ID})
			if err != nil {
				return diag.FromErr(err)
			}
		}
		if err := resourceRGSetDefNet(ctx, d, m); err != nil {
			return diag.FromErr(err)
		}
	}

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

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

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

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

	return resourceResgroupRead(ctx, d, m)
}

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

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

	req := rg.DeleteRequest{
		RGID: rg_facts.ID,
	}

	if force, ok := d.GetOk("force"); ok {
		req.Force = force.(bool)
	}
	if permanently, ok := d.GetOk("permanently"); ok {
		req.Permanently = permanently.(bool)
	}

	c := m.(*controller.ControllerCfg)
	_, err = c.CloudBroker().RG().Delete(ctx, req)
	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId("")

	return nil
}

func resourceRGAccessGrant(ctx context.Context, d *schema.ResourceData, m interface{}) []error {
	var errs []error

	access := d.Get("access")
	rgId := uint64(d.Get("rg_id").(int))
	log.Debugf("resourceRGAccessGrant: access %v for rg id %d", access, rgId)

	c := m.(*controller.ControllerCfg)

	var user, right string

	if access.(*schema.Set).Len() > 0 {
		accessList := access.(*schema.Set).List()
		for _, accessIface := range accessList {
			access := accessIface.(map[string]interface{})
			user = access["user"].(string)
			right = access["right"].(string)

			req := rg.AccessGrantRequest{
				RGID:  rgId,
				User:  user,
				Right: right,
			}

			if _, err := c.CloudBroker().RG().AccessGrant(ctx, req); err != nil {
				errs = append(errs, err)
			}
		}
	}

	return errs
}

func resourceRGSetDefNet(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	defNet := d.Get("def_net")
	rgId := uint64(d.Get("rg_id").(int))
	log.Debugf("resourceRGSetDefNet: def_net %v for rg id %d", defNet, rgId)

	c := m.(*controller.ControllerCfg)

	if defNet.(*schema.Set).Len() > 0 {
		defNetList := defNet.(*schema.Set).List()
		defNetItem := defNetList[0].(map[string]interface{})

		netType := defNetItem["net_type"].(string)

		req := rg.SetDefNetRequest{
			RGID:    rgId,
			NetType: netType,
		}

		if netID, ok := defNetItem["net_id"]; ok {
			req.NetID = uint64(netID.(int))
		}

		_, err := c.CloudBroker().RG().SetDefNet(ctx, req)
		return err
	}

	return nil
}

func resourceRGSetCPUAllocationParameter(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	cpuAllocationParameter := d.Get("cpu_allocation_parameter").(string)
	log.Debugf("resourceRGSetCPUAllocationParameter: cpuAllocationParameter %s for rg id %d", cpuAllocationParameter, uint64(d.Get("rg_id").(int)))

	c := m.(*controller.ControllerCfg)

	req := rg.SetCPUAllocationParameterRequest{
		RGID:        uint64(d.Get("rg_id").(int)),
		StrictLoose: cpuAllocationParameter,
	}

	_, err := c.CloudBroker().RG().SetCPUAllocationParameter(ctx, req)
	return err
}

func resourceRGSetCPUAllocationRatio(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	cpuAllocationRatio := d.Get("cpu_allocation_ratio").(float64)
	log.Debugf("resourceRGSetCPUAllocationRatio: cpuAllocationRatio %s for rg id %d", cpuAllocationRatio, uint64(d.Get("rg_id").(int)))

	c := m.(*controller.ControllerCfg)

	req := rg.SetCPUAllocationRatioRequest{
		RGID:  uint64(d.Get("rg_id").(int)),
		Ratio: cpuAllocationRatio,
	}

	_, err := c.CloudBroker().RG().SetCPUAllocationRatio(ctx, req)
	return err
}

func resourceRGChangeAccess(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	rgId := uint64(d.Get("rg_id").(int))
	log.Debugf("resourceRGChangeAccess: for rg id %d", rgId)

	c := m.(*controller.ControllerCfg)
	oldSet, newSet := d.GetChange("access")

	deletedAccess := oldSet.(*schema.Set).Difference(newSet.(*schema.Set)).List()
	for _, deletedIface := range deletedAccess {
		deleteItem := deletedIface.(map[string]interface{})
		user := deleteItem["user"].(string)

		reqRevoke := rg.AccessRevokeRequest{
			RGID: rgId,
			User: user,
		}

		_, err := c.CloudBroker().RG().AccessRevoke(ctx, reqRevoke)
		if err != nil {
			return err
		}
	}

	addedAccess := newSet.(*schema.Set).Difference(oldSet.(*schema.Set)).List()
	for _, addedIface := range addedAccess {
		addedItem := addedIface.(map[string]interface{})
		user := addedItem["user"].(string)
		right := addedItem["right"].(string)

		reqGrant := rg.AccessGrantRequest{
			RGID:  rgId,
			User:  user,
			Right: right,
		}

		_, err := c.CloudBroker().RG().AccessGrant(ctx, reqGrant)
		if err != nil {
			return err
		}
	}

	return nil
}

func resourceRGChangeEnable(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	rgId := uint64(d.Get("rg_id").(int))
	rgStatus := d.Get("status").(string)
	enable := d.Get("enable").(bool)
	log.Debugf("resourceRGChangeEnable: enable %t for rg id %d with status %s", enable, rgId, rgStatus)

	c := m.(*controller.ControllerCfg)

	if enable && rgStatus == status.Disabled {
		if _, err := c.CloudBroker().RG().Enable(ctx, rg.EnableRequest{RGID: rgId}); err != nil {
			return err
		}

	} else if !enable && (rgStatus == status.Enabled || rgStatus == status.Created) {
		req := rg.DisableRequest{RGID: rgId}

		if _, err := c.CloudBroker().RG().Disable(ctx, req); err != nil {
			return err
		}
	}

	return nil
}

func resourceRGChangeComputeFeatures(ctx context.Context, d *schema.ResourceData, m interface{}) error {
	rgId := uint64(d.Get("rg_id").(int))
	compFeaturesInterface := d.Get("compute_features").(*schema.Set).List()

	compFeatures := make([]string, 0, len(compFeaturesInterface))
	for _, item := range compFeaturesInterface {
		compFeatures = append(compFeatures, item.(string))
	}

	log.Debugf("resourceRGChangeComputeFeatures: compute_features %v for rg id %d", compFeatures, rgId)

	c := m.(*controller.ControllerCfg)

	req := rg.UpdateComputeFeaturesRequest{
		RGID:            rgId,
		ComputeFeatures: compFeatures,
	}

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

	return nil
}

func ResourceResgroup() *schema.Resource {
	return &schema.Resource{
		SchemaVersion: 1,

		CreateContext: resourceResgroupCreate,
		ReadContext:   resourceResgroupRead,
		UpdateContext: resourceResgroupUpdate,
		DeleteContext: resourceResgroupDelete,

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

		CustomizeDiff: func(ctx context.Context, diff *schema.ResourceDiff, i interface{}) error {
			if diff.HasChange("def_net") {
				diff.SetNewComputed("def_net_id")
			}
			if diff.HasChange("storage_policy") {
				diff.SetNewComputed("storage_policy_ids")
				diff.SetNewComputed("resource_limits")
			}
			if diff.HasChanges() {
				diff.SetNewComputed("updated_by")
				diff.SetNewComputed("updated_time")
			}
			return nil
		},

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

		Schema: resourceRgSchemaMake(),
	}
}
