package flattens

import (
	"context"
	"fmt"
	"strconv"

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

// VINSResource flattens resource for vins.
// Return error in case resource is not found on the platform.
// Flatten errors are added to tflog.
func VINSResource(ctx context.Context, plan *models.ResourceVINSModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start flattens.VINSResource")

	diags := diag.Diagnostics{}

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

	recordVins, diags := utilities.VINSResourceCheckPresence(ctx, vinsId, c)
	if diags.HasError() {
		return diags
	}

	tflog.Info(ctx, "flattens.VINSResource: before flatten", map[string]any{"vins_id": vinsId})

	*plan = models.ResourceVINSModel{
		Name:      types.StringValue(recordVins.Name),
		RGID:      types.Int64Value(int64(recordVins.RGID)),
		AccountID: plan.AccountID,

		IPCIDR:             plan.IPCIDR,
		PreReservationsNum: plan.PreReservationsNum,
		Description:        plan.Description,
		GID:                types.Int64Value(int64(recordVins.GID)),
		DNS:                flattens.FlattenSimpleTypeToSet(ctx, types.StringType, recordVins.VNFs.DHCP.Config.DNS),

		Enable:         types.BoolValue(recordVins.Status == "ENABLED"),
		Permanently:    plan.Permanently,
		Force:          plan.Force,
		Restore:        plan.Restore,
		VnfdevRestart:  plan.VnfdevRestart,
		VnfdevRedeploy: plan.VnfdevRedeploy,

		ExtNet:   flattenExtNet(ctx, recordVins),
		IP:       plan.IP,
		NatRule:  plan.NatRule,
		Timeouts: plan.Timeouts,

		VinsID:      types.Int64Value(int64(vinsId)),
		Id:          types.StringValue(strconv.Itoa(int(recordVins.ID))),
		LastUpdated: plan.LastUpdated,
		VNFDev:      flattenVNFDev(ctx, &recordVins.VNFDev),
		AccountName: types.StringValue(recordVins.AccountName),
		Computes:    flattenComputes(ctx, &recordVins.Computes),
		CreatedBy:   types.StringValue(recordVins.CreatedBy),
		CreatedTime: types.Int64Value(int64(recordVins.CreatedTime)),
		DefaultGW:   types.StringValue(recordVins.DefaultGW),
		DefaultQOS:  flattenQOS(ctx, &recordVins.DefaultQOS),
		DeletedBy:   types.StringValue(recordVins.DeletedBy),
		DeletedTime: types.Int64Value(int64(recordVins.DeletedTime)),
		GUID:        types.Int64Value(int64(recordVins.GUID)),
		LockStatus:  types.StringValue(recordVins.LockStatus),
		ManagerID:   types.Int64Value(int64(recordVins.ManagerID)),
		ManagerType: types.StringValue(recordVins.ManagerType),
		Milestones:  types.Int64Value(int64(recordVins.Milestones)),
		NetMask:     types.Int64Value(int64(recordVins.NetMask)),
		Network:     types.StringValue(recordVins.Network),
		Redundant:   types.BoolValue(recordVins.Redundant),
		RGName:      types.StringValue(recordVins.RGName),
		SecVNFDevID: types.Int64Value(int64(recordVins.SecVNFDevID)),
		Status:      types.StringValue(recordVins.Status),
		UpdatedBy:   types.StringValue(recordVins.UpdatedBy),
		UpdatedTime: types.Int64Value(int64(recordVins.UpdatedTime)),
		UserManaged: types.BoolValue(recordVins.UserManaged),
		VNFs:        flattenVNFs(ctx, &recordVins.VNFs),
		VXLANID:     types.Int64Value(int64(recordVins.VXLANID)),
	}

	if plan.RGID.IsUnknown() {
		plan.RGID = types.Int64Value(int64(recordVins.RGID))
	}
	if plan.AccountID.IsUnknown() {
		plan.AccountID = types.Int64Value(int64(recordVins.AccountID))
	}
	if plan.GID.IsUnknown() {
		plan.GID = types.Int64Value(int64(recordVins.GID))
	}
	if plan.PreReservationsNum.IsUnknown() {
		plan.PreReservationsNum = types.Int64Value(int64(recordVins.PreReservaionsNum))
	}
	if plan.Description.IsUnknown() {
		plan.Description = types.StringValue(recordVins.Description)
	}
	if plan.DNS.IsNull() {
		plan.DNS = types.SetNull(types.StringType)
	}

	if !plan.NatRule.IsNull() {
		plan.NatRule = flattenNatRule(ctx, plan, &recordVins.VNFs.NAT.Config.Rules)
	}

	tflog.Info(ctx, "flattens.VINSResource: after flatten", map[string]any{"vins_id": plan.Id.ValueString()})

	tflog.Info(ctx, "End flattens.VINSResource", map[string]any{"vins_id": plan.Id.ValueString()})
	return nil
}

// flattenNatRule flattens nat rule parameters:
// - rule_id (computed),
// - int_port, ext_port_end, proto (optional & computed).
// Flatten errors are added to tflog.
func flattenNatRule(ctx context.Context, plan *models.ResourceVINSModel, rules *vins.ListNATRulesConfig) types.List {
	tflog.Info(ctx, "Start flattenRuleIdInNatRule")
	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, "flattenRuleIdInNatRule: cannot populate itemsNatRulePlan with plan.NatRule list elements")
	}

	for i, natRule := range itemsNatRulePlan {
		ruleFromPlatform := natRule.GetNatRule(*rules)

		if ruleFromPlatform == nil {
			tflog.Error(ctx, fmt.Sprintf("flattenRuleIdInNatRule: rule_id can not be flatten for natRule %v because such nat_rule does not exist", natRule))
			continue
		}

		itemsNatRulePlan[i].RuleID = types.Int64Value(int64(ruleFromPlatform.ID))

		if itemsNatRulePlan[i].IntPort.ValueInt64() == 0 {
			itemsNatRulePlan[i].IntPort = types.Int64Value(int64(ruleFromPlatform.LocalPort))
		}
		if itemsNatRulePlan[i].ExtPortEnd.ValueInt64() == 0 {
			itemsNatRulePlan[i].ExtPortEnd = types.Int64Value(int64(ruleFromPlatform.PublicPortEnd))
		}
		if itemsNatRulePlan[i].Proto.ValueString() == "" {
			itemsNatRulePlan[i].Proto = types.StringValue(ruleFromPlatform.Protocol)
		}
	}

	res, err := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: models.ItemNatRuleResource}, itemsNatRulePlan)
	if err != nil {
		tflog.Error(ctx, fmt.Sprint("Error flattenRuleIdInNatRule", err))
	}

	tflog.Info(ctx, "End flattenRuleIdInNatRule")
	return res
}

// flattenExtNet flattens ExtNet.
// Flatten errors are added to tflog.
func flattenExtNet(ctx context.Context, vins *vins.RecordVINS) types.Object {
	tflog.Info(ctx, "Start flattenExtNet")

	temp := models.ExtNetModel{
		ExtNetID: types.Int64Value(int64(vins.VNFs.GW.Config.ExtNetID)),
		ExtNetIP: types.StringValue(vins.VNFs.GW.Config.ExtNetIP),
	}

	res, err := types.ObjectValueFrom(ctx, models.ItemExtNet, temp)
	if err != nil {
		tflog.Error(ctx, fmt.Sprint("Error flattenExtNet struct to obj", err))
	}

	tflog.Info(ctx, "End flattenExtNet")
	return res
}
