package flattens

import (
	"context"
	"fmt"
	"strings"

	"github.com/google/uuid"
	"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/compute"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/k8s"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/lb"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/client"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/k8s/models"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/k8s/utilities"
)

func DataSourceK8s(ctx context.Context, state *models.RecordK8SDataSourceModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start FlattenDataSourceK8s", map[string]any{"k8s_id": state.K8SID.ValueInt64()})

	diags := diag.Diagnostics{}

	k8SID := uint64(state.K8SID.ValueInt64())

	cluster, err := utilities.K8SCPResourceCheckPresence(ctx, k8SID, c)
	if err != nil {
		diags.AddError(fmt.Sprintf("Cannot get info about cluster with ID %d", k8SID), err.Error())
		return diags
	}

	k8sList, err := utilities.K8sListForResourceCheckPresence(ctx, k8SID, c)
	if err != nil || len(k8sList.Data) == 0 {
		diags.AddError(fmt.Sprintf("Cluster with ID %d not found in List", k8SID), err.Error())
		return diags
	}

	masterComputeList := make([]*compute.RecordCompute, 0, len(cluster.K8SGroups.Masters.DetailedInfo))
	workersComputeList := make([]*compute.RecordCompute, 0, len(cluster.K8SGroups.Workers))
	for _, masterNode := range cluster.K8SGroups.Masters.DetailedInfo {
		compute, err := utilities.ComputeCheckPresence(ctx, masterNode.ID, c)
		if err != nil {
			diags.AddError(fmt.Sprintf("Cannot get info about masterNode with ID %d", masterNode.ID), err.Error())
			return diags
		}
		masterComputeList = append(masterComputeList, compute)
	}
	for _, worker := range cluster.K8SGroups.Workers {
		for _, info := range worker.DetailedInfo {
			compute, err := utilities.ComputeCheckPresence(ctx, info.ID, c)
			if err != nil {
				diags.AddError(fmt.Sprintf("Cannot get info about workerNode with ID %d", info.ID), err.Error())
				return diags
			}
			workersComputeList = append(workersComputeList, compute)
		}
	}

	id := uuid.New()

	*state = models.RecordK8SDataSourceModel{
		K8SID:             state.K8SID,
		Timeouts:          state.Timeouts,
		Id:                types.StringValue(id.String()),
		ACL:               flattenACLDataSource(ctx, &cluster.ACL),
		AccountID:         types.Int64Value(int64(cluster.AccountID)),
		AccountName:       types.StringValue(cluster.AccountName),
		K8SAddressVIP:     flattenAddressVIP(ctx, cluster.AddressVIP),
		BServiceID:        types.Int64Value(int64(cluster.BServiceID)),
		K8CI:              types.Int64Value(int64(cluster.CIID)),
		CreatedBy:         types.StringValue(cluster.CreatedBy),
		CreatedTime:       types.Int64Value(int64(cluster.CreatedTime)),
		DeletedBy:         types.StringValue(cluster.DeletedBy),
		DeletedTime:       types.Int64Value(int64(cluster.DeletedTime)),
		Desc:              types.StringValue(cluster.Description),
		ExtnetOnly:        types.BoolValue(cluster.ExtnetOnly),
		HighlyAvailableLB: types.BoolValue(cluster.HighlyAvailableLB),
		K8CIName:          types.StringValue(cluster.K8CIName),
		Masters:           flattenMasterGroup(ctx, &cluster.K8SGroups.Masters, masterComputeList),
		Workers:           flattenK8sGroup(ctx, &cluster.K8SGroups.Workers, workersComputeList),
		LBID:              types.Int64Value(int64(cluster.LBID)),
		Name:              types.StringValue(cluster.Name),
		NetworkPlugin:     types.StringValue(cluster.NetworkPlugin),
		RGID:              types.Int64Value(int64(cluster.RGID)),
		RGName:            types.StringValue(cluster.RGName),
		Status:            types.StringValue(cluster.Status),
		TechStatus:        types.StringValue(cluster.TechStatus),
		UpdatedBy:         types.StringValue(cluster.UpdatedBy),
		UpdatedTime:       types.Int64Value(int64(cluster.UpdatedTime)),
		VinsId:            types.Int64Value(int64(k8sList.Data[0].VINSID)),
	}

	if cluster.LBID != 0 {
		lb, err := c.CloudAPI().LB().Get(ctx, lb.GetRequest{LBID: cluster.LBID})
		if err != nil {
			tflog.Error(ctx, fmt.Sprintf("Cannot get info about LB with ID %d", cluster.LBID)+err.Error())
		}
		state.ExtNetID = types.Int64Value(int64(lb.ExtNetID))
		state.LBIP = types.StringValue(lb.PrimaryNode.FrontendIP)
	}

	kubeconfig, err := c.CloudAPI().K8S().GetConfig(ctx, k8s.GetConfigRequest{K8SID: k8SID})
	if err != nil {
		tflog.Error(ctx, "Could not get kubeconfig:"+err.Error())
	}
	state.Kubeconfig = types.StringValue(kubeconfig)

	tflog.Info(ctx, "End FlattenDataSourceK8s", map[string]any{"k8s_id": state.K8SID.ValueInt64()})
	return nil
}

