package flattens

import (
	"context"
	"encoding/json"
	"fmt"

	"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/terraform-provider-dynamix/internal/client"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/flattens"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/kvmvm/models"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/kvmvm/utilities"
)

func DataSourceCompute(ctx context.Context, state *models.RecordComputeModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start FlattenDataSourceCompute")

	diags := diag.Diagnostics{}

	computeRecord, err := utilities.ComputeCheckPresence(ctx, state, c)
	if err != nil {
		diags.AddError("Error get compute info", err.Error())
		return diags
	}

	pciDevicesList, err := utilities.ComputePCIDevicesListCheckPresence(ctx, state, c)

	id := uuid.New()

	customFields, _ := json.Marshal(computeRecord.CustomFields)
	devices, _ := json.Marshal(computeRecord.Devices)
	userdata, _ := json.Marshal(computeRecord.Userdata)

	*state = models.RecordComputeModel{
		Timeouts:               state.Timeouts,
		Id:                     types.StringValue(id.String()),
		ComputeId:              state.ComputeId,
		ACL:                    flattenACL(ctx, &computeRecord.ACL),
		AccountID:              types.Int64Value(int64(computeRecord.AccountID)),
		AccountName:            types.StringValue(computeRecord.AccountName),
		AffinityLabel:          types.StringValue(computeRecord.AffinityLabel),
		AffinityRules:          flattenAffinityRule(ctx, &computeRecord.AffinityRules),
		AffinityWeight:         types.Int64Value(int64(computeRecord.AffinityWeight)),
		AntiAffinityRules:      flattenAffinityRule(ctx, &computeRecord.AntiAffinityRules),
		Architecture:           types.StringValue(computeRecord.Architecture),
		AutoStartWithNode:      types.BoolValue(computeRecord.AutoStart),
		Chipset:                types.StringValue(computeRecord.Chipset),
		BootDiskSize:           types.Int64Value(int64(computeRecord.BootDiskSize)),
		BootOrder:              flattens.FlattenSimpleTypeToList(ctx, types.StringType, &computeRecord.BootOrder),
		BootType:               types.StringValue(computeRecord.BootType),
		CdImageId:              types.Int64Value(int64(computeRecord.CdImageId)),
		Clones:                 flattens.FlattenSimpleTypeToList(ctx, types.Int64Type, &computeRecord.Clones),
		CloneReference:         types.Int64Value(int64(computeRecord.CloneReference)),
		ComputeCIID:            types.Int64Value(int64(computeRecord.ComputeCIID)),
		CPU:                    types.Int64Value(int64(computeRecord.CPU)),
		CPUPin:                 types.BoolValue(computeRecord.CPUPin),
		CreatedBy:              types.StringValue(computeRecord.CreatedBy),
		CreatedTime:            types.Int64Value(int64(computeRecord.CreatedTime)),
		CustomFields:           types.StringValue(string(customFields)),
		DeletedBy:              types.StringValue(computeRecord.DeletedBy),
		DeletedTime:            types.Int64Value(int64(computeRecord.DeletedTime)),
		Description:            types.StringValue(computeRecord.Description),
		Devices:                types.StringValue(string(devices)),
		Disks:                  flattenDisks(ctx, &computeRecord.Disks),
		Driver:                 types.StringValue(computeRecord.Driver),
		GID:                    types.Int64Value(int64(computeRecord.GID)),
		GUID:                   types.Int64Value(int64(computeRecord.GUID)),
		HPBacked:               types.BoolValue(computeRecord.HPBacked),
		HotResize:              types.BoolValue(computeRecord.HotResize),
		ImageID:                types.Int64Value(int64(computeRecord.ImageID)),
		ImageName:              types.StringValue(computeRecord.ImageName),
		Interfaces:             flattenInterfaces(ctx, &computeRecord.Interfaces),
		LockStatus:             types.StringValue(computeRecord.LockStatus),
		LoaderType:             types.StringValue(computeRecord.LoaderType),
		ManagerID:              types.Int64Value(int64(computeRecord.ManagerID)),
		ManagerType:            types.StringValue(computeRecord.ManagerType),
		MigrationJob:           types.Int64Value(int64(computeRecord.MigrationJob)),
		Milestones:             types.Int64Value(int64(computeRecord.Milestones)),
		Name:                   types.StringValue(computeRecord.Name),
		NatableVINSID:          types.Int64Value(int64(computeRecord.NatableVINSID)),
		NatableVINSIP:          types.StringValue(computeRecord.NatableVINSIP),
		NatableVINSName:        types.StringValue(computeRecord.NatableVINSName),
		NatableVINSNetwork:     types.StringValue(computeRecord.NatableVINSNetwork),
		NatableVINSNetworkName: types.StringValue(computeRecord.NatableVINSNetworkName),
		NeedReboot:             types.BoolValue(computeRecord.NeedReboot),
		NetworkInterfaceNaming: types.StringValue(computeRecord.NetworkInterfaceNaming),
		NumaAffinity:           types.StringValue(computeRecord.NumaAffinity),
		NumaNodeId:             types.Int64Value(int64(computeRecord.NumaNodeId)),
		OSUsers:                flattenOSUsers(ctx, &computeRecord.OSUsers),
		//Pinned:                 types.BoolValue(computeRecord.Pinned),
		PreferredCPU:     flattens.FlattenSimpleTypeToList(ctx, types.Int64Type, &computeRecord.PreferredCPU),
		PCIDevices:       flattens.FlattenSimpleTypeToList(ctx, types.Int64Type, flattenPCI(ctx, pciDevicesList)),
		RAM:              types.Int64Value(int64(computeRecord.RAM)),
		ReferenceID:      types.StringValue(computeRecord.ReferenceID),
		Registered:       types.BoolValue(computeRecord.Registered),
		ResName:          types.StringValue(computeRecord.ResName),
		ReservedNodeCpus: flattens.FlattenSimpleTypeToList(ctx, types.Int64Type, &computeRecord.ReservedNodeCpus),
		RGID:             types.Int64Value(int64(computeRecord.RGID)),
		RGName:           types.StringValue(computeRecord.RGName),
		SnapSets:         flattenSnapSet(ctx, &computeRecord.SnapSets),
		StatelessSepID:   types.Int64Value(int64(computeRecord.StatelessSepID)),
		StatelessSepType: types.StringValue(computeRecord.StatelessSepType),
		Status:           types.StringValue(computeRecord.Status),
		TechStatus:       types.StringValue(computeRecord.TechStatus),
		UpdatedBy:        types.StringValue(computeRecord.UpdatedBy),
		UpdatedTime:      types.Int64Value(int64(computeRecord.UpdatedTime)),
		UserManaged:      types.BoolValue(computeRecord.UserManaged),
		Userdata:         types.StringValue(string(userdata)),
		VirtualImageID:   types.Int64Value(int64(computeRecord.VirtualImageID)),
		VirtualImageName: types.StringValue(computeRecord.VirtualImageName),
		VNCPassword:      types.StringValue(computeRecord.VNCPassword),
		VGPUs:            flattenComputeVGPU(ctx, computeRecord.VGPUs),
	}

	state.Tags, diags = types.MapValueFrom(ctx, types.StringType, computeRecord.Tags)
	if diags != nil {
		tflog.Error(ctx, fmt.Sprint("Error FlattenDataSourceCompute", diags))
	}

	tflog.Info(ctx, "End FlattenDataSourceCompute")
	return nil
}

