package utilities

import (
	"context"
	"fmt"
	"strconv"

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

func CreateResourceFlipgroup(ctx context.Context, plan *models.ResourceFLIPGroupModel, c *client.Client) (uint64, diag.Diagnostics) {
	tflog.Info(ctx, fmt.Sprintf("Start create ResourceFlipgroup: flipgroup_name %s", plan.Name.ValueString()))

	diags := diag.Diagnostics{}

	createReq := flipgroup.CreateRequest{
		AccountID:  uint64(plan.AccountID.ValueInt64()),
		Name:       plan.Name.ValueString(),
		NetType:    plan.NetType.ValueString(),
		NetID:      uint64(plan.NetID.ValueInt64()),
		ClientType: plan.ClientType.ValueString(),
	}

	if !plan.IP.IsUnknown() { // IP is optional & computed
		createReq.IP = plan.IP.ValueString()
	}

	if !plan.Description.IsNull() { // Description is optional
		createReq.Description = plan.Description.ValueString()
	}

	tflog.Info(ctx, "CreateResourceFlipgroup: before call CloudAPI().FLIPGroup().Create", map[string]any{"req": createReq})

	resp, err := c.CloudAPI().FLIPGroup().Create(ctx, createReq)
	if err != nil {
		diags.AddError("CreateResourceFlipgroup: unable to create flipgroup", err.Error())
		return 0, diags
	}
	tflog.Info(ctx, "CreateResourceFlipgroup: flipgroup created", map[string]any{"flipgroup_id": resp.ID, "name": plan.Name.ValueString()})

	return resp.ID, nil
}

// AddClientsFlipgroup add computes in flipgroup for created resource.
// In case of failure returns warnings.
func AddClientsFlipgroup(ctx context.Context, flipgroupID uint64, plan *models.ResourceFLIPGroupModel, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	tflog.Info(ctx, "AddClientsFlipgroup: start add clients", map[string]any{"flipgroup_id": flipgroupID})
	clientIDs := make([]int, 0, len(plan.ClientIDs.Elements()))
	diagsItem := plan.ClientIDs.ElementsAs(ctx, &clientIDs, true)
	if diagsItem.HasError() {
		tflog.Error(ctx, fmt.Sprintf("AddClientsFlipgroup: cannot populate clientIDs with plan.ClientIDs list elements: %v", diagsItem))
		diags.AddWarning("AddClientsFlipgroup: Unable to read clientIDs for flipgroup", fmt.Sprintf("%v", diagsItem))
		return diags
	}

	for _, computeID := range clientIDs {
		addClientsReq := flipgroup.ComputeAddRequest{
			FLIPGroupID: flipgroupID,
			ComputeID:   uint64(computeID),
		}

		res, err := c.CloudAPI().FLIPGroup().ComputeAdd(ctx, addClientsReq)
		if err != nil {
			diags.AddWarning("AddClientsFlipgroup: Unable to add compute for flipgroup", err.Error())
		}
		tflog.Info(ctx, "AddClientsFlipgroup: response from CloudAPI().FLIPGroup().ComputeAdd", map[string]any{
			"flipgroup_id": flipgroupID,
			"response":     res})
	}

	return diags
}

// FlipgroupReadStatus loads flipgroup resource by id, gets it current status
// In case of failure returns errors.
func FlipgroupReadStatus(ctx context.Context, state *models.ResourceFLIPGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "FlipgroupReadStatus: Read status flipgroup with ID", map[string]any{"flipgroup_id": state.ID.ValueString()})

	diags := diag.Diagnostics{}

	flipgroupId, err := strconv.ParseUint(state.ID.ValueString(), 10, 64)
	if err != nil {
		diags.AddError("FlipgroupReadStatus: Cannot parse flipgroup ID from state", err.Error())
		return diags
	}

	recordFG, diags := FlipgroupResourceCheckPresence(ctx, flipgroupId, c)
	if err != nil {
		diags.AddError("FlipgroupReadStatus: Unable to Read/Update flipgroup before status check", err.Error())
		return diags
	}

	//check resource status

	switch recordFG.Status {
	case status.Modeled:
		diags.AddError(
			"FlipgroupReadStatus: flipgroup is in status Modeled",
			"please, contact support for more information",
		)
		return diags
	case status.Destroyed:
		diags.AddError(
			"FlipgroupReadStatus: flipgroup is in status Destroyed",
			fmt.Sprintf("the resource with flipgroup_id %d cannot be read or updated because it has been destroyed", flipgroupId),
		)
		return diags
	}

	return nil
}

