package utilities

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

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

func VINSResourceCheckPresence(ctx context.Context, vinsId uint64, c *client.Client) (*vins.RecordVINS, diag.Diagnostics) {
	tflog.Info(ctx, fmt.Sprintf("VINSResourceCheckPresence: Get info about vins with ID - %v", vinsId))

	diags := diag.Diagnostics{}

	recordVINS, err := c.CloudBroker().VINS().Get(ctx, vins.GetRequest{VINSID: vinsId})
	if err != nil {
		diags.AddError(fmt.Sprintf("Cannot get info about vins with ID %v", vinsId), err.Error())
		return nil, diags
	}

	tflog.Info(ctx, "VINSResourceCheckPresence: response from CloudBroker().VINS().Get", map[string]any{"vins_id": vinsId, "response": recordVINS})

	return recordVINS, nil
}

// CreateInRGResourceVINS creates vins in resource group based on plan.
// Returns vins_id for created vins and errors in case of failures.
func CreateInRGResourceVINS(ctx context.Context, plan *models.ResourceVINSModel, c *client.Client) (uint64, diag.Diagnostics) {
	tflog.Info(ctx, fmt.Sprintf("Start CreateInRGResourceVINS: vins_name %s", plan.Name.ValueString()))

	diags := diag.Diagnostics{}

	createReq := vins.CreateInRGRequest{
		Name: plan.Name.ValueString(),
		RGID: uint64(plan.RGID.ValueInt64()),
	}

	if !plan.IPCIDR.IsNull() { // IPCIDR is optional
		createReq.IPCIDR = plan.IPCIDR.ValueString()
	}

	if !plan.ExtNet.IsNull() { // ExtNet is optional
		var extnetPlan models.ExtNetModel
		tflog.Info(ctx, "CreateInRGResourceVINS: extnet specified", map[string]any{"name": plan.Name.ValueString()})
		diags.Append(plan.ExtNet.As(ctx, &extnetPlan, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true})...)
		if diags.HasError() {
			tflog.Error(ctx, "CreateInRGResourceVINS: cannot populate extnet with plan.ExtNet object element")
			return 0, diags
		}

		if extnetPlan.ExtNetID.IsNull() {
			createReq.ExtNetID = -1 // default value
		} else {
			createReq.ExtNetID = extnetPlan.ExtNetID.ValueInt64()
		}

		if !extnetPlan.ExtNetIP.IsNull() {
			createReq.ExtIP = extnetPlan.ExtNetIP.ValueString()
		}
	} else {
		createReq.ExtNetID = -1 // default value
	}

	if !plan.Description.IsNull() { // Description is optional
		createReq.Description = plan.Description.ValueString()
	}
	if plan.PreReservationsNum.IsUnknown() { // PreReservationsNum is optional & computed
		createReq.PreReservationsNum = uint64(32) // default value
	} else {
		createReq.PreReservationsNum = uint64(plan.PreReservationsNum.ValueInt64())
	}

	if !plan.DNS.IsNull() {
		result := make([]string, 0, len(plan.DNS.Elements()))
		for _, val := range plan.DNS.Elements() {
			result = append(result, strings.Trim(val.String(), "\""))
		}
		createReq.DNSList = result
	}

	tflog.Info(ctx, "CreateInRGResourceVINS: before call CloudBroker().VINS().CreateInRG", map[string]any{"req": createReq})

	vinsId, err := c.CloudBroker().VINS().CreateInRG(ctx, createReq)
	if err != nil {
		diags.AddError(
			"Create resourceVINS: unable to Create VINS in RG",
			err.Error(),
		)
		return 0, diags
	}
	tflog.Info(ctx, "CreateInRGResourceVINS: vins created", map[string]any{"vins_id": vinsId, "vins_name": plan.Name.ValueString()})

	return vinsId, nil
}

