package utilities

import (
	"context"
	"fmt"
	"strconv"

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

func BServiceGroupResourceCheckPresence(ctx context.Context, serviceID uint64, compGroupID uint64, c *client.Client) (*bservice.RecordGroup, error) {
	tflog.Info(ctx, fmt.Sprintf("BServiceGroupResourceCheckPresence: Get info about bserivce group with  ID - %v", compGroupID))

	req := bservice.GroupGetRequest{ServiceID: serviceID, CompGroupID: compGroupID}
	record, err := c.CloudAPI().BService().GroupGet(ctx, req)

	if err != nil {
		return nil, fmt.Errorf("cannot get info about bservice with error: %w", err)
	}

	return record, err
}

func BServiceGroupResourceCreate(ctx context.Context, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start BServiceGroupResourceCreate", map[string]any{"service": plan.ServiceID.ValueInt64()})

	diags := diag.Diagnostics{}

	req := bservice.GroupAddRequest{
		ServiceID: uint64(plan.ServiceID.ValueInt64()),
		Name:      plan.Name.ValueString(),
		Count:     uint64(plan.CompCount.ValueInt64()),
		CPU:       uint64(plan.CPU.ValueInt64()),
		RAM:       uint64(plan.RAM.ValueInt64()),
		Disk:      uint64(plan.Disk.ValueInt64()),
		ImageID:   uint64(plan.ImageID.ValueInt64()),
		Driver:    plan.Driver.ValueString(),
	}

	if !plan.SEPID.IsNull() {
		req.SEPID = uint64(plan.SEPID.ValueInt64())
	}

	if plan.Chipset.IsNull() {
		req.Chipset = "i440fx"
	} else {
		req.Chipset = plan.Chipset.ValueString()
	}

	if !plan.SepPool.IsNull() {
		req.SEPPool = plan.SepPool.ValueString()
	}

	if !plan.Role.IsNull() {
		req.Role = plan.Role.ValueString()
	}

	if !plan.TimeoutStart.IsNull() {
		req.TimeoutStart = uint64(plan.TimeoutStart.ValueInt64())
	}

	if !plan.VINSes.IsNull() {
		result := make([]uint64, 0, len(plan.VINSes.Elements()))

		diags.Append(plan.VINSes.ElementsAs(ctx, &result, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "BServiceGroupResourceCreate: cannot populate result with plan.VINSes object element")
			return diags
		}

		req.VINSes = result
	}

	if !plan.ExtNets.IsNull() {
		result := make([]uint64, 0, len(plan.ExtNets.Elements()))

		diags.Append(plan.ExtNets.ElementsAs(ctx, &result, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "BServiceGroupResourceCreate: cannot populate result with plan.ExtNets object element")
			return diags
		}

		req.ExtNets = result
	}

	// Make request and get response
	compgroupId, err := c.CloudAPI().BService().GroupAdd(ctx, req)
	if err != nil {
		tflog.Error(ctx, "Error response for group add", map[string]any{"error": err.Error()})
		diags.AddError("Unable to add group", err.Error())
		return diags
	}
	plan.ID = types.StringValue(strconv.Itoa(int(compgroupId)))
	plan.SID = types.StringValue(strconv.Itoa(int(plan.ServiceID.ValueInt64())))

	if plan.Start.ValueBool() {
		tflog.Info(ctx, "BServiceGroupResourceCreate: start group", map[string]any{"service_id": plan.ServiceID.ValueInt64()})
		_, err := c.CloudAPI().BService().GroupStart(ctx, bservice.GroupStartRequest{
			ServiceID:   uint64(plan.ServiceID.ValueInt64()),
			CompGroupID: compgroupId,
		})
		if err != nil {
			diags.AddWarning(
				"resourceBasicServiceCreate: cannot start BService",
				err.Error(),
			)
			return diags
		}

	}
	tflog.Info(ctx, "End BServiceGroupResourceCreate", map[string]any{"service_id": compgroupId})
	return diags
}

func BServiceGroupResize(ctx context.Context, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "BServiceGroupResize: start.", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": plan.ID.ValueString()})

	diags := diag.Diagnostics{}

	compGroupID, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("BServiceGroupResize: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	req := bservice.GroupResizeRequest{
		ServiceID:   uint64(plan.ServiceID.ValueInt64()),
		CompGroupID: compGroupID,
		Count:       plan.CompCount.ValueInt64(),
		Mode:        plan.Mode.ValueString(),
	}

	if plan.Chipset.IsNull() {
		req.Chipset = "i440fx"
	} else {
		req.Chipset = plan.Chipset.ValueString()
	}

	tflog.Info(ctx, "BServiceGroupResize: before calling CloudAPI().BService().GroupResize", map[string]any{"req": req})
	_, err = c.CloudAPI().BService().GroupResize(ctx, req)
	if err != nil {
		diags.AddError(fmt.Sprintf("Cannot resize group with ServiceID - %d,CompgroupID - %d ", plan.ServiceID.ValueInt64(), compGroupID), err.Error())
		return diags
	}

	tflog.Info(ctx, "BServiceGroupResize: resize group successfully", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": compGroupID})
	return diags
}

func BServiceGroupUpdate(ctx context.Context, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "BServiceGroupUpdate: start.", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": plan.ID.ValueString()})

	diags := diag.Diagnostics{}

	compGroupID, err := strconv.ParseUint(plan.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("BServiceGroupResize: cannot parsed ID compute from plan", err.Error())
		return diags
	}

	req := bservice.GroupUpdateRequest{
		ServiceID:   uint64(plan.ServiceID.ValueInt64()),
		CompGroupID: compGroupID,
		Name:        plan.Name.ValueString(),
		Role:        plan.Role.ValueString(),
		CPU:         uint64(plan.CPU.ValueInt64()),
		RAM:         uint64(plan.RAM.ValueInt64()),
		Disk:        uint64(plan.Disk.ValueInt64()),
		Force:       plan.ForceUpdate.ValueBool(),
	}

	tflog.Info(ctx, "BServiceGroupResize: before calling CloudAPI().BService().GroupUpdate", map[string]any{"req": req})
	_, err = c.CloudAPI().BService().GroupUpdate(ctx, req)
	if err != nil {
		diags.AddError(fmt.Sprintf("Cannot update group with ServiceID - %d,CompgroupID - %d ", plan.ServiceID.ValueInt64(), compGroupID), err.Error())
		return diags
	}

	tflog.Info(ctx, "BServiceGroupUpdate: update group successfully", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": compGroupID})
	return diags
}

func BServiceGroupReadStatus(ctx context.Context, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Read status BServiceGroupReadStatus with ID", map[string]any{"service_id": plan.SID.ValueString()})

	diags := diag.Diagnostics{}

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

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

	resource, err := BServiceGroupResourceCheckPresence(ctx, serviceID, compGroupID, c)
	if err != nil {
		diags.AddError("Cannot get info about bservice group ", err.Error())
		return diags
	}

	switch resource.Status {
	case status.Modeled:
		diags.AddError("Error:", fmt.Sprintf("The bservice group is in status: %s, please, contact support for more information", resource.Status))
		return diags
	case status.Destroying:
		diags.AddError("Error:", fmt.Sprintf("The bservice group is in progress with status: %s", resource.Status))
		return diags
	case status.Destroyed:
		diags.AddError("Error:", "The resource cannot be updated because it has been destroyed")
		return diags
	}

	tflog.Info(ctx, "Read status bservice group successfully", map[string]any{"compgroup_id": plan.ID.ValueString()})
	return diags
}

func BServiceGroupStartStop(ctx context.Context, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start/Stop bservice group", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": plan.ID.ValueString()})

	diags := diag.Diagnostics{}

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

	if plan.Start.ValueBool() {
		req := bservice.GroupStartRequest{
			ServiceID:   uint64(plan.ServiceID.ValueInt64()),
			CompGroupID: compGroupID,
		}
		tflog.Info(ctx, "BServiceGroupStartStop: before calling CloudAPI().BService().GroupStart", map[string]any{"req": req})
		_, err := c.CloudAPI().BService().GroupStart(ctx, req)
		if err != nil {
			diags.AddError(fmt.Sprintf("Cannot start bservice group with ServiceID - %d,CompgroupID - %d ", plan.ServiceID.ValueInt64(), compGroupID), err.Error())
			return diags
		}
	} else {
		req := bservice.GroupStopRequest{
			ServiceID:   uint64(plan.ServiceID.ValueInt64()),
			CompGroupID: compGroupID,
			Force:       plan.ForceStop.ValueBool(),
		}
		tflog.Info(ctx, "BServiceGroupStartStop: before calling CloudAPI().BService().GroupStop", map[string]any{"req": req})
		_, err := c.CloudAPI().BService().GroupStop(ctx, req)
		if err != nil {
			diags.AddError(fmt.Sprintf("Cannot stop bservice group with ServiceID - %d,CompgroupID - %d ", plan.ServiceID.ValueInt64(), compGroupID), err.Error())
			return diags
		}
	}

	tflog.Info(ctx, "Start/Stop bservice group successfully", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": compGroupID})
	return diags
}

func BServiceGroupExtNet(ctx context.Context, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "update ExtNets", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": plan.ID.ValueString()})

	diags := diag.Diagnostics{}

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

	extnetList := make([]uint64, 0, len(plan.ExtNets.Elements()))

	diags.Append(plan.ExtNets.ElementsAs(ctx, &extnetList, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "BServiceGroupExtNet: cannot populate result with plan.ExtNets object element")
		return diags
	}

	req := bservice.GroupUpdateExtNetRequest{
		ServiceID:   uint64(plan.ServiceID.ValueInt64()),
		CompGroupID: compGroupID,
		ExtNets:     extnetList,
	}

	tflog.Info(ctx, "BServiceGroupExtNet: before calling CloudAPI().BService().GroupUpdateExtNet", map[string]any{"req": req})
	_, err = c.CloudAPI().BService().GroupUpdateExtNet(ctx, req)
	if err != nil {
		diags.AddError(fmt.Sprintf("Cannot update bservice group extnets with ServiceID - %d,CompgroupID - %d ", plan.ServiceID.ValueInt64(), compGroupID), err.Error())
		return diags
	}

	tflog.Info(ctx, "BServiceGroupExtNet: update successfully", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": compGroupID})
	return diags
}

func BServiceGroupVinses(ctx context.Context, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "update Vinses", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": plan.ID.ValueString()})

	diags := diag.Diagnostics{}

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

	vinsesList := make([]uint64, 0, len(plan.VINSes.Elements()))

	diags.Append(plan.VINSes.ElementsAs(ctx, &vinsesList, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "BServiceGroupVinses: cannot populate result with plan.VINSes object element")
		return diags
	}

	req := bservice.GroupUpdateVINSRequest{
		ServiceID:   uint64(plan.ServiceID.ValueInt64()),
		CompGroupID: compGroupID,
		VINSes:      vinsesList,
	}

	tflog.Info(ctx, "BServiceGroupVinses: before calling CloudAPI().BService().GroupUpdateVINS", map[string]any{"req": req})
	_, err = c.CloudAPI().BService().GroupUpdateVINS(ctx, req)
	if err != nil {
		diags.AddError(fmt.Sprintf("Cannot update bservice group vinses with ServiceID - %d,CompgroupID - %d ", plan.ServiceID.ValueInt64(), compGroupID), err.Error())
		return diags
	}

	tflog.Info(ctx, "BServiceGroupVinses: update successfully", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": compGroupID})
	return diags
}

func BServiceGroupParents(ctx context.Context, newParents basetypes.ListValue, oldParents basetypes.ListValue, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}
	tflog.Info(ctx, "Start BServiceGroupParents", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": plan.ID.ValueString()})

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

	deletedParents := make([]uint64, 0)
	addedParents := make([]uint64, 0)

	oldParentsList := make([]uint64, 0, len(oldParents.Elements()))
	newParentsList := make([]uint64, 0, len(newParents.Elements()))

	diags.Append(oldParents.ElementsAs(ctx, &oldParentsList, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "BServiceGroupParents: cannot populate uint64 with plan.Parents object element")
		return diags
	}

	diags.Append(newParents.ElementsAs(ctx, &newParentsList, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "BServiceGroupParents: cannot populate uint64 with plan.Parents object element")
		return diags
	}

	for _, el := range oldParentsList {
		if !isContainsParent(newParentsList, el) {
			deletedParents = append(deletedParents, el)
		}
	}

	for _, el := range newParentsList {
		if !isContainsParent(oldParentsList, el) {
			addedParents = append(addedParents, el)
		}
	}

	if len(deletedParents) > 0 {
		for _, parent := range deletedParents {

			req := bservice.GroupParentRemoveRequest{
				ServiceID:   uint64(plan.ServiceID.ValueInt64()),
				CompGroupID: compGroupID,
				ParentID:    parent,
			}

			_, err := c.CloudAPI().BService().GroupParentRemove(ctx, req)
			if err != nil {
				tflog.Error(ctx, "BServiceGroupParents: Failed to remove parent")
				return diags
			}
		}
		tflog.Info(ctx, "Deleted parents", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": compGroupID})
	}

	if len(addedParents) > 0 {
		for _, parent := range addedParents {

			req := bservice.GroupParentAddRequest{
				ServiceID:   uint64(plan.ServiceID.ValueInt64()),
				CompGroupID: compGroupID,
				ParentID:    parent,
			}
			tflog.Info(ctx, "BServiceGroupParents: before calling CloudAPI().BService().GroupParentAdd", map[string]any{"req": req})
			_, err := c.CloudAPI().BService().GroupParentAdd(ctx, req)
			if err != nil {
				tflog.Error(ctx, "BServiceGroupParents: Failed to add parent")
				return diags
			}
		}
		tflog.Info(ctx, "Added parents", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": compGroupID})
	}

	return diags
}

func BServiceGroupRemoveComputes(ctx context.Context, plan *models.ResourceRecordGroupModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}
	tflog.Info(ctx, "Start BServiceGroupRemoveComputes", map[string]any{"service_id": plan.ServiceID.ValueInt64(), "compgroup_id": plan.ID.ValueString()})

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

	rcs := plan.RemoveComputes

	rcsList := make([]uint64, 0, len(rcs.Elements()))

	diags.Append(rcs.ElementsAs(ctx, &rcsList, true)...)
	if diags.HasError() {
		tflog.Error(ctx, "BServiceGroupRemoveComputes: cannot populate uint64 with plan.RemoveComputes object element")
		return diags
	}
	if len(rcsList) > 0 {
		for _, rc := range rcsList {
			req := bservice.GroupComputeRemoveRequest{
				ServiceID:   uint64(plan.ServiceID.ValueInt64()),
				CompGroupID: compGroupID,
				ComputeID:   rc,
			}
			tflog.Info(ctx, "BServiceGroupParents: before calling CloudAPI().BService().GroupComputeRemove", map[string]any{"req": req})
			_, err := c.CloudAPI().BService().GroupComputeRemove(ctx, req)
			if err != nil {
				tflog.Error(ctx, "BServiceGroupRemoveComputes: Failed to remove compute")
				return diags
			}
		}
	}
	return diags
}

func isContainsParent(els []uint64, el uint64) bool {
	for _, elOld := range els {
		if elOld == el {
			return true
		}
	}
	return false
}
