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

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

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

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

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

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

package rg

import (
	"context"
	"fmt"
	"strconv"

	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/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"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceResgroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	rgName, ok := d.GetOk("name")
	if !ok {
		return diag.FromErr(fmt.Errorf("cannot create new RG: missing name"))
	}

	log.Debugf("resourceResgroupCreate: called for RG name %s, account ID %d",
		rgName.(string), d.Get("account_id").(int))

	haveAccount, err := existAccountID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}
	if !haveAccount {
		return diag.Errorf("resourceResgroupCreate: can't create RG because AccountID %d is not allowed or does not exist", d.Get("account_id").(int))
	}

	haveGID, err := existGID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}
	if !haveGID {
		return diag.Errorf("resourceResgroupCreate: can't create RG because GID %d is not allowed or does not exist", d.Get("gid").(int))
	}

	if _, ok := d.GetOk("ext_net_id"); ok {
		haveExtNet, err := existExtNetID(ctx, d, m)
		if err != nil {
			return diag.FromErr(err)
		}
		if !haveExtNet {
			return diag.Errorf("resourceResgroupCreate: can't create RG because ExtNetID %d is not allowed or does not exist", d.Get("ext_net_id").(int))
		}
	}
	c := m.(*controller.ControllerCfg)
	req := rg.CreateRequest{
		Name:      rgName.(string),
		AccountID: uint64(d.Get("account_id").(int)),
		GID:       uint64(d.Get("gid").(int)),
		// Owner:     c.GetDecortUsername(),
	}

	if quota, ok := d.GetOk("quota"); ok {
		quotaBlocks := quota.([]interface{})[0]
		quotaMap := quotaBlocks.(map[string]interface{})

		if quotaMap["ram"] != nil {
			maxMemCap := int64(quotaMap["ram"].(int))
			if maxMemCap == 0 {
				req.MaxMemoryCapacity = -1
			} else {
				req.MaxMemoryCapacity = maxMemCap
			}
		}
		if quotaMap["disk"] != nil {
			maxDiskCap := int64(quotaMap["disk"].(int))
			if maxDiskCap == 0 {
				req.MaxVDiskCapacity = -1
			} else {
				req.MaxVDiskCapacity = maxDiskCap
			}
		}
		if quotaMap["cpu"] != nil {
			maxCPUCap := int64(quotaMap["cpu"].(int))
			if maxCPUCap == 0 {
				req.MaxCPUCapacity = -1
			} else {
				req.MaxCPUCapacity = maxCPUCap
			}
		}
		if quotaMap["ext_ips"] != nil {
			maxNumPublicIP := int64(quotaMap["ext_ips"].(int))
			if maxNumPublicIP == 0 {
				req.MaxNumPublicIP = -1
			} else {
				req.MaxNumPublicIP = maxNumPublicIP
			}
		}
	}
	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) // NOTE: in API default network type is set by "def_net" parameter
		}
	} else {
		req.DefNet = "NONE"
	}

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

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

	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 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)
			}
		}
	}

	apiResp, err := c.CloudAPI().RG().Create(ctx, req)
	if err != nil {
		return diag.FromErr(err)
	}

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

	w := dc.Warnings{}
	if access, ok := d.GetOk("access"); ok {
		var user, right string

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

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

				_, err := c.CloudAPI().RG().AccessGrant(ctx, req)
				if err != nil {
					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{})

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

			if netType == "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")
			}

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

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

			_, err := c.CloudAPI().RG().SetDefNet(ctx, req)
			if err != nil {
				w.Add(err)
			}

		}

	}

	if enable, ok := d.GetOk("enable"); ok {
		if enable.(bool) {
			req := rg.EnableRequest{
				RGID: apiResp,
			}

			_, err := c.CloudAPI().RG().Enable(ctx, req)
			if err != nil {
				w.Add(err)
			}
		} else {
			req := rg.DisableRequest{
				RGID: apiResp,
			}

			_, err := c.CloudAPI().RG().Disable(ctx, req)
			if err != nil {
				w.Add(err)
			}
		}
	}

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

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("name").(string), d.Get("account_id").(int))

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

	rgData, err := utilityResgroupCheckPresence(ctx, d, m)
	if err != nil {
		d.SetId("")
		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.Deleted:
		// restoreReq := rg.RestoreRequest{RGID: rgData.ID}
		// enableReq := rg.EnableRequest{RGID: rgData.ID}

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

		// _, err = c.CloudAPI().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)
		}
	}

	return diag.FromErr(flattenResgroup(d, *rgData))
}

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("name").(string), d.Get("account_id").(int))

	c := m.(*controller.ControllerCfg)

	haveAccount, err := existAccountID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}
	if !haveAccount {
		return diag.Errorf("resourceResgroupUpdate: can't create RG bacause AccountID %d not allowed or does not exist", d.Get("account_id").(int))
	}

	haveGID, err := existGID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}
	if !haveGID {
		return diag.Errorf("resourceResgroupUpdate: can't create RG because GID %d not allowed or does not exist", d.Get("gid").(int))
	}

	if _, ok := d.GetOk("ext_net_id"); ok {
		haveExtNet, err := existExtNetID(ctx, d, m)
		if err != nil {
			return diag.FromErr(err)
		}
		if !haveExtNet {
			return diag.Errorf("resourceResgroupUpdate: can't create RG bacause ExtNetID %d not allowed or does not exist", d.Get("ext_net_id").(int))
		}
	}

	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:
		if restore, ok := d.GetOk("restore"); ok && restore.(bool) {
			restoreReq := rg.RestoreRequest{RGID: rgData.ID}

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

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

				_, err = c.CloudAPI().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
	   - ipcidr
	   - 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", "ipcidr", "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))
		}
	}

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

	needUpdateSP := false
	if d.HasChange("storage_policy") {
		needUpdate, err := utilityRGUpdateStPolicy(ctx, d, m)
		if err != nil {
			return diag.FromErr(err)
		}
		needUpdateSP = needUpdate
	}

	if d.HasChanges("name", "quota", "description", "uniq_pools") || needUpdateSP {
		if err := utilityUpdateRG(ctx, d, m, rgData.ID); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("enable") {
		enableOld, enableNew := d.GetChange("enable")
		if enableOld.(bool) && !enableNew.(bool) {
			req := rg.DisableRequest{RGID: rgData.ID}

			_, err := c.CloudAPI().RG().Disable(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		} else if !enableOld.(bool) && enableNew.(bool) {
			req := rg.EnableRequest{RGID: rgData.ID}

			_, err := c.CloudAPI().RG().Enable(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

	if d.HasChange("access") {
		oldSet, newSet := d.GetChange("access")
		deletedAccess := (oldSet.(*schema.Set).Difference(newSet.(*schema.Set))).List()
		for _, deletedInterface := range deletedAccess {
			deletedItem := deletedInterface.(map[string]interface{})
			user := deletedItem["user"].(string)
			req := rg.AccessRevokeRequest{
				RGID: rgData.ID,
				User: user,
			}

			_, err := c.CloudAPI().RG().AccessRevoke(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		}

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

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

			_, err := c.CloudAPI().RG().AccessGrant(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

	if d.HasChange("def_net") {
		oldDefNet, newDefNet := d.GetChange("def_net")
		if oldDefNet.(*schema.Set).Len() > 0 {
			_, err := c.CloudAPI().RG().RemoveDefNet(ctx, rg.RemoveDefNetRequest{RGID: rgData.ID})
			if err != nil {
				return diag.FromErr(err)
			}
		}
		if newDefNet.(*schema.Set).Len() > 0 {
			changedDefNet := (newDefNet.(*schema.Set).Difference(oldDefNet.(*schema.Set))).List()
			for _, changedDefNetInterface := range changedDefNet {
				defNetItem := changedDefNetInterface.(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)
						}
					}
				}

				req := rg.SetDefNetRequest{
					RGID:    rgData.ID,
					NetType: netType,
					NetID:   netID,
				}

				_, err := c.CloudAPI().RG().SetDefNet(ctx, req)
				if 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("name").(string), d.Get("account_id").(int))

	c := m.(*controller.ControllerCfg)
	rgId, _ := strconv.ParseUint(d.Id(), 10, 64)
	req := rg.DeleteRequest{
		RGID: rgId,
	}

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

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

	d.SetId("")

	return nil
}

func ResourceRgSchemaMake() map[string]*schema.Schema {
	return map[string]*schema.Schema{
		"account_id": {
			Type:         schema.TypeInt,
			Required:     true,
			ValidateFunc: validation.IntAtLeast(1),
			Description:  "Unique ID of the account, which this resource group belongs to.",
		},

		"gid": {
			Type:     schema.TypeInt,
			Required: true,
			// ForceNew:    true,
			Description: "Unique ID of the grid, where this resource group is deployed.",
		},

		"name": {
			Type:        schema.TypeString,
			Required:    true,
			Description: "Name of this resource group. Names are case sensitive and unique within the context of a account.",
		},

		"def_net_type": {
			Type:         schema.TypeString,
			Optional:     true,
			ValidateFunc: validation.StringInSlice([]string{"PRIVATE", "PUBLIC", "NONE"}, false),
			Description:  "Type of the network, which this resource group will use as default for its computes - PRIVATE or PUBLIC or NONE.",
		},

		"def_net_id": {
			Type:        schema.TypeInt,
			Computed:    true,
			Description: "ID of the default network for this resource group (if any).",
		},

		"ipcidr": {
			Type:        schema.TypeString,
			Optional:    true,
			Description: "Address of the netowrk inside the private network segment (aka ViNS) if def_net_type=PRIVATE",
		},

		"ext_net_id": {
			Type:        schema.TypeInt,
			Optional:    true,
			Default:     0,
			Description: "ID of the external network for default ViNS. Pass 0 if def_net_type=PUBLIC or no external connection required for the defult ViNS when def_net_type=PRIVATE",
		},

		"ext_ip": {
			Type:        schema.TypeString,
			Optional:    true,
			Description: "IP address on the external netowrk to request when def_net_type=PRIVATE and ext_net_id is not 0",
		},

		"owner": {
			Type:     schema.TypeString,
			Optional: true,
		},

		"quota": {
			Type:     schema.TypeList,
			Optional: true,
			Computed: true,
			MaxItems: 1,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"cpu": {
						Type:        schema.TypeInt,
						Optional:    true,
						Computed:    true,
						Description: "Limit on the total number of CPUs in this resource group.",
					},
					"ram": {
						Type:        schema.TypeInt, // NB: API expects and returns this as float in units of MB! This may be changed in the future.
						Optional:    true,
						Computed:    true,
						Description: "Limit on the total amount of RAM in this resource group, specified in MB.",
					},
					"disk": {
						Type:        schema.TypeInt,
						Optional:    true,
						Computed:    true,
						Description: "Limit on the total volume of virtual storage resources in this resource group, specified in GB.",
					},
					"ext_ips": {
						Type:        schema.TypeInt,
						Optional:    true,
						Computed:    true,
						Description: "Limit on the total number of external IP addresses this resource group can use.",
					},
					"gpu_units": {
						Type:        schema.TypeInt,
						Computed:    true,
						Description: "Limit on the total number of virtual GPUs this resource group can use.",
					},
					"cu_d": {
						Type:        schema.TypeInt,
						Computed:    true,
						Description: "Limit on the total volume of storage resources in this resource group, specified in GB.",
					},
				},
			},
			Description: "Quota settings for this resource group.",
		},

		"access": {
			Type:     schema.TypeSet,
			Optional: true,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"user": {
						Type:        schema.TypeString,
						Required:    true,
						Description: "User or group name to grant access",
					},
					"right": {
						Type:        schema.TypeString,
						Required:    true,
						Description: "Access rights to set, one of 'R', 'RCX' or 'ARCXDU'",
					},
				},
			},
		},
		"def_net": {
			Type:     schema.TypeSet,
			Optional: true,
			MaxItems: 1,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"net_type": {
						Type:         schema.TypeString,
						Required:     true,
						ValidateFunc: validation.StringInSlice([]string{"PRIVATE", "PUBLIC"}, false),
						Description:  "Network type to set. Must be on of 'PRIVATE' or 'PUBLIC'.",
					},
					"net_id": {
						Type:        schema.TypeInt,
						Optional:    true,
						Default:     0,
						Description: "Network segment ID. If netType is PUBLIC and netId is 0 then default external network segment will be selected. If netType is PRIVATE and netId=0, the first ViNS defined for this RG will be selected. Otherwise, netId identifies either existing external network segment or ViNS.",
					},
				},
			},
		},
		"storage_policy": {
			Type:     schema.TypeSet,
			Optional: true,
			Computed: true,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"id": {
						Type:     schema.TypeInt,
						Required: true,
					},
					"limit": {
						Type:     schema.TypeInt,
						Optional: true,
						Default:  -1,
					},
				},
			},
		},
		"description": {
			Type:        schema.TypeString,
			Optional:    true,
			Computed:    true,
			Description: "User-defined text description of this resource group.",
		},
		"force": {
			Type:        schema.TypeBool,
			Optional:    true,
			Default:     false,
			Description: "Set to True if you want force delete non-empty RG",
		},
		"permanently": {
			Type:        schema.TypeBool,
			Optional:    true,
			Default:     false,
			Description: "Set to True if you want force delete non-empty RG",
		},
		"restore": {
			Type:     schema.TypeBool,
			Optional: true,
			Default:  false,
		},
		"enable": {
			Type:        schema.TypeBool,
			Optional:    true,
			Default:     true,
			Description: "flag for enable/disable RG",
		},
		"sdn_access_group_id": {
			Type:        schema.TypeString,
			Optional:    true,
			Computed:    true,
			Description: "ID of the SDN access group",
		},

		"account_name": {
			Type:        schema.TypeString,
			Computed:    true,
			Description: "Name of the account, which this resource group belongs to.",
		},
		// "resources": {
		// 	Type:     schema.TypeList,
		// 	Computed: true,
		// 	Elem: &schema.Resource{
		// 		Schema: resourcesSchemaMake(),
		// 	},
		// },

		"acl": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Resource{
				Schema: aclSchemaMake(),
			},
		},
		"compute_features": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeString,
			},
		},
		"created_by": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"created_time": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"deleted_by": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"deleted_time": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"dirty": {
			Type:     schema.TypeBool,
			Computed: true,
		},
		"guid": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"rg_id": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"lock_status": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"milestones": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"secret": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"status": {
			Type:        schema.TypeString,
			Computed:    true,
			Description: "Current status of this resource group.",
		},
		"updated_by": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"updated_time": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"vins": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeInt,
			},
			Description: "List of VINs deployed in this resource group.",
		},

		"vms": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeInt,
			},
			Description: "List of computes deployed in this resource group.",
		},

		"res_types": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeString,
			},
		},
		"uniq_pools": {
			Type:     schema.TypeList,
			Optional: true,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeString,
			},
		},
		"cpu_allocation_parameter": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"cpu_allocation_ratio": {
			Type:     schema.TypeFloat,
			Computed: true,
		},
		"storage_policy_ids": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeInt,
			},
		},
		"resource_limits": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"cu_c": {
						Type:     schema.TypeFloat,
						Computed: true,
					},
					"cu_d": {
						Type:     schema.TypeFloat,
						Computed: true,
					},
					"cu_dm": {
						Type:     schema.TypeFloat,
						Computed: true,
					},
					"cu_i": {
						Type:     schema.TypeFloat,
						Computed: true,
					},
					"cu_m": {
						Type:     schema.TypeFloat,
						Computed: true,
					},
					"gpu_units": {
						Type:     schema.TypeFloat,
						Computed: true,
					},
					"storage_policy": {
						Type:     schema.TypeSet,
						Computed: true,
						Elem: &schema.Resource{
							Schema: map[string]*schema.Schema{
								"id": {
									Type:     schema.TypeInt,
									Computed: true,
								},
								"limit": {
									Type:     schema.TypeInt,
									Computed: true,
								},
							},
						},
					},
				},
			},
		},
	}
}

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

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

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

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

		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
		},

		Schema: ResourceRgSchemaMake(),
		StateUpgraders: []schema.StateUpgrader{
			{
				Type:    resourceRGResourceV1().CoreConfigSchema().ImpliedType(),
				Upgrade: resourceRGStateUpgradeV1,
				Version: 1,
			},
		},
	}
}