// CreateInAccountResourceVINS creates vins in account based on plan.
// Returns vins_id for created vins and errors in case of failures.
func CreateInAccountResourceVINS(ctx context.Context, plan *models.ResourceVINSModel, c *client.Client) (uint64, diag.Diagnostics) {
	tflog.Info(ctx, fmt.Sprintf("Start CreateInAccountResourceVINS: vins_name %s", plan.Name.ValueString()))

	diags := diag.Diagnostics{}

	createReq := vins.CreateInAccountRequest{
		Name:      plan.Name.ValueString(),
		AccountID: uint64(plan.AccountID.ValueInt64()),
	}

	if !plan.GID.IsUnknown() { // IPCIDR is optional & computed
		createReq.GID = uint64(plan.GID.ValueInt64())
	}

	if !plan.IPCIDR.IsNull() { // IPCIDR is optional
		createReq.IPCIDR = plan.IPCIDR.ValueString()
	}

	if !plan.Description.IsNull() { // Description is optional
		createReq.Description = plan.Description.ValueString()
	}

	if plan.PreReservationsNum.IsNull() { // PreReservationsNum is optional
		createReq.PreReservationsNum = uint64(32) // default value
	} else {
		createReq.PreReservationsNum = uint64(plan.PreReservationsNum.ValueInt64())
	}

	if !plan.DNS.IsNull() {
		result := make([]string, 0, len(plan.DNS.Elements()))
		for _, val := range plan.DNS.Elements() {
			result = append(result, strings.Trim(val.String(), "\""))
		}
		createReq.DNSList = result
	}

	tflog.Info(ctx, "CreateInAccountResourceVINS: before call CloudBroker().VINS().CreateInAccount", map[string]any{"req": createReq})

	vinsId, err := c.CloudBroker().VINS().CreateInAccount(ctx, createReq)
	if err != nil {
		diags.AddError(
			"Create resourceVINS: unable to Create VINS in Account",
			err.Error(),
		)
		return 0, diags
	}
	tflog.Info(ctx, "CreateInAccountResourceVINS: vins created", map[string]any{"vins_id": vinsId, "vins_name": plan.Name.ValueString()})

	return vinsId, nil
}

// IPCreateVINS reserves ips that user specified in ip field for created resource.
// In case of failure returns warnings.
func IPCreateVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	// plan.IP is not null as it was checked before call
	ipPlan := make([]models.IPModel, 0, len(plan.IP.Elements()))
	tflog.Info(ctx, "IPCreateVINS: new ip specified", map[string]any{"vins_id": vinsId})
	diagsItem := plan.IP.ElementsAs(ctx, &ipPlan, true)
	if diagsItem.HasError() {
		tflog.Error(ctx, fmt.Sprintf("IPCreateVINS: cannot populate ipPlan with plan.IP list elements: %v", diagsItem))
		diags.AddWarning("IPCreateVINS: Unable to read ip for vins",
			fmt.Sprintf("%v", diagsItem))
		return diags
	}

	for _, ip := range ipPlan {
		ipReserveReq := vins.IPReserveRequest{
			VINSID: vinsId,
			Type:   ip.Type.ValueString(),
		}

		if ip.IPAddr.ValueString() != "" {
			ipReserveReq.IPAddr = ip.IPAddr.ValueString()
		}
		if ip.MacAddr.ValueString() != "" {
			ipReserveReq.MAC = ip.MacAddr.ValueString()
		}
		if ip.ComputeID.ValueInt64() != 0 {
			ipReserveReq.ComputeID = uint64(ip.ComputeID.ValueInt64())
		}

		tflog.Info(ctx, "IPCreateVINS: before calling CloudBroker().VINS().IPReserve", map[string]any{
			"vins_id":      vinsId,
			"ipReserveReq": ipReserveReq})
		res, err := c.CloudBroker().VINS().IPReserve(ctx, ipReserveReq)
		if err != nil {
			diags.AddWarning("IPCreateVINS: Unable to reserve ip for vins",
				err.Error())
		}
		tflog.Info(ctx, "IPCreateVINS: response from CloudBroker().VINS().IPReserve", map[string]any{
			"vins_id":  vinsId,
			"response": res})
	}

	return diags
}