func FlipgroupResourceCheckPresence(ctx context.Context, flipgroupId uint64, c *client.Client) (*flipgroup.RecordFLIPGroup, diag.Diagnostics) {
	tflog.Info(ctx, fmt.Sprintf("FlipgroupResourceCheckPresence: Get info about flipgroup with ID - %v", flipgroupId))

	diags := diag.Diagnostics{}

	recordFG, err := c.CloudAPI().FLIPGroup().Get(ctx, flipgroup.GetRequest{FLIPGroupID: flipgroupId})
	if err != nil {
		diags.AddError(fmt.Sprintf("Cannot get info about flipgroup with ID %v", flipgroupId), err.Error())
		return nil, diags
	}

	tflog.Info(ctx, "FlipgroupResourceCheckPresence: response from CloudAPI().FLIPGroup().Get", map[string]any{"flipgroup_id": flipgroupId, "response": recordFG})

	return recordFG, nil
}

func EditFlipgroup(ctx context.Context, flipgroupId uint64, plan, state *models.ResourceFLIPGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, fmt.Sprintf("EditFlipgroup: Start edit flipgroup with ID - %v", flipgroupId))

	diags := diag.Diagnostics{}

	editReq := flipgroup.EditRequest{
		FLIPGroupID: flipgroupId,
	}

	if !plan.Name.Equal(state.Name) {
		editReq.Name = plan.Name.ValueString()
	}

	if !plan.Description.IsNull() && !plan.Description.Equal(state.Description) {
		editReq.Description = plan.Description.ValueString()
	}

	_, err := c.CloudAPI().FLIPGroup().Edit(ctx, editReq)
	if err != nil {
		diags.AddError("EditFlipgroup: cannot edit flipgroup", err.Error())
		return diags
	}

	tflog.Info(ctx, fmt.Sprintf("EditFlipgroup: Finish edit flipgroup with ID - %v", flipgroupId))

	return nil
}

func UpdateClientIDsFlipgroup(ctx context.Context, flipgroupId uint64, plan, state *models.ResourceFLIPGroupModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, fmt.Sprintf("UpdateClientIDsFlipgroup: Start update flipgroup with ID - %v", flipgroupId))
	diags := diag.Diagnostics{}

	clientIDsPlan := make([]int, 0, len(plan.ClientIDs.Elements()))
	diags.Append(plan.ClientIDs.ElementsAs(ctx, &clientIDsPlan, true)...)
	if diags.HasError() {
		tflog.Error(ctx, fmt.Sprintf("UpdateClientIDsFlipgroup: cannot populate clientIDs with plan.ClientIDs list elements"))
		return diags
	}

	clientIDsState := make([]int, 0, len(state.ClientIDs.Elements()))
	diags.Append(state.ClientIDs.ElementsAs(ctx, &clientIDsState, true)...)
	if diags.HasError() {
		tflog.Error(ctx, fmt.Sprintf("UpdateClientIDsFlipgroup: cannot populate clientIDs with state.ClientIDs list elements"))
		return diags
	}

	deleteIDs := difference(clientIDsState, clientIDsPlan)
	addIDs := difference(clientIDsPlan, clientIDsState)

	for _, id := range deleteIDs {
		tflog.Info(ctx, fmt.Sprintf("UpdateClientIDsFlipgroup: Start remove client with ID - %v", id))
		req := flipgroup.ComputeRemoveRequest{
			FLIPGroupID: flipgroupId,
			ComputeID:   id,
		}
		res, err := c.CloudAPI().FLIPGroup().ComputeRemove(ctx, req)
		tflog.Info(ctx, "UpdateClientIDsFlipgroup: response from CloudAPI().FLIPGroup().ComputeRemove", map[string]any{"flipgroup_id": plan.ID.ValueString(), "response": res})
		if err != nil {
			diags.AddError("UpdateClientIDsFlipgroup: can not remove client for flipgroup", err.Error())
		}
	}

	for _, id := range addIDs {
		tflog.Info(ctx, fmt.Sprintf("UpdateClientIDsFlipgroup: Start add client with ID - %v", id))
		req := flipgroup.ComputeAddRequest{
			FLIPGroupID: flipgroupId,
			ComputeID:   id,
		}
		res, err := c.CloudAPI().FLIPGroup().ComputeAdd(ctx, req)
		tflog.Info(ctx, "UpdateClientIDsFlipgroup: response from CloudAPI().FLIPGroup().ComputeAdd", map[string]any{"flipgroup_id": plan.ID.ValueString(), "response": res})
		if err != nil {
			diags.AddError("UpdateClientIDsFlipgroup: can not add client for flipgroup", err.Error())
		}
	}

	tflog.Info(ctx, fmt.Sprintf("UpdateClientIDsFlipgroup: Finish update flipgroup with ID - %v", flipgroupId))
	return diags
}

func difference(set, check []int) []uint64 {
	mapCheck := make(map[int]struct{})
	for _, id := range check {
		mapCheck[id] = struct{}{}
	}
	var diff []uint64
	for _, id := range set {
		if _, ok := mapCheck[id]; !ok {
			diff = append(diff, uint64(id))
		}
	}
	return diff
}
