package action

import (
	"context"
	"strconv"

	"github.com/tidbcloud/fastflow/pkg/entity/run"
	clusterdedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	clusterreop "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	regionrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const (
	CreateLoadBalancer = "create_load_balancer"
)

type CreateLoadBalancerAction struct {
	clusterRepo     clusterreop.Repo
	tidbGroupRepo   tidbgroup.Repo
	regionRepo      regionrepo.Repo
	regionServerCli regionserver.Client
}

func NewCreateLoadBalancerAction(
	clusterRepo clusterreop.Repo,
	tidbGroupRepo tidbgroup.Repo,
	regionRepo regionrepo.Repo,
	regionServerCli regionserver.Client,
) *CreateLoadBalancerAction {
	return &CreateLoadBalancerAction{
		clusterRepo:     clusterRepo,
		tidbGroupRepo:   tidbGroupRepo,
		regionRepo:      regionRepo,
		regionServerCli: regionServerCli,
	}
}

func (c *CreateLoadBalancerAction) Name() string {
	return CreateLoadBalancer
}

func (c *CreateLoadBalancerAction) ParameterNew() any {
	return &common.TidbGroupParameter{}
}

func (c *CreateLoadBalancerAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)

	clusterId, err := common.GetClusterIdFromTidbGroupParams(params)
	if err != nil {
		tilog.Error(cctx, "failed to get cluster id", zap.Error(err))
		return err
	}
	tidbGroupId, err := common.GetTidbGroupIdFromTidbGroupParams(params)
	if err != nil {
		tilog.Error(cctx, "failed to get work pool id", zap.Error(err))
		return err
	}

	tilog.Info(
		cctx,
		"start to create load balancer",
		zap.Uint64("cluster_id", clusterId),
		zap.Uint64("tidb_group_id", tidbGroupId),
	)
	defer func() {
		tilog.Info(
			cctx,
			"end to create load balancer",
			zap.Uint64("cluster_id", clusterId),
			zap.Uint64("tidb_group_id", tidbGroupId),
		)
		span.End()
	}()

	cluster, err := c.clusterRepo.Get(cctx, clusterId, true)
	if err != nil {
		tilog.Error(
			cctx,
			"get cluster failed",
			zap.Uint64("cluster_id", clusterId),
			zap.Uint64("tidb_group_id", tidbGroupId),
			zap.Error(err),
		)
		return err
	}
	if cluster.GetDeleteTime() != nil {
		tilog.Warn(
			cctx,
			"cancel the creation of the load balancer because the cluster has been deleted",
			zap.Uint64("cluster_id", clusterId),
			zap.Uint64("tidb_group_id", tidbGroupId),
		)
		return nil
	}

	tidbGroup, err := c.tidbGroupRepo.Load(cctx, cluster.ClusterId, tidbGroupId)
	if err != nil {
		tilog.Error(
			cctx,
			"load work pool failed",
			zap.Uint64("cluster_id", clusterId),
			zap.Uint64("tidb_group_id", tidbGroupId),
			zap.Error(err),
		)
		return err
	}

	if err := c.create(cctx, cluster, tidbGroup); err != nil {
		tilog.Error(
			cctx,
			"create load balancer failed",
			zap.Uint64("cluster_id", clusterId),
			zap.Uint64("tidb_group_id", tidbGroupId),
			zap.Error(err),
		)
		return err
	}

	return nil
}

func (c *CreateLoadBalancerAction) create(
	ctx context.Context,
	cluster *entity.Cluster,
	tidbGroup *entity.TidbNodeGroup,
) error {
	region, err := c.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.GetOrganizationId())
	if err != nil {
		tilog.Error(ctx, "find region by id failed", zap.String("region_id", cluster.RegionId.String()), zap.Error(err))
		return err
	}

	getTidbGroupRequest := &clusterdedicatedv1.GetTidbGroupRequest{
		ClusterId:   strconv.FormatUint(cluster.ClusterId, 10),
		TidbGroupId: strconv.FormatUint(tidbGroup.Id, 10),
	}
	rtidbGroup, err := c.regionServerCli.GetTidbGroup(ctx, region, getTidbGroupRequest)
	if err != nil {
		tilog.Error(
			ctx,
			"get tidb group failed",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Uint64("tidb_group_id", tidbGroup.Id),
			zap.Error(err),
		)
		return err
	}

	rtidbGroup.EnablePublicLb = true
	rtidbGroup.EnablePrivateLb = true
	rtidbGroup.PublicLb = &clusterdedicatedv1.LoadBalancer{
		Host: "", // leave it empty
		Port: int32(cluster.Port),
		IpAllowList: []*clusterdedicatedv1.LoadBalancer_IpAddress{
			{
				CidrBlock: DefaultInternalCIDR,
			},
		},
		CloudResourceId: "", // leave it empty
	}
	rtidbGroup.PrivateLb = &clusterdedicatedv1.LoadBalancer{
		Host:            "", // leave it empty
		Port:            int32(cluster.Port),
		IpAllowList:     nil, // leave it empty
		CloudResourceId: "",  // leave it empty
	}

	updateTidbGroupRequest := &clusterdedicatedv1.UpdateTidbGroupRequest{
		ClusterId:   strconv.FormatUint(cluster.ClusterId, 10),
		TidbGroupId: strconv.FormatUint(tidbGroup.Id, 10),
		TidbGroup:   rtidbGroup,
	}

	if _, err := c.regionServerCli.UpdateTidbGroup(ctx, region, updateTidbGroupRequest); err != nil {
		tilog.Error(
			ctx,
			"update tidb group failed",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Uint64("tidb_group_id", tidbGroup.Id),
			zap.Error(err),
		)
		return err
	}

	return nil
}