// IPUpdateVINS reserves/releases ips that user specified in ip field for updated resource.
// In case of failure returns errors.
func IPUpdateVINS(ctx context.Context, vinsId uint64, plan, state *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start IPUpdateVINS: new ip specified", map[string]any{"vins_id": vinsId})
	diags := diag.Diagnostics{}

	ipPlan := make([]models.IPModel, 0, len(plan.IP.Elements()))
	tflog.Info(ctx, "IPUpdateVINS: new ip specified", map[string]any{"vins_id": vinsId})
	diags.Append(plan.IP.ElementsAs(ctx, &ipPlan, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "IPUpdateVINS: cannot populate ipPlan with plan.IP list elements")
		return diags
	}

	ipState := make([]models.IPModel, 0, len(state.IP.Elements()))
	tflog.Info(ctx, "IPUpdateVINS: new ip specified", map[string]any{"vins_id": vinsId})
	diags.Append(state.IP.ElementsAs(ctx, &ipState, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "IPUpdateVINS: cannot populate ipState with state.IP list elements")
		return diags
	}

	// define ip to be released and release them
	var deletedIP []models.IPModel
	for _, ipStateElem := range ipState {
		if !ipStateElem.Contains(ipPlan) {
			deletedIP = append(deletedIP, ipStateElem)
		}
	}
	if len(deletedIP) == 0 {
		tflog.Info(ctx, "IPUpdateVINS: no ip needs to be release", map[string]any{"vins_id": plan.Id.ValueString()})
	}
	if len(deletedIP) > 0 {
		tflog.Info(ctx, "IPUpdateVINS: ip needs to be released", map[string]any{
			"vins_id":   plan.Id.ValueString(),
			"deletedIP": deletedIP})

		for _, deletedIPItem := range deletedIP {
			releaseIPReq := vins.IPReleaseRequest{
				VINSID: vinsId,
				IPAddr: deletedIPItem.IPAddr.ValueString(),
				MAC:    deletedIPItem.MacAddr.ValueString(),
			}

			tflog.Info(ctx, "IPUpdateVINS: before calling CloudBroker().VINS().IPRelese", map[string]any{"vins_id": plan.Id.ValueString(), "req": releaseIPReq})
			res, err := c.CloudBroker().VINS().IPRelease(ctx, releaseIPReq)
			tflog.Info(ctx, "IPUpdateVINS: response from CloudBroker().VINS().IPRelese", map[string]any{"vins_id": plan.Id.ValueString(), "response": res})
			if err != nil {
				diags.AddError(
					"IPUpdateVINS: can not release ip for VINS",
					err.Error())
			}
		}
	}

	// define ips to be reserved and reserve them
	var addedIP []models.IPModel
	for _, ipPlanElem := range ipPlan {
		if !ipPlanElem.Contains(ipState) {
			addedIP = append(addedIP, ipPlanElem)
		}
	}

	if len(addedIP) == 0 {
		tflog.Info(ctx, "IPUpdateVINS: no ip needs to be reserved", map[string]any{"vins_id": plan.Id.ValueString()})
	}
	if len(addedIP) > 0 {
		tflog.Info(ctx, "IPUpdateVINS: ip needs to be reserved", map[string]any{
			"vins_id": plan.Id.ValueString(),
			"addedIP": addedIP})

		for _, addedIPItem := range addedIP {
			ipReserveReq := vins.IPReserveRequest{
				VINSID: vinsId,
				Type:   addedIPItem.Type.ValueString(),
			}

			if addedIPItem.IPAddr.ValueString() != "" {
				ipReserveReq.IPAddr = addedIPItem.IPAddr.ValueString()
			}
			if addedIPItem.MacAddr.ValueString() != "" {
				ipReserveReq.MAC = addedIPItem.MacAddr.ValueString()
			}
			if addedIPItem.ComputeID.ValueInt64() != 0 {
				ipReserveReq.ComputeID = uint64(addedIPItem.ComputeID.ValueInt64())
			}

			tflog.Info(ctx, "IPUpdateVINS: before calling CloudBroker().VINS().IPReserve", map[string]any{
				"vins_id":      vinsId,
				"ipReserveReq": ipReserveReq})
			res, err := c.CloudBroker().VINS().IPReserve(ctx, ipReserveReq)
			if err != nil {
				diags.AddError("IPUpdateVINS: Unable to reserve ip for vins",
					err.Error())
			}
			tflog.Info(ctx, "IPUpdateVINS: response from CloudBroker().VINS().IPReserve", map[string]any{
				"vins_id":  vinsId,
				"response": res})
		}
	}

	return diags
}