func flattenACL(ctx context.Context, acl *compute.RecordACL) *models.RecordACLModel {
	tflog.Info(ctx, "Start flattenACL")

	res := models.RecordACLModel{
		AccountACL: flattenACLList(ctx, &acl.AccountACL),
		ComputeACL: flattenACLList(ctx, &acl.ComputeACL),
		RGACL:      flattenACLList(ctx, &acl.RGACL),
	}

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

func flattenACLList(ctx context.Context, acl *compute.ListACL) []models.ItemACLModel {
	tflog.Info(ctx, "Start flattenACLList")

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

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

func flattenAffinityRule(ctx context.Context, rules *compute.ListRules) []models.ItemRuleModel {
	tflog.Info(ctx, "Start flattenAffinityRule")

	res := make([]models.ItemRuleModel, 0, len(*rules))
	for _, item := range *rules {
		temp := models.ItemRuleModel{
			GUID:     types.StringValue(item.GUID),
			Key:      types.StringValue(item.Key),
			Mode:     types.StringValue(item.Mode),
			Policy:   types.StringValue(item.Policy),
			Topology: types.StringValue(item.Topology),
			Value:    types.StringValue(item.Value),
		}
		res = append(res, temp)
	}

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

func flattenDisks(ctx context.Context, disks *compute.ListComputeDisks) []models.ItemDiskModel {
	tflog.Info(ctx, "Start flattenDisks")

	var diags diag.Diagnostics

	res := make([]models.ItemDiskModel, 0, len(*disks))
	for _, item := range *disks {
		acl, _ := json.Marshal(item.ACL)
		temp := models.ItemDiskModel{
			CKey:            types.StringValue(item.CKey),
			ACL:             types.StringValue(string(acl)),
			AccountID:       types.Int64Value(int64(item.AccountID)),
			BootPartition:   types.Int64Value(int64(item.BootPartition)),
			BusNumber:       types.Int64Value(int64(item.BusNumber)),
			CreatedTime:     types.Int64Value(int64(item.CreatedTime)),
			DeletedTime:     types.Int64Value(int64(item.CreatedTime)),
			Description:     types.StringValue(item.Description),
			DestructionTime: types.Int64Value(int64(item.DestructionTime)),
			DiskPath:        types.StringValue(item.DiskPath),
			GID:             types.Int64Value(int64(item.GID)),
			GUID:            types.Int64Value(int64(item.GUID)),
			ID:              types.Int64Value(int64(item.ID)),
			ImageID:         types.Int64Value(int64(item.ImageID)),
			IOTune: &models.IOTuneModel{
				ReadBytesSec:     types.Int64Value(int64(item.IOTune.ReadBytesSec)),
				ReadBytesSecMax:  types.Int64Value(int64(item.IOTune.ReadBytesSecMax)),
				ReadIOPSSec:      types.Int64Value(int64(item.IOTune.ReadIOPSSec)),
				ReadIOPSSecMax:   types.Int64Value(int64(item.IOTune.ReadIOPSSecMax)),
				SizeIOPSSec:      types.Int64Value(int64(item.IOTune.SizeIOPSSec)),
				TotalBytesSec:    types.Int64Value(int64(item.IOTune.TotalBytesSec)),
				TotalBytesSecMax: types.Int64Value(int64(item.IOTune.TotalBytesSecMax)),
				TotalIOPSSec:     types.Int64Value(int64(item.IOTune.TotalIOPSSec)),
				TotalIOPSSecMax:  types.Int64Value(int64(item.IOTune.TotalIOPSSecMax)),
				WriteBytesSec:    types.Int64Value(int64(item.IOTune.WriteBytesSec)),
				WriteBytesSecMax: types.Int64Value(int64(item.IOTune.WriteBytesSecMax)),
				WriteIOPSSec:     types.Int64Value(int64(item.IOTune.WriteIOPSSec)),
				WriteIOPSSecMax:  types.Int64Value(int64(item.IOTune.WriteIOPSSecMax)),
			},
			IQN:                 types.StringValue(item.IQN),
			Login:               types.StringValue(item.Login),
			Milestones:          types.Int64Value(int64(item.Milestones)),
			Name:                types.StringValue(item.Name),
			Order:               types.Int64Value(int64(item.Order)),
			Params:              types.StringValue(item.Params),
			ParentID:            types.Int64Value(int64(item.ParentID)),
			Passwd:              types.StringValue(item.Passwd),
			Pool:                types.StringValue(item.Pool),
			PCISlot:             types.Int64Value(item.PCISlot),
			PurgeTime:           types.Int64Value(int64(item.PurgeTime)),
			RealityDeviceNumber: types.Int64Value(int64(item.RealityDeviceNumber)),
			Replication: &models.ReplicationModel{
				DiskID:       types.Int64Value(int64(item.Replication.DiskID)),
				PoolID:       types.StringValue(item.Replication.PoolID),
				Role:         types.StringValue(item.Replication.Role),
				SelfVolumeID: types.StringValue(item.Replication.SelfVolumeID),
				StorageID:    types.StringValue(item.Replication.StorageID),
				VolumeID:     types.StringValue(item.Replication.VolumeID),
			},
			ResID:         types.StringValue(item.ResID),
			Role:          types.StringValue(item.Role),
			SepID:         types.Int64Value(int64(item.SepID)),
			Shareable:     types.BoolValue(item.Shareable),
			SizeAvailable: types.Float64Value(item.SizeAvailable),
			SizeMax:       types.Int64Value(int64(item.SizeMax)),
			SizeUsed:      types.Float64Value(item.SizeUsed),
			Snapshots:     flattenSnapshotExtend(ctx, &item.Snapshots),
			Status:        types.StringValue(item.Status),
			TechStatus:    types.StringValue(item.TechStatus),
			Type:          types.StringValue(item.Type),
			VMID:          types.Int64Value(int64(item.VMID)),
		}
		temp.Images, diags = types.ListValueFrom(ctx, types.Int64Type, item.Images)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenDisks", diags))
		}
		temp.PresentTo, diags = types.MapValueFrom(ctx, types.Int64Type, item.PresentTo)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenDisks", diags))
		}
		res = append(res, temp)
	}

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

func flattenSnapshotExtend(ctx context.Context, snapshot *compute.SnapshotExtendList) []models.ItemSnapshotExtendModel {
	tflog.Info(ctx, "Start flattenSnapshotExtend")

	res := make([]models.ItemSnapshotExtendModel, 0, len(*snapshot))
	for _, item := range *snapshot {
		temp := models.ItemSnapshotExtendModel{
			GUID:        types.StringValue(item.GUID),
			Label:       types.StringValue(item.Label),
			ReferenceID: types.StringValue(item.ReferenceID),
			ResID:       types.StringValue(item.ResID),
			SnapSetGUID: types.StringValue(item.SnapSetGUID),
			SnapSetTime: types.Int64Value(int64(item.SnapSetTime)),
			TimeStamp:   types.Int64Value(int64(item.TimeStamp)),
		}

		res = append(res, temp)
	}

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

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

	var diags diag.Diagnostics

	res := make([]models.ItemInterfaceModel, 0, len(*interfaces))
	for _, item := range *interfaces {
		temp := models.ItemInterfaceModel{
			BusNumber:   types.Int64Value(int64(item.BusNumber)),
			ConnID:      types.Int64Value(int64(item.ConnID)),
			ConnType:    types.StringValue(item.ConnType),
			DefGW:       types.StringValue(item.DefGW),
			Enabled:     types.BoolValue(item.Enabled),
			FLIPGroupID: types.Int64Value(int64(item.FLIPGroupID)),
			GUID:        types.StringValue(item.GUID),
			IPAddress:   types.StringValue(item.IPAddress),
			LibvirtSettings: &models.LibvirtModel{
				GUID:        types.StringValue(item.LibvirtSettings.GUID),
				TXMode:      types.StringValue(item.LibvirtSettings.TXMode),
				IOEventFD:   types.StringValue(item.LibvirtSettings.IOEventFD),
				EventIDx:    types.StringValue(item.LibvirtSettings.EventIDx),
				Queues:      types.Int64Value(int64(item.LibvirtSettings.Queues)),
				RXQueueSize: types.Int64Value(int64(item.LibvirtSettings.RXQueueSize)),
				TXQueueSize: types.Int64Value(int64(item.LibvirtSettings.TXQueueSize)),
			},
			ListenSSH: types.BoolValue(item.ListenSSH),
			MAC:       types.StringValue(item.MAC),
			MTU:       types.Int64Value(int64(item.MTU)),
			Name:      types.StringValue(item.Name),
			NetID:     types.Int64Value(int64(item.NetID)),
			NetMask:   types.Int64Value(int64(item.NetMask)),
			NetType:   types.StringValue(item.NetType),
			NodeID:    types.Int64Value(int64(item.NodeID)),
			PCISlot:   types.Int64Value(item.PCISlot),
			QOS: &models.QOSModel{
				ERate:   types.Int64Value(int64(item.QOS.ERate)),
				GUID:    types.StringValue(item.QOS.GUID),
				InBurst: types.Int64Value(int64(item.QOS.InBurst)),
				InRate:  types.Int64Value(int64(item.QOS.InRate)),
			},
			Target: types.StringValue(item.Target),
			Type:   types.StringValue(item.Type),
		}
		temp.VNFs, diags = types.ListValueFrom(ctx, types.Int64Type, item.VNFs)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenInterfaces", diags))
		}
		res = append(res, temp)
	}

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

func flattenSnapSet(ctx context.Context, snapSet *compute.ListSnapSets) []models.ItemSnapSetModel {
	tflog.Info(ctx, "Start flattenSnapSet")

	var diags diag.Diagnostics

	res := make([]models.ItemSnapSetModel, 0, len(*snapSet))
	for _, item := range *snapSet {
		temp := models.ItemSnapSetModel{
			GUID:      types.StringValue(item.GUID),
			Label:     types.StringValue(item.Label),
			Timestamp: types.Int64Value(int64(item.Timestamp)),
		}
		temp.Disks, diags = types.ListValueFrom(ctx, types.Int64Type, item.Disks)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenSnapSet", diags))
		}
		res = append(res, temp)
	}

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

func flattenOSUsers(ctx context.Context, osUsers *compute.ListOSUser) []models.ItemOSUserModel {
	tflog.Info(ctx, "Start flattenOSUsers")

	res := make([]models.ItemOSUserModel, 0, len(*osUsers))
	for _, item := range *osUsers {
		temp := models.ItemOSUserModel{
			GUID:     types.StringValue(item.GUID),
			Login:    types.StringValue(item.Login),
			Password: types.StringValue(item.Password),
			PubKey:   types.StringValue(item.PubKey),
		}
		res = append(res, temp)
	}

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

func flattenPCI(ctx context.Context, pciList *compute.ListPCIDevices) []uint64 {
	tflog.Info(ctx, "Start flattenPCI")
	res := make([]uint64, 0, len(pciList.Data))

	for _, v := range pciList.Data {
		res = append(res, v.ID)
	}

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

func flattenComputeVGPU(ctx context.Context, vgpuList []compute.VGPUItem) []models.VGPUModel {
	tflog.Info(ctx, "Start flattenVGPU")

	res := make([]models.VGPUModel, 0, len(vgpuList))
	for _, item := range vgpuList {
		temp := models.VGPUModel{
			ID:             types.Int64Value(int64(item.ID)),
			GID:            types.Int64Value(int64(item.GID)),
			Type:           types.StringValue(item.Type),
			Mode:           types.StringValue(item.Mode),
			Status:         types.StringValue(item.Status),
			ProfileID:      types.Int64Value(int64(item.ProfileID)),
			RAM:            types.Int64Value(int64(item.RAM)),
			LastUpdateTime: types.Int64Value(int64(item.LastUpdateTime)),
			CreatedTime:    types.Int64Value(int64(item.CreatedTime)),
			DeletedTime:    types.Int64Value(int64(item.DeletedTime)),
			VMID:           types.Int64Value(int64(item.VMID)),
			PGPUID:         types.Int64Value(int64(item.PGPuid)),
			ReferenceID:    types.StringValue(item.ReferenceID),
			AccountID:      types.Int64Value(int64(item.AccountID)),
			RGID:           types.Int64Value(int64(item.RgID)),
			LastClaimedBy:  types.Int64Value(int64(item.LastClaimedBy)),
			PCISlot:        types.Int64Value(int64(item.PCISlot)),
			BusNumber:      types.Int64Value(int64(item.BusNumber)),
			GUID:           types.Int64Value(int64(item.GUID)),
		}
		res = append(res, temp)
	}

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