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-log/tflog"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vfpool"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/client"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/ic"
	"repository.basistech.ru/BASIS/terraform-provider-dynamix/internal/service/cloudbroker/vfpool/models"
)

func ResourceVFPoolCheckPresence(ctx context.Context, vfPoolID uint64, c *client.Client) (*vfpool.RecordVFPool,
	error) {
	req := vfpool.GetRequest{VFPoolID: vfPoolID}

	tflog.Info(ctx, "ResourceVFPoolCheckPresence: before call CloudBroker().VFPool().Get", map[string]any{"req": req})
	vfPool, err := c.CloudBroker().VFPool().Get(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("VFPoolCheckPresence: cannot get info about vfpool")
	}

	tflog.Info(ctx, "ResourceVFPoolCheckPresence: response from CloudBroker().VFPool().Get", map[string]any{"response": vfPool})

	return vfPool, err
}

func VFpoolResourceCreate(ctx context.Context, plan *models.ResourceItemVFPoolModel, c *client.Client) (*uint64, diag.Diagnostics) {
	tflog.Info(ctx, "Start VFpoolResourceCreate", map[string]any{"name": plan.Name.ValueString()})

	diags := diag.Diagnostics{}

	req := vfpool.CreateRequest{
		Name: plan.Name.ValueString(),
	}

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

	if !plan.AccountAccess.IsNull() {

		accountAccessList := make([]uint64, len(plan.AccountAccess.Elements()))

		diags.Append(plan.AccountAccess.ElementsAs(ctx, &accountAccessList, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "VFpoolResourceCreate: cannot populate VFpoolResourceCreate with plan.AccountAccess object element")
			return nil, diags
		}

		req.AccountAccess = accountAccessList
	}

	if !plan.RGAccess.IsNull() {

		RGAccessList := make([]uint64, len(plan.RGAccess.Elements()))

		diags.Append(plan.RGAccess.ElementsAs(ctx, &RGAccessList, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "VFpoolResourceCreate: cannot populate VFpoolResourceCreate with plan.RGAccess object element")
			return nil, diags
		}
		req.RGAccess = RGAccessList
	}

	diags.Append(checkParamsExistence(ctx, req.AccountAccess, req.RGAccess, c)...)
	if diags.HasError() {
		tflog.Error(ctx, "VFpoolResourceCreate: RGAccess or AccountAccess does not exist")
		return nil, diags
	}

	if !plan.Config.IsNull() {
		configList := make([]models.ResourceItemVFPoolConfigModel, 0, len(plan.Config.Elements()))
		diags.Append(plan.Config.ElementsAs(ctx, &configList, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "VFpoolResourceCreate: cannot populate VFpoolResourceCreate with v.Config object element")
			return nil, diags
		}

		config := make([]vfpool.Config, 0, len(configList))

		for _, v := range configList {
			vfIDs := make([]uint64, 0, len(v.VFIDs.Elements()))
			diags.Append(v.VFIDs.ElementsAs(ctx, &vfIDs, true)...)
			if diags.HasError() {
				tflog.Error(ctx, "VFpoolResourceCreate: cannot populate UpdateVFpool with vfIDs object element")
				return nil, diags
			}
			item := vfpool.Config{
				NodeID:  uint64(v.NodeID.ValueInt64()),
				NicName: v.NicName.ValueString(),
				VFIDs:   vfIDs}

			config = append(config, item)
		}
		req.Config = config

	}

	vfPoolID, err := c.CloudBroker().VFPool().Create(ctx, req)
	if err != nil {
		tflog.Error(ctx, "Error response for create VFpool", map[string]any{"error": err.Error()})
		diags.AddError("Unable to Create VFpool", err.Error())
		return nil, diags
	}

	plan.ID = types.StringValue(strconv.Itoa(int(vfPoolID)))

	if !plan.Enable.IsNull() {
		EnableDisableVFpool(ctx, plan, c)
	}

	tflog.Info(ctx, "End VFpoolResourceCreate", map[string]any{"vfpool_id": vfPoolID})
	return &vfPoolID, diags
}

// EnableDisableVFpool performs VFpool Enable/Disable request.
// Returns error in case of failures.
func EnableDisableVFpool(ctx context.Context, plan *models.ResourceItemVFPoolModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start EnableDisableVFpool", map[string]any{"vfpool_id": plan.ID.ValueString()})

	diags := diag.Diagnostics{}

	ID, err := strconv.Atoi(plan.ID.ValueString())
	if err != nil {
		diags.AddError("EnableDisableVFpool: Cannot parse ID from state", err.Error())
		return diags
	}

	if plan.Enable.ValueBool() {
		tflog.Info(ctx, "EnableDisableVFpool: before calling CloudBroker().VFPool().Enable", map[string]any{"vfpool_id": ID})
		res, err := c.CloudBroker().VFPool().Enable(ctx, vfpool.EnableRequest{VFPoolID: uint64(ID)})
		if err != nil {
			diags.AddError(
				"EnableDisableVFpool: cannot enable VFpool",
				err.Error(),
			)
			return diags
		}
		tflog.Info(ctx, "EnableDisableVFpool: response from CloudBroker().VFPool().Enable", map[string]any{"vfpool_id": ID, "response": res})
		return nil
	} else {
		tflog.Info(ctx, "EnableDisableVFpool: before calling CloudBroker().VFPool().Disable", map[string]any{"vfpool_id": ID})
		res, err := c.CloudBroker().VFPool().Disable(ctx, vfpool.DisableRequest{VFPoolID: uint64(ID)})
		if err != nil {
			diags.AddError(
				"EnableDisableVFpool: cannot disable VFPool",
				err.Error(),
			)
			return diags
		}
		tflog.Info(ctx, "EnableDisableVFpool: response from CloudBroker().VFPool().Disable", map[string]any{"vfpool_id": ID, "response": res})
	}

	return nil
}