// ExtNetUpdateVINS updates ext_net_id and/or ext_net_ip that user specified in ext_net block for updated resource.
// In case of failure returns errors.
func ExtNetUpdateVINS(ctx context.Context, vinsId uint64, plan, state *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start ExtNetUpdateVINS: new ext_net specified", map[string]any{
		"vins_id": vinsId,
	})
	diags := diag.Diagnostics{}

	if !state.ExtNet.IsNull() {
		disconReq := vins.ExtNetDisconnectRequest{
			VINSID: vinsId,
		}
		tflog.Info(ctx, "ExtNetUpdateVINS: before calling CloudBroker().VINS().ExtNetDisconnect", map[string]any{"vins_id": plan.Id.ValueString(), "req": disconReq})
		res, err := c.CloudBroker().VINS().ExtNetDisconnect(ctx, disconReq)
		tflog.Info(ctx, "ExtNetUpdateVINS: response from CloudBroker().VINS().ExtNetDisconnect", map[string]any{"vins_id": plan.Id.ValueString(), "response": res})
		if err != nil {
			diags.AddError(
				"ExtNetUpdateVINS: can not disconnect extnet for VINS",
				err.Error())
		}
	}

	if !plan.ExtNet.IsNull() {
		var extnetPlan models.ExtNetModel
		tflog.Info(ctx, "ExtNetUpdateVINS: new extnet specified", map[string]any{"name": plan.Name.ValueString()})
		diags.Append(plan.ExtNet.As(ctx, &extnetPlan, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true})...)
		if diags.HasError() {
			tflog.Error(ctx, "ExtNetUpdateVINS: cannot populate extnet with plan.ExtNet object element")
			return diags
		}

		conReq := vins.ExtNetConnectRequest{
			VINSID: vinsId,
		}
		if !extnetPlan.ExtNetID.IsNull() {
			conReq.NetID = uint64(extnetPlan.ExtNetID.ValueInt64())
		}
		if !extnetPlan.ExtNetIP.IsNull() {
			conReq.IP = extnetPlan.ExtNetIP.ValueString()
		}

		tflog.Info(ctx, "ExtNetUpdateVINS: before calling CloudBroker().VINS().ExtNetConnect", map[string]any{"vins_id": plan.Id.ValueString(), "req": conReq})
		res, err := c.CloudBroker().VINS().ExtNetConnect(ctx, conReq)
		tflog.Info(ctx, "ExtNetUpdateVINS: response from CloudBroker().VINS().ExtNetConnect", map[string]any{"vins_id": plan.Id.ValueString(), "response": res})
		if err != nil {
			diags.AddError(
				"ExtNetUpdateVINS: can not connect extnet to VINS",
				err.Error())
		}
	}

	return diags
}

// NATRuleCreateVINS adds nat rules that user specified in nat_rule field for created resource.
// In case of failure returns warnings.
func NATRuleCreateVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	// plan.NatRule is not null as it was checked before call
	natRulePlan := make([]models.NatRuleResourceModel, 0, len(plan.NatRule.Elements()))
	tflog.Info(ctx, "NATRuleCreateVINS: new natRule specified", map[string]any{"vins_id": vinsId})
	diagsItem := plan.NatRule.ElementsAs(ctx, &natRulePlan, false)
	if diagsItem.HasError() {
		tflog.Error(ctx, fmt.Sprintf("NATRuleCreateVINS: cannot populate natRulePlan with plan.NatRule list elements: %v", diagsItem))
		diags.AddWarning("NATRuleCreateVINS: Unable to add nat rule for vins",
			fmt.Sprintf("%v", diagsItem))
		return diags
	}

	for _, nat := range natRulePlan {
		natAddReq := vins.NATRuleAddRequest{
			VINSID:       vinsId,
			IntIP:        nat.IntIP.ValueString(),
			IntPort:      uint64(nat.IntPort.ValueInt64()),
			ExtPortStart: uint64(nat.ExtPortStart.ValueInt64()),
		}

		if !nat.ExtPortEnd.IsUnknown() {
			natAddReq.ExtPortEnd = uint64(nat.ExtPortEnd.ValueInt64())
		}
		if !nat.Proto.IsUnknown() {
			natAddReq.Proto = nat.Proto.ValueString()
		}

		tflog.Info(ctx, "NATRuleCreateVINS: before calling CloudBroker().VINS().NATRuleAdd", map[string]any{
			"vins_id":   vinsId,
			"natAddReq": natAddReq})
		res, err := c.CloudBroker().VINS().NATRuleAdd(ctx, natAddReq)
		if err != nil {
			diags.AddWarning("NATRuleCreateVINS: Unable to add nat rule for vins",
				err.Error())
		}
		tflog.Info(ctx, "NATRuleCreateVINS: response from CloudBroker().VINS().NATRuleAdd", map[string]any{
			"vins_id":  vinsId,
			"response": res})
	}

	return diags
}

