package action

import (
	"context"
	"strconv"

	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/fastflow/pkg/entity/run"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/regionalserver"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	clusterreop "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster"
	regionrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region"
	reservedcapacitytemplaterepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/reservedcapacitytemplate"
	resourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/resourcepoolpolicy"
	topologysvc "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/topology"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const (
	CreateTidbGroup = "create_tidb_group"
)

type CreateTidbGroupAction struct {
	clusterRepo          clusterreop.Repo
	tidbGroupRepo        tidbgroup.Repo
	regionRepo           regionrepo.Repo
	regionalserverCli    regionalserver.Client
	reservedTemplateRepo reservedcapacitytemplaterepo.Repo
	resourcePoolRepo     resourcepoolrepo.Repo
}

func NewCreateTidbGroupAction(
	clusterRepo clusterreop.Repo,
	tidbGroupRepo tidbgroup.Repo,
	regionRepo regionrepo.Repo,
	regionalserverCli regionalserver.Client,
	reservedTemplateRepo reservedcapacitytemplaterepo.Repo,
	resourcePoolRepo resourcepoolrepo.Repo,
) *CreateTidbGroupAction {
	return &CreateTidbGroupAction{
		clusterRepo:          clusterRepo,
		tidbGroupRepo:        tidbGroupRepo,
		regionRepo:           regionRepo,
		regionalserverCli:    regionalserverCli,
		reservedTemplateRepo: reservedTemplateRepo,
		resourcePoolRepo:     resourcePoolRepo,
	}
}

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
	}

	dnsIdentity, _ := ctx.GetVar(common.KeyDnsIdentity)
	disablePublicDnsIdentityStr, _ := ctx.GetVar(common.KeyDisablePublicDnsIdentity)
	var disablePublicDnsIdentity bool
	if disablePublicDnsIdentityStr != "" {
		var parseErr error
		disablePublicDnsIdentity, parseErr = strconv.ParseBool(disablePublicDnsIdentityStr)
		if parseErr != nil {
			tilog.Error(cctx, "failed to parse disable_public_dns_identity",
				zap.String("value", disablePublicDnsIdentityStr), zap.Error(parseErr))
			return parseErr
		}
	}

	if err := c.create(cctx, cluster, tidbGroup, dnsIdentity, disablePublicDnsIdentity); 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.TidbGroup,
	dnsIdentity string,
	disablePublicDnsIdentity bool,
) error {
	region, err := c.regionRepo.Get(ctx, cluster.RegionId)
	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 := &clusterngv1beta.CreateTiDBGroupRequest{
		Id:        strconv.FormatUint(tidbGroup.Id, 10),
		ClusterId: strconv.FormatUint(cluster.ClusterId, 10),
		TenantId:  strconv.FormatUint(cluster.OrganizationId, 10),
	}

	if dnsIdentity != "" || disablePublicDnsIdentity {
		sql := &clusterngv1beta.TiDBGroup_SqlEndpoint{}
		if dnsIdentity != "" {
			sql.DnsIdentity = dnsIdentity
		}
		if disablePublicDnsIdentity {
			sql.DisablePublicDnsIdentity = disablePublicDnsIdentity
		}
		createTidbGroupReq.SqlEndpoint = sql
	}

	if cluster.GetServicePlan() == globalcommonv1.ServicePlan_Premium_Reserved {
		topologySpec, topoErr := c.buildTidbGroupTopologySpec(ctx, cluster)
		if topoErr != nil {
			return topoErr
		}
		createTidbGroupReq.TopologySpec = topologySpec
	}
	stop, guardErr := guardClusterResourcePoolMutation(
		ctx,
		c.clusterRepo,
		c.resourcePoolRepo,
		cluster,
		resourcepoolpolicy.OperationUpdateInstance,
	)
	if guardErr != nil {
		return guardErr
	}
	if stop {
		return nil
	}

	if _, err := c.regionalserverCli.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
}

func (c *CreateTidbGroupAction) buildTidbGroupTopologySpec(ctx context.Context, cluster *entity.Cluster) (*clusterngv1beta.TopologySpec, error) {
	template := cluster.GetReservedCapacityTemplate()
	if template == nil {
		loadedTemplate, err := c.reservedTemplateRepo.Load(ctx, cluster.GetReservedCapacityTemplateId(), false)
		if err != nil {
			tilog.Error(ctx,
				"failed to load reserved capacity template for tidb group topology",
				zap.Uint64("cluster_id", cluster.GetClusterId()),
				zap.Uint64("reserved_capacity_template_id", cluster.GetReservedCapacityTemplateId()),
				zap.Error(err),
			)
			return nil, err
		}
		template = loadedTemplate
	}

	return topologysvc.BuildTopologySpec(template.GetFixedTopology(), topologysvc.TiDBGroupComponentFilter)
}