func UpdateVFpool(ctx context.Context, state *models.ResourceItemVFPoolModel, plan *models.ResourceItemVFPoolModel, c *client.Client) diag.Diagnostics {
	tflog.Info(ctx, "Start UpdateVFpool", map[string]any{"vfpool_id": plan.ID.ValueString()})

	diags := diag.Diagnostics{}

	ID, err := strconv.Atoi(plan.ID.ValueString())
	if err != nil {
		diags.AddError("UpdateVFpool: Cannot parse ID from state", err.Error())
		return diags
	}

	req := vfpool.UpdateRequest{
		VFPoolID: uint64(ID),
	}

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

	if !plan.Description.Equal(state.Description) {
		req.Description = plan.Description.ValueString()
	}

	if !plan.Config.Equal(state.Config) && !plan.Config.IsNull() {
		configList := make([]models.ResourceItemVFPoolConfigModel, 0, len(plan.Config.Elements()))
		diags.Append(plan.Config.ElementsAs(ctx, &configList, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "UpdateVFpool: cannot populate UpdateVFpool with plan.Config object element")
			return diags
		}

		config := make([]vfpool.Config, 0, len(configList))

		for _, v := range configList {
			vfIDs := make([]uint64, 0, len(v.VFIDs.Elements()))
			diags.Append(v.VFIDs.ElementsAs(ctx, &vfIDs, true)...)
			if diags.HasError() {
				tflog.Error(ctx, "UpdateVFpool: cannot populate UpdateVFpool with vfIDs object element")
				return diags
			}
			item := vfpool.Config{
				NodeID:  uint64(v.NodeID.ValueInt64()),
				NicName: v.NicName.ValueString(),
				VFIDs:   vfIDs}

			config = append(config, item)
		}
		req.Config = config
	}

	if !plan.AccountAccess.Equal(state.AccountAccess) && !plan.AccountAccess.IsNull() {
		aaList := make([]uint64, 0, len(plan.AccountAccess.Elements()))
		diags.Append(plan.AccountAccess.ElementsAs(ctx, &aaList, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "UpdateVFpool: cannot populate UpdateVFpool with plan.AccountAccess object element")
			return diags
		}

		req.AccountAccess = aaList
	}

	if !plan.RGAccess.Equal(state.RGAccess) && !plan.RGAccess.IsNull() {
		rgAccessList := make([]uint64, 0, len(plan.RGAccess.Elements()))
		diags.Append(plan.RGAccess.ElementsAs(ctx, &rgAccessList, true)...)
		if diags.HasError() {
			tflog.Error(ctx, "UpdateVFpool: cannot populate UpdateVFpool with plan.RGAccess object element")
			return diags
		}

		req.RGAccess = rgAccessList
	}

	if state.Status.ValueString() == "ENABLED" || state.Status.ValueString() == "CREATED" {
		reqDisable := vfpool.DisableRequest{
			VFPoolID: uint64(ID),
		}
		tflog.Info(ctx, fmt.Sprintf("UpdateVFpool: need to disable vfPool with ID: %d, after update", ID))
		_, err = c.CloudBroker().VFPool().Disable(ctx, reqDisable)
		if err != nil {
			diags.AddWarning(
				"UpdateVFpool: cannot disable VFPool",
				err.Error(),
			)
			return diags
		}
		tflog.Info(ctx, "utilityVFPoolUpdate: disable VFPool with complete")
	}

	_, err = c.CloudBroker().VFPool().Update(ctx, req)
	if err != nil {
		diags.AddError("EnableDisableVFpool: Cannot update", err.Error())
		return diags
	}
	tflog.Info(ctx, fmt.Sprintf("utilityVFPoolUpdate: update vfPool with ID: %d, complete with params=%v", ID, req))

	if plan.Enable.ValueBool() {
		reqEnable := vfpool.EnableRequest{
			VFPoolID: uint64(ID),
		}
		tflog.Info(ctx, fmt.Sprintf("utilityVFPoolUpdate: start to enable vfPool with ID: %d, after update", ID))
		_, err = c.CloudBroker().VFPool().Enable(ctx, reqEnable)
		if err != nil {
			diags.AddWarning(
				"UpdateVFpool: cannot enable VFPool",
				err.Error(),
			)
			return diags
		}
		tflog.Info(ctx, fmt.Sprintf("utilityVFPoolUpdate: enable vfPool with ID: %d, complete", ID))
	} else {
		diags.AddWarning(
			"UpdateVFpool: vfPool is not enabled",
			"the vfPool is not enabled after update, you must provide configuration for this resource, after enabling it",
		)
		return diags
	}

	return nil
}

func checkParamsExistence(ctx context.Context, accountIDs, rgIDs []uint64, c *client.Client) diag.Diagnostics {
	diags := diag.Diagnostics{}

	if err := ic.ExistAccounts(ctx, accountIDs, c); err != nil {
		diags.AddError("Error check input values", err.Error())
	}

	if err := ic.ExistRGs(ctx, rgIDs, c); err != nil {
		diags.AddError("Error check input values", err.Error())
	}

	return diags
}