// DefaultQosCreateVINS update qos that user specified in defaultQos field for created resource.
// In case of failure returns warnings.
func DefaultQosCreateVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	// plan.DefaultQOS is not null as it was checked before call
	var defaultQosPlan models.QOSModel
	tflog.Info(ctx, "DefaultQosCreateVINS: defaultQos specified", map[string]any{"name": plan.Name.ValueString()})
	diags.Append(plan.DefaultQOS.As(ctx, &defaultQosPlan, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true})...)
	if diags.HasError() {
		tflog.Error(ctx, "DefaultQosCreateVINS: cannot populate defaultQosPlan with plan.DefaultQOS object")
		diags.AddWarning("DefaultQosCreateVINS: Unable to update defaultQos for vins",
			fmt.Sprintf("cannot populate defaultQosPlan with plan.DefaultQOS object"))
		return diags
	}

	qosReq := vins.DefaultQOSUpdateRequest{
		VINSID: vinsId,
	}

	if !defaultQosPlan.InRate.IsUnknown() {
		qosReq.IngressRate = uint64(defaultQosPlan.InRate.ValueInt64())
	}
	if !defaultQosPlan.InBurst.IsUnknown() {
		qosReq.IngressBirst = uint64(defaultQosPlan.InBurst.ValueInt64())
	}
	if !defaultQosPlan.ERate.IsUnknown() {
		qosReq.EgressRate = uint64(defaultQosPlan.ERate.ValueInt64())
	}

	tflog.Info(ctx, "DefaultQosCreateVINS: before calling CloudBroker().VINS().DefaultQOSUpdate", map[string]any{
		"vins_id":   vinsId,
		"natAddReq": qosReq})
	res, err := c.CloudBroker().VINS().DefaultQOSUpdate(ctx, qosReq)
	if err != nil {
		diags.AddWarning("DefaultQosCreateVINS: Unable to update defaultQos for vins",
			err.Error())
	}
	tflog.Info(ctx, "DefaultQosCreateVINS: response from CloudBroker().VINS().DefaultQOSUpdate", map[string]any{
		"vins_id":  vinsId,
		"response": res})

	return diags
}

