package flattens

import (
	"context"
	"encoding/json"
	"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/disks"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/client"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/disks/models"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudapi/disks/utilities"
)

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

	diags := diag.Diagnostics{}

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

	recordDisk, err := utilities.DiskCheckPresence(ctx, diskId, c)
	if err != nil {
		diags.AddError(fmt.Sprintf("Cannot get info about disk with ID %v", diskId), err.Error())
		return diags
	}

	tflog.Info(ctx, "flattens.DiskResource: before flatten", map[string]any{"disk_id": diskId, "recordDisk": recordDisk})

	diskAcl, _ := json.Marshal(recordDisk.ACL)
	*plan = models.ResourceDiskModel{
		// required fields
		AccountID: types.Int64Value(int64(recordDisk.AccountID)),
		DiskName:  types.StringValue(recordDisk.Name),
		SizeMax:   types.Int64Value(int64(recordDisk.SizeMax)),

		// optional fields
		Description: types.StringValue(recordDisk.Description),
		Pool:        types.StringValue(recordDisk.Pool),
		SEPID:       types.Int64Value(int64(recordDisk.SepID)),
		Detach:      plan.Detach,
		Permanently: plan.Permanently,
		Shareable:   plan.Shareable,
		Timeouts:    plan.Timeouts,

		// computed fields
		LastUpdated:     plan.LastUpdated,
		ID:              types.StringValue(strconv.Itoa(int(recordDisk.ID))),
		DiskID:          types.Int64Value(int64(recordDisk.ID)),
		AccountName:     types.StringValue(recordDisk.AccountName),
		ACL:             types.StringValue(string(diskAcl)),
		Computes:        flattenComputes(ctx, recordDisk.Computes),
		CreatedBy:       types.StringValue(recordDisk.CreatedBy),
		CreatedTime:     types.Int64Value(int64(recordDisk.CreatedTime)),
		DeletedBy:       types.StringValue(recordDisk.DeletedBy),
		DeletedTime:     types.Int64Value(int64(recordDisk.DeletedTime)),
		DestructionTime: types.Int64Value(int64(recordDisk.DestructionTime)),
		DeviceName:      types.StringValue(recordDisk.DeviceName),
		ImageID:         types.Int64Value(int64(recordDisk.ImageID)),
		MachineID:       types.Int64Value(int64(recordDisk.MachineID)),
		MachineName:     types.StringValue(recordDisk.MachineName),
		GID:             types.Int64Value(int64(recordDisk.GID)),
		Order:           types.Int64Value(int64(recordDisk.Order)),
		Params:          types.StringValue(recordDisk.Params),
		ParentID:        types.Int64Value(int64(recordDisk.ParentID)),
		PCISlot:         types.Int64Value(int64(recordDisk.PCISlot)),
		PurgeTime:       types.Int64Value(int64(recordDisk.PurgeTime)),
		ResID:           types.StringValue(recordDisk.ResID),
		ResName:         types.StringValue(recordDisk.ResName),
		Role:            types.StringValue(recordDisk.Role),
		SepType:         types.StringValue(recordDisk.SepType),
		SizeUsed:        types.Float64Value(recordDisk.SizeUsed),
		Snapshots:       flattenSnapshots(ctx, recordDisk.Snapshots),
		Status:          types.StringValue(recordDisk.Status),
		TechStatus:      types.StringValue(recordDisk.TechStatus),
		Type:            types.StringValue(recordDisk.Type),
		UpdatedBy:       types.StringValue(recordDisk.UpdatedBy),
		UpdatedTime:     types.Int64Value(int64(recordDisk.UpdatedTime)),
		VMID:            types.Int64Value(int64(recordDisk.VMID)),
	}

	plan.Images, diags = types.ListValueFrom(ctx, types.Int64Type, recordDisk.Images)
	if diags != nil {
		tflog.Error(ctx, fmt.Sprint("flattens.DiskResource: cannot flatten recordDisk.Images to plan.Images", diags))
	}
	plan.PresentTo, diags = types.MapValueFrom(ctx, types.Int64Type, recordDisk.PresentTo)
	if diags != nil {
		tflog.Error(ctx, fmt.Sprint("flattens.DiskResource: cannot flatten recordDisk.PresentTo to plan.PresentTo", diags))
	}

	if plan.Description.IsUnknown() {
		plan.Description = types.StringValue(recordDisk.Description)
	}
	if plan.Pool.IsUnknown() {
		plan.Pool = types.StringValue(recordDisk.Pool)
	}
	if plan.SEPID.IsUnknown() {
		plan.SEPID = types.Int64Value(int64(recordDisk.SepID))
	}
	if plan.Shareable.IsUnknown() {
		plan.Shareable = types.BoolValue(recordDisk.Shareable)
	}
	if plan.Type.IsUnknown() {
		plan.Type = types.StringValue(recordDisk.Type)
	}

	iotune := models.IOTuneModel{
		ReadBytesSec:     types.Int64Value(int64(recordDisk.IOTune.ReadBytesSec)),
		ReadBytesSecMax:  types.Int64Value(int64(recordDisk.IOTune.ReadBytesSecMax)),
		ReadIOPSSec:      types.Int64Value(int64(recordDisk.IOTune.ReadIOPSSec)),
		ReadIOPSSecMax:   types.Int64Value(int64(recordDisk.IOTune.ReadIOPSSecMax)),
		SizeIOPSSec:      types.Int64Value(int64(recordDisk.IOTune.SizeIOPSSec)),
		TotalBytesSec:    types.Int64Value(int64(recordDisk.IOTune.TotalBytesSec)),
		TotalBytesSecMax: types.Int64Value(int64(recordDisk.IOTune.TotalBytesSecMax)),
		TotalIOPSSec:     types.Int64Value(int64(recordDisk.IOTune.TotalIOPSSec)),
		TotalIOPSSecMax:  types.Int64Value(int64(recordDisk.IOTune.TotalIOPSSecMax)),
		WriteBytesSec:    types.Int64Value(int64(recordDisk.IOTune.WriteBytesSec)),
		WriteBytesSecMax: types.Int64Value(int64(recordDisk.IOTune.WriteBytesSecMax)),
		WriteIOPSSec:     types.Int64Value(int64(recordDisk.IOTune.WriteIOPSSec)),
		WriteIOPSSecMax:  types.Int64Value(int64(recordDisk.IOTune.WriteIOPSSecMax)),
	}
	obj, diags := types.ObjectValueFrom(ctx, models.ItemIOTune, iotune)
	if diags != nil {
		tflog.Error(ctx, fmt.Sprint("Error flattens.DiskResource iotune struct to obj", diags))
	}
	plan.IOTune = obj

	tflog.Info(ctx, "flattens.DiskResource: after flatten", map[string]any{"disk_id": plan.ID.ValueString()})

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

