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"
	endpointrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/endpoint"
	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/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const (
	CreateTidbGroup = "create_tidb_group"

	// DefaultInternalCIDR is the default internal CIDR for public endpoint, avoid load balancer to be accessed from outside by default.
	DefaultInternalCIDR string = "10.0.0.0/8"
)

type CreateTidbGroupAction struct {
	clusterRepo       clusterreop.Repo
	tidbGroupRepo     tidbgroup.Repo
	regionRepo        regionrepo.Repo
	trafficFilterRepo endpointrepo.TrafficFilterRepo
	regionServerCli   regionserver.Client
}

func NewCreateTidbGroupAction(
	clusterRepo clusterreop.Repo,
	tidbGroupRepo tidbgroup.Repo,
	regionRepo regionrepo.Repo,
	trafficFilterRepo endpointrepo.TrafficFilterRepo,
	regionServerCli regionserver.Client,
) *CreateTidbGroupAction {
	return &CreateTidbGroupAction{
		clusterRepo:       clusterRepo,
		tidbGroupRepo:     tidbGroupRepo,
		regionRepo:        regionRepo,
		trafficFilterRepo: trafficFilterRepo,
		regionServerCli:   regionServerCli,
	}
}

func (c *CreateTidbGroupAction) Name() string {
	return CreateTidbGroup
}

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

func (c *CreateTidbGroupAction) 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 tidb group",
		zap.Uint64("cluster_id", clusterId),
		zap.Uint64("tidb_group_id", tidbGroupId),
	)
	defer func() {
		tilog.Info(
			cctx,
			"end to create tidb group",
			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, "failed to get cluster", zap.Uint64("cluster_id", clusterId), zap.Error(err))
		return err
	}
	if cluster.GetDeleteTime() != nil {
		tilog.Warn(
			cctx,
			"cancel the creation of the tidb group 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, clusterId, tidbGroupId)
	if err != nil {
		tilog.Error(
			cctx,
			"failed to get work pool",
			zap.Uint64("cluster_id", clusterId),
			zap.Uint64("tidb_group_id", tidbGroupId),
			zap.Error(err),
		)
		return err
	}

	trafficFilter, bizErr := c.trafficFilterRepo.LoadByTidbGroupId(cctx, clusterId, tidbGroupId)
	if bizErr != nil && bizErr.ErrCode() != bizerr.ErrCodeRecordNotFound {
		tilog.Error(
			cctx,
			"failed to load traffic filter",
			zap.Uint64("cluster_id", clusterId),
			zap.Uint64("tidb_group_id", tidbGroupId),
			zap.Error(bizErr),
		)
		return bizErr
	}

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

	return nil
}

func (c *CreateTidbGroupAction) create(
	ctx context.Context,
	cluster *entity.Cluster,
	tidbGroup *entity.TidbNodeGroup,
	trafficFilter *entity.TrafficFilter,
) error {
	region, err := c.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.GetOrganizationId())
	if err != nil {
		tilog.Error(
			ctx,
			"failed to find region",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Uint64("tidb_group_id", tidbGroup.Id),
			zap.Error(err),
		)
		return err
	}

	createTidbGroupReq := &clusterdedicatedv1.CreateTidbGroupRequest{
		TidbGroup: &clusterdedicatedv1.TidbGroup{
			Id:                    strconv.FormatUint(tidbGroup.Id, 10),
			ClusterId:             strconv.FormatUint(cluster.ClusterId, 10),
			InstanceType:          "", // leave it empty
			Count:                 0,  // leave it empty
			EnablePublicLb:        true,
			EnablePrivateLb:       true,
			ClusterIdentifierHash: "", // leave it empty
			PublicLb: &clusterdedicatedv1.LoadBalancer{
				Host: "", // leave it empty
				Port: int32(cluster.Port),
				IpAllowList: []*clusterdedicatedv1.LoadBalancer_IpAddress{
					{
						CidrBlock: DefaultInternalCIDR,
					},
				},
				CloudResourceId: "", // leave it empty
			},
			PrivateLb: &clusterdedicatedv1.LoadBalancer{
				Host:            "", // leave it empty
				Port:            int32(cluster.Port),
				IpAllowList:     nil, // leave it empty
				CloudResourceId: "",  // leave it empty
			},
			IsDefault:               false, // default tidb group created by CreateCluster API
			ComponentConfigurations: nil,   // leave it empty
			ClusterVersion:          nil,   // leave it empty
		},
	}

	if trafficFilter != nil {
		createTidbGroupReq.TidbGroup.EnablePublicLb = !trafficFilter.DisablePublicLb
		var ipAllowList []*clusterdedicatedv1.LoadBalancer_IpAddress
		for _, each := range trafficFilter.Items {
			ipAllowList = append(ipAllowList, &clusterdedicatedv1.LoadBalancer_IpAddress{
				CidrBlock: each.Cidr,
			})
		}
		if len(ipAllowList) != 0 {
			createTidbGroupReq.TidbGroup.PublicLb.IpAllowList = ipAllowList
		}
	}

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

	return nil
}