// NATRuleUpdateVINS adds/deleted nat rules that user specified in nat_rule field for updated resource.
// In case of failure returns errors.
func NATRuleUpdateVINS(ctx context.Context, vinsId uint64, plan, state *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start NATRuleUpdateVINS: new natRule specified", map[string]any{"vins_id": vinsId})
	diags := diag.Diagnostics{}

	itemsNatRulePlan := make([]models.NatRuleResourceModel, 0, len(plan.NatRule.Elements()))
	diags.Append(plan.NatRule.ElementsAs(ctx, &itemsNatRulePlan, false)...)
	if diags.HasError() {
		tflog.Error(ctx, "NATRuleUpdateVINS: cannot populate natRulePlan with plan.NatRule list elements")
		return diags
	}

	itemsNatRuleState := make([]models.NatRuleResourceModel, 0, len(state.NatRule.Elements()))
	diags.Append(state.NatRule.ElementsAs(ctx, &itemsNatRuleState, false)...)
	if diags.HasError() {
		tflog.Error(ctx, "NATRuleUpdateVINS: cannot populate natRuleState with state.NatRule list elements")
		return diags
	}

	// define nat rules to be deleted and delete them
	var deletedNatRule []models.NatRuleResourceModel
	for _, natRuleStateElem := range itemsNatRuleState {
		if !natRuleStateElem.Contains(itemsNatRulePlan) {
			deletedNatRule = append(deletedNatRule, natRuleStateElem)
		}
	}

	if len(deletedNatRule) == 0 {
		tflog.Info(ctx, "NATRuleUpdateVINS: no natRule needs to be deleted", map[string]any{"vins_id": plan.Id.ValueString()})
	}
	if len(deletedNatRule) > 0 {
		tflog.Info(ctx, "NATRuleUpdateVINS: natRule needs to be deleted", map[string]any{
			"vins_id":        plan.Id.ValueString(),
			"deletedNatRule": deletedNatRule})

		for _, deletedNatRuleItem := range deletedNatRule {
			deleteNATReq := vins.NATRuleDelRequest{
				VINSID: vinsId,
				RuleID: deletedNatRuleItem.RuleID.ValueInt64(),
			}

			tflog.Info(ctx, "NATRuleUpdateVINS: before calling CloudBroker().VINS().NATRuleDel", map[string]any{"vins_id": plan.Id.ValueString(), "req": deleteNATReq})
			res, err := c.CloudBroker().VINS().NATRuleDel(ctx, deleteNATReq)
			tflog.Info(ctx, "NATRuleUpdateVINS: response from CloudBroker().VINS().NATRuleDel", map[string]any{"vins_id": plan.Id.ValueString(), "response": res})
			if err != nil {
				diags.AddError(
					"NATRuleUpdateVINS: can not delete nat rule for VINS",
					err.Error())
			}
		}
	}

	// define nat rules to be added and add them
	var addedNatRules []models.NatRuleResourceModel
	for _, natRulePlanElem := range itemsNatRulePlan {
		if !natRulePlanElem.Contains(itemsNatRuleState) {
			addedNatRules = append(addedNatRules, natRulePlanElem)
		}
	}

	if len(addedNatRules) == 0 {
		tflog.Info(ctx, "NATRuleUpdateVINS: no nat rule needs to be added", map[string]any{"vins_id": plan.Id.ValueString()})
	}
	if len(addedNatRules) > 0 {
		tflog.Info(ctx, "NATRuleUpdateVINS: nat rule needs to be added", map[string]any{
			"vins_id":       plan.Id.ValueString(),
			"addedNatRules": addedNatRules})

		for _, addedNatRuleItem := range addedNatRules {
			natAddReq := vins.NATRuleAddRequest{
				VINSID:       vinsId,
				IntIP:        addedNatRuleItem.IntIP.ValueString(),
				IntPort:      uint64(addedNatRuleItem.IntPort.ValueInt64()),
				ExtPortStart: uint64(addedNatRuleItem.ExtPortStart.ValueInt64()),
			}
			if !addedNatRuleItem.ExtPortEnd.IsUnknown() {
				natAddReq.ExtPortEnd = uint64(addedNatRuleItem.ExtPortEnd.ValueInt64())
			}
			if !addedNatRuleItem.Proto.IsUnknown() {
				natAddReq.Proto = addedNatRuleItem.Proto.ValueString()
			}

			tflog.Info(ctx, "NATRuleUpdateVINS: before calling CloudBroker().VINS().NATRuleAdd", map[string]any{
				"vins_id":   vinsId,
				"natAddReq": natAddReq})
			res, err := c.CloudBroker().VINS().NATRuleAdd(ctx, natAddReq)
			if err != nil {
				diags.AddError("NATRuleUpdateVINS: Unable to add nat rule for vins",
					err.Error())
			}
			tflog.Info(ctx, "NATRuleUpdateVINS: response from CloudBroker().VINS().NATRuleAdd", map[string]any{
				"vins_id":  vinsId,
				"response": res})
		}
	}

	return diags
}

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

	diags := diag.Diagnostics{}

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

	recordVINS, diags := VINSResourceCheckPresence(ctx, vinsId, c)
	if err != nil {
		diags.AddError("VINSReadStatus: Unable to Read/Update VINS before status check", err.Error())
		return diags
	}

	// check resource status
	switch recordVINS.Status {
	case status.Modeled:
		diags.AddError(
			"VINS is in status Modeled",
			"please, contact support for more information",
		)
		return diags
	case status.Deleted:
		// attempt to restore vins
		tflog.Info(ctx, "VINSReadStatus: vins with status.Deleted is being read or updated, attempt to restore it", map[string]any{
			"vins_id": recordVINS.ID,
			"status":  recordVINS.Status})
		if state.Restore.IsNull() || state.Restore.ValueBool() { // default true or user set-up true
			diags.Append(RestoreVINS(ctx, vinsId, c)...)
			if diags.HasError() {
				tflog.Error(ctx, "VINSReadStatus: cannot restore vins")
				return diags
			}
			tflog.Info(ctx, "VINSReadStatus: vins restored successfully", map[string]any{"vins_id": vinsId})

			if state.Enable.IsNull() || state.Enable.ValueBool() {
				diags.Append(EnableVINS(ctx, vinsId, c)...)
				if diags.HasError() {
					tflog.Error(ctx, "VINSReadStatus: cannot enable vins")
					return diags
				}
				tflog.Info(ctx, "VINSReadStatus: vins enabled successfully", map[string]any{"vins_id": vinsId})
			}

			state.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
		}
	case status.Enabled:
		if !state.Enable.ValueBool() && !state.Enable.IsNull() {
			tflog.Info(ctx, "VINSReadStatus: vins with status.Enabled is being read or updated but should not be according to configuration (enable=false), attempt to disable it", map[string]any{
				"vins_id": recordVINS.ID,
				"status":  recordVINS.Status})
			diags.Append(DisableVINS(ctx, vinsId, c)...)
			if diags.HasError() {
				tflog.Error(ctx, "VINSReadStatus: cannot disable vins")
				return diags
			}
			tflog.Info(ctx, "VINSReadStatus: vins disabled successfully", map[string]any{"vins_id": vinsId})
			state.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
		}
	case status.Disabled:
		if state.Enable.ValueBool() {
			tflog.Info(ctx, "VINSReadStatus: vins with status.Disabled is being read or updated but should not be according to configuration (enable=true), attempt to enable it", map[string]any{
				"vins_id": recordVINS.ID,
				"status":  recordVINS.Status})
			diags.Append(EnableVINS(ctx, vinsId, c)...)
			if diags.HasError() {
				tflog.Error(ctx, "VINSReadStatus: cannot enable vins")
				return diags
			}
			tflog.Info(ctx, "VINSReadStatus: vins enabled successfully", map[string]any{"vins_id": vinsId})
			state.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
		}
	case status.Destroyed:
		diags.AddError(
			"VINSReadStatus: vins is in status Destroyed",
			fmt.Sprintf("the resource with vins_id %d cannot be read or updated because it has been destroyed", vinsId),
		)
		return diags
	}

	return nil
}

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

	tflog.Info(ctx, "RestoreVINS: before calling CloudBroker().VINS().Restore", map[string]any{"vinsId": vinsId, "req": vins.RestoreRequest{VINSID: vinsId}})

	res, err := c.CloudBroker().VINS().Restore(ctx, vins.RestoreRequest{VINSID: vinsId})
	if err != nil {
		diags.AddError(
			"RestoreVINS: cannot restore vins",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "RestoreVINS: response from CloudBroker().VINS().Restore", map[string]any{"vinsId": vinsId, "response": res})

	return nil
}