func flattenComputes(ctx context.Context, items map[string]string) types.List {
	tflog.Info(ctx, "Start flattenComputes")
	tempSlice := make([]types.Object, 0, len(items))
	for id, name := range items {
		temp := models.ItemComputeModel{
			ComputeId:   types.StringValue(id),
			ComputeName: types.StringValue(name),
		}
		obj, diags := types.ObjectValueFrom(ctx, models.ItemCompute, temp)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenComputes struct to obj", diags))
		}
		tempSlice = append(tempSlice, obj)
	}

	res, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: models.ItemCompute}, tempSlice)
	if diags != nil {
		tflog.Error(ctx, fmt.Sprint("Error flattenComputes", diags))
	}

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

func flattenSnapshots(ctx context.Context, snapshots disks.ListSnapshots) types.List {
	tflog.Info(ctx, "Start flattenSnapshots")
	tempSlice := make([]types.Object, 0, len(snapshots))
	for _, item := range snapshots {
		temp := models.ItemSnapshotModel{
			GUID:        types.StringValue(item.GUID),
			Label:       types.StringValue(item.Label),
			ResID:       types.StringValue(item.ResID),
			SnapSetGUID: types.StringValue(item.SnapSetGUID),
			SnapSetTime: types.Int64Value(int64(item.SnapSetTime)),
			TimeStamp:   types.Int64Value(int64(item.TimeStamp)),
		}
		obj, diags := types.ObjectValueFrom(ctx, models.ItemSnapshot, temp)
		if diags != nil {
			tflog.Error(ctx, fmt.Sprint("Error flattenSnapshots struct to obj", diags))
		}
		tempSlice = append(tempSlice, obj)
	}

	res, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: models.ItemSnapshot}, tempSlice)
	if diags != nil {
		tflog.Error(ctx, fmt.Sprint("Error flattenSnapshots", diags))
	}

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