func flattenAddressVIP(ctx context.Context, addressedVip k8s.K8SAddressVIP) *models.K8SAddressVIP {
	tflog.Info(ctx, "Start flattenAddressVIP")

	res := models.K8SAddressVIP{
		BackendIP:  types.StringValue(addressedVip.BackendIP),
		FrontendIP: types.StringValue(addressedVip.FrontendIP),
	}

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

func flattenMasterGroup(ctx context.Context, mastersGroup *k8s.MasterGroup, masters []*compute.RecordCompute) *models.MasterGroupDataSourceModel {
	tflog.Info(ctx, "Start flattenMasterGroup")

	res := models.MasterGroupDataSourceModel{
		CPU:             types.Int64Value(int64(mastersGroup.CPU)),
		DetailedInfo:    flattenDetailedInfoDataSource(ctx, &mastersGroup.DetailedInfo, masters),
		Disk:            types.Int64Value(int64(mastersGroup.Disk)),
		MasterGroupId:   types.Int64Value(int64(mastersGroup.ID)),
		MasterGroupName: types.StringValue(mastersGroup.Name),
		Num:             types.Int64Value(int64(mastersGroup.Num)),
		RAM:             types.Int64Value(int64(mastersGroup.RAM)),
	}

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

func flattenK8sGroup(ctx context.Context, k8SGroupList *k8s.ListK8SGroups, workers []*compute.RecordCompute) []models.ItemK8SGroupDataSourceModel {
	tflog.Info(ctx, "Start flattenK8sGroup")

	res := make([]models.ItemK8SGroupDataSourceModel, 0, len(*k8SGroupList))
	var diags diag.Diagnostics
	for _, k8sGroup := range *k8SGroupList {
		labels := make([]string, 0)
		for _, label := range k8sGroup.Labels {
			if strings.HasPrefix(label, "workersGroupName") {
				continue
			}
			labels = append(labels, label)
		}
		temp := models.ItemK8SGroupDataSourceModel{
			CPU:             types.Int64Value(int64(k8sGroup.CPU)),
			DetailedInfo:    flattenDetailedInfoDataSource(ctx, &k8sGroup.DetailedInfo, workers),
			Disk:            types.Int64Value(int64(k8sGroup.Disk)),
			WorkerGroupID:   types.Int64Value(int64(k8sGroup.ID)),
			WorkerGroupName: types.StringValue(k8sGroup.Name),
			Num:             types.Int64Value(int64(k8sGroup.Num)),
			RAM:             types.Int64Value(int64(k8sGroup.RAM)),
		}
		temp.Annotations, diags = types.ListValueFrom(ctx, types.StringType, k8sGroup.Annotations)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenAnnotations", diags))
		}
		temp.Labels, diags = types.ListValueFrom(ctx, types.StringType, labels)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenLabels", diags))
		}
		temp.Taints, diags = types.ListValueFrom(ctx, types.StringType, k8sGroup.Taints)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenTaints", diags))
		}
		res = append(res, temp)
	}

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

func flattenDetailedInfoDataSource(ctx context.Context, di *k8s.ListDetailedInfo, computes []*compute.RecordCompute) []models.ItemDetailedInfoDataSourceModel {
	tflog.Info(ctx, "Start flattenDetailedInfoDataSource")

	res := make([]models.ItemDetailedInfoDataSourceModel, 0, len(*di))
	for i, detailedInfo := range *di {
		temp := models.ItemDetailedInfoDataSourceModel{
			ComputeId:          types.Int64Value(int64(detailedInfo.ID)),
			Name:               types.StringValue(detailedInfo.Name),
			Status:             types.StringValue(detailedInfo.Status),
			TechStatus:         types.StringValue(detailedInfo.TechStatus),
			Interfaces:         flattenInterfacesDataSource(ctx, &computes[i].Interfaces),
			NatableVinsIp:      types.StringValue(computes[i].NatableVINSIP),
			NatableVinsNetwork: types.StringValue(computes[i].NatableVINSNetwork),
		}
		res = append(res, temp)
	}

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

func flattenInterfacesDataSource(ctx context.Context, interfaces *compute.ListInterfaces) []models.ItemInterfacesDataSourceModel {
	tflog.Info(ctx, "Start flattenInterfacesDataSource")

	res := make([]models.ItemInterfacesDataSourceModel, 0, len(*interfaces))
	for _, interfaceCompute := range *interfaces {
		temp := models.ItemInterfacesDataSourceModel{
			DefGw:     types.StringValue(interfaceCompute.DefGW),
			IpAddress: types.StringValue(interfaceCompute.IPAddress),
		}
		res = append(res, temp)
	}

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

func flattenACLDataSource(ctx context.Context, acl *k8s.RecordACL) *models.RecordACLDataSourceModel {
	tflog.Info(ctx, "Start flattenACLDataSource")

	res := models.RecordACLDataSourceModel{
		AccountACL: flattenACLItemsDataSource(ctx, &acl.AccountACL),
		K8SACL:     flattenACLItemsDataSource(ctx, &acl.K8SACL),
		RGACL:      flattenACLItemsDataSource(ctx, &acl.RGACL),
	}

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

func flattenACLItemsDataSource(ctx context.Context, item *k8s.ListACL) []models.ItemACLDataSourceModel {
	tflog.Info(ctx, "Start flattenACLItemsDataSource")

	res := make([]models.ItemACLDataSourceModel, 0, len(*item))
	for _, aclItem := range *item {
		temp := models.ItemACLDataSourceModel{
			Explicit:    types.BoolValue(aclItem.Explicit),
			GUID:        types.StringValue(aclItem.GUID),
			Right:       types.StringValue(aclItem.Right),
			Status:      types.StringValue(aclItem.Status),
			Type:        types.StringValue(aclItem.Type),
			UserGroupID: types.StringValue(aclItem.UserGroupID),
		}
		res = append(res, temp)
	}

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