// DisableVINS performs vins Disable request.
// Returns error in case of failures.
func DisableVINS(ctx context.Context, vinsId uint64, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	tflog.Info(ctx, "DisableVINS: before calling CloudBroker().VINS().Disable", map[string]any{"vinsId": vinsId})

	res, err := c.CloudBroker().VINS().Disable(ctx, vins.DisableRequest{VINSID: vinsId})
	if err != nil {
		diags.AddError(
			"DisableVINS: cannot disable vins",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "DisableVINS: response from CloudBroker().VINS().Disable", map[string]any{"vinsId": vinsId, "response": res})

	return nil
}

// EnableVINS performs vins Enable request.
// Returns error in case of failures.
func EnableVINS(ctx context.Context, vinsId uint64, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	tflog.Info(ctx, "EnableVINS: before calling CloudBroker().VINS().Enable", map[string]any{"vinsId": vinsId})

	res, err := c.CloudBroker().VINS().Enable(ctx, vins.EnableRequest{VINSID: vinsId})
	if err != nil {
		diags.AddError(
			"EnableVINS: cannot enable vins",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "EnableVINS: response from CloudBroker().VINS().Enable", map[string]any{"vinsId": vinsId, "response": res})

	return nil
}

// EnableDisableUpdateVINS performs vins Enable/disable request.
// Returns errors in case of failures.
func EnableDisableUpdateVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}
	enable := plan.Enable.ValueBool()
	tflog.Info(ctx, "Start EnableDisableUpdateVINS", map[string]any{"vinsId": vinsId, "enable": enable})

	if enable {
		diags.Append(EnableVINS(ctx, vinsId, c)...)
		return diags
	}

	if !enable {
		diags.Append(DisableVINS(ctx, vinsId, c)...)
		return diags
	}

	return nil
}

// VnfdevRestartUpdateVINS restarts vnf_dev for vins.
// Returns error in case of failures.
func VnfdevRestartUpdateVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	tflog.Info(ctx, "VnfdevRestartUpdateVINS: before calling CloudBroker().VINS().VNFDevRestart", map[string]any{"vinsId": vinsId})

	req := vins.VNFDevRestartRequest{
		VINSID: vinsId,
	}

	res, err := c.CloudBroker().VINS().VNFDevRestart(ctx, req)
	if err != nil {
		diags.AddError(
			"VnfdevRestartUpdateVINS: cannot restart vnf_dev for vins",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "VnfdevRestartUpdateVINS: response from CloudBroker().VINS().VNFDevRestart", map[string]any{"vinsId": vinsId, "response": res})

	return nil
}

// VnfdevRedeployUpdateVINS redeploys vnf_dev for vins.
// Returns error in case of failures.
func VnfdevRedeployUpdateVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	tflog.Info(ctx, "VnfdevRedeployUpdateVINS: before calling CloudBroker().VINS().VNFDevRedeploy", map[string]any{"vinsId": vinsId})

	req := vins.VNFDevRedeployRequest{
		VINSID: vinsId,
	}

	res, err := c.CloudBroker().VINS().VNFDevRedeploy(ctx, req)
	if err != nil {
		diags.AddError(
			"VnfdevRedeployUpdateVINS: cannot redeploy vnf_dev for vins",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "VnfdevRedeployUpdateVINS: response from CloudBroker().VINS().VNFDevRedeploy", map[string]any{"vinsId": vinsId, "response": res})

	return nil
}

// VnfdevResetUpdateVINS reset vnf_dev for vins.
// Returns error in case of failures.
func VnfdevResetUpdateVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	tflog.Info(ctx, "VnfdevResetUpdateVINS: before calling CloudBroker().VINS().VNFDevReset", map[string]any{"vinsId": vinsId})

	req := vins.VNFDevResetRequest{
		VINSID: vinsId,
	}

	res, err := c.CloudBroker().VINS().VNFDevReset(ctx, req)
	if err != nil {
		diags.AddError(
			"VnfdevResetUpdateVINS: cannot reset vnf_dev for vins",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "VnfdevResetUpdateVINS: response from CloudBroker().VINS().VNFDevReset", map[string]any{"vinsId": vinsId, "response": res})

	return nil
}

// VnfdevStartStopUpdateVINS start/stop vnf_dev for vins.
// Returns error in case of failures.
func VnfdevStartStopUpdateVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	if plan.VnfdevStart.ValueBool() {
		req := vins.VNFDevStartRequest{
			VINSID: vinsId,
		}

		tflog.Info(ctx, "VnfdevResetUpdateVINS: before calling CloudBroker().VINS().VNFDevStart", map[string]any{"vinsId": vinsId})
		res, err := c.CloudBroker().VINS().VNFDevStart(ctx, req)
		if err != nil {
			diags.AddError(
				"VnfdevResetUpdateVINS: cannot start vnf_dev for vins",
				err.Error(),
			)
			return diags
		}
		tflog.Info(ctx, "VnfdevResetUpdateVINS: response from CloudBroker().VINS().VNFDevStart", map[string]any{"vinsId": vinsId, "response": res})
		return nil
	}

	req := vins.VNFDevStopRequest{
		VINSID: vinsId,
	}

	tflog.Info(ctx, "VnfdevResetUpdateVINS: before calling CloudBroker().VINS().VNFDevStop", map[string]any{"vinsId": vinsId})
	res, err := c.CloudBroker().VINS().VNFDevStop(ctx, req)
	if err != nil {
		diags.AddError(
			"VnfdevResetUpdateVINS: cannot start vnf_dev for vins",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "VnfdevResetUpdateVINS: response from CloudBroker().VINS().VNFDevStop", map[string]any{"vinsId": vinsId, "response": res})

	return nil
}

// UpdateDNSlistVINS apply new DNS list in VINS
// Returns error in case of failures.
func UpdateDNSlistVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	req := vins.DNSApplyRequest{
		VINSID: vinsId,
	}
	dnsList := make([]string, 0, len(plan.DNS.Elements()))
	for _, val := range plan.DNS.Elements() {
		dnsList = append(dnsList, strings.Trim(val.String(), "\""))
	}
	req.DNSList = dnsList

	tflog.Info(ctx, "UpdateDNSListVINS: before calling CloudBroker().VINS().DNSApply", map[string]any{"vinsId": vinsId})

	res, err := c.CloudBroker().VINS().DNSApply(ctx, req)
	if err != nil {
		diags.AddError(
			"UpdateDNSListVINS: cannot apply DNSList for vins",
			err.Error(),
		)
		return diags
	}
	tflog.Info(ctx, "UpdateDNSListVINS: after calling CloudBroker().VINS().DNSApply", map[string]any{"vinsId": vinsId, "response": res})

	return nil

}

// UpdateDefaultQosVINS update qos that user specified in defaultQos field for update resource.
// In case of failure returns error.
func UpdateDefaultQosVINS(ctx context.Context, vinsId uint64, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	// plan.DefaultQOS is not null as it was checked before call
	var defaultQosPlan models.QOSModel
	tflog.Info(ctx, "DefaultQosUpdateVINS: defaultQos specified", map[string]any{"name": plan.Name.ValueString()})
	diags.Append(plan.DefaultQOS.As(ctx, &defaultQosPlan, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true})...)
	if diags.HasError() {
		tflog.Error(ctx, "DefaultQosUpdateVINS: cannot populate defaultQosPlan with plan.DefaultQOS object")
		return diags
	}

	qosReq := vins.DefaultQOSUpdateRequest{
		VINSID: vinsId,
	}

	if !defaultQosPlan.InRate.IsUnknown() {
		qosReq.IngressRate = uint64(defaultQosPlan.InRate.ValueInt64())
	}
	if !defaultQosPlan.InBurst.IsUnknown() {
		qosReq.IngressBirst = uint64(defaultQosPlan.InBurst.ValueInt64())
	}
	if !defaultQosPlan.ERate.IsUnknown() {
		qosReq.EgressRate = uint64(defaultQosPlan.ERate.ValueInt64())
	}

	tflog.Info(ctx, "DefaultQosUpdateVINS: before calling CloudBroker().VINS().DefaultQOSUpdate", map[string]any{
		"vins_id":   vinsId,
		"natAddReq": qosReq})
	res, err := c.CloudBroker().VINS().DefaultQOSUpdate(ctx, qosReq)
	if err != nil {
		diags.AddError("DefaultQosUpdateVINS: Unable to update defaultQos for vins",
			err.Error())
	}
	tflog.Info(ctx, "DefaultQosUpdateVINS: response from CloudBroker().VINS().DefaultQOSUpdate", map[string]any{
		"vins_id":  vinsId,
		"response": res})

	return diags
}
