package tidbgroup

import (
	"context"
	"errors"
	"strconv"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/resourcepoolpolicy"
	servicecommon "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/common"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const defaultTidbGroupName = "DefaultGroup"

func (s *serviceImpl) CreateTiDBGroup(
	ctx context.Context,
	req *globalngv1.CreateTiDBGroupRequest,
) (*globalngv1.TiDBGroup, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.CreateTiDBGroup")
	defer span.End()

	if req.GetTidbGroup() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "tidb_group is required")
	}
	if req.GetTidbGroup().GetClusterId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster_id is required")
	}

	clusterId := req.GetTidbGroup().GetClusterId()

	cluster, err := s.clusterRepo.Get(ctx, clusterId, false)
	if err != nil {
		return nil, err
	}

	if cluster.State != globalngv1.Cluster_AVAILABLE {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster is not available")
	}
	resourcePoolID, parseErr := strconv.ParseUint(cluster.GetSharedPoolId(), 10, 64)
	if parseErr != nil || resourcePoolID == 0 {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "cluster has an invalid resource pool id")
	}
	resourcePool, err := s.resourcePoolRepo.Load(ctx, resourcePoolID, false)
	if err != nil {
		return nil, err
	}
	if lifecycleErr := servicecommon.RequireResourcePoolLifecycle(
		resourcePool,
		resourcepoolpolicy.OperationUpdateInstance,
	); lifecycleErr != nil {
		return nil, lifecycleErr
	}

	// Generate a new ID for the TiDB group
	id := s.idGeneratorAdapter.MustNew()
	group := &entity.TidbGroup{
		Id:          id,
		DisplayName: defaultTidbGroupName,
		Default:     true,
		ClusterId:   clusterId,
	}

	workflowVars := map[string]string{
		workflow.KeyTraceId:    workflow.GetTraceId(ctx),
		common.KeyClusterId:    strconv.FormatUint(clusterId, 10),
		common.KeyTidbGroupId:  strconv.FormatUint(id, 10),
		common.KeyTidbGroupIds: strconv.FormatUint(id, 10),
	}

	if req.GetTidbGroup().GetDnsIdentity() != "" {
		workflowVars[common.KeyDnsIdentity] = req.GetTidbGroup().GetDnsIdentity()
	}
	if req.GetTidbGroup().GetDisablePublicDnsIdentity() {
		workflowVars[common.KeyDisablePublicDnsIdentity] = strconv.FormatBool(req.GetTidbGroup().GetDisablePublicDnsIdentity())
	}

	terr := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		lockedResourcePool, lockErr := s.resourcePoolRepo.LoadForUpdate(ctx, resourcePoolID, false)
		if lockErr != nil {
			return lockErr
		}
		if lifecycleErr := servicecommon.RequireResourcePoolLifecycle(
			lockedResourcePool,
			resourcepoolpolicy.OperationUpdateInstance,
		); lifecycleErr != nil {
			return lifecycleErr
		}
		tidbGroups, err := s.tidbgroupRepo.Query(ctx, &tidbgroup.Filter{
			ClusterIds: []uint64{cluster.ClusterId},
		})
		if err != nil {
			return err
		}
		if len(tidbGroups) > 0 {
			return bizerr.New(bizerr.ErrCodeRecordExist, "tidb group already exists for cluster %d", cluster.ClusterId)
		}

		if err := s.tidbgroupRepo.Create(ctx, group); err != nil {
			return bizerr.WrapError(err, "create tidb group failed")
		}

		workflowErr := s.workflow.RunDagWithBizId(
			ctx,
			common.CreateTidbGroup,
			strconv.FormatUint(cluster.OrganizationId, 10),
			"0",
			strconv.FormatUint(clusterId, 10),
			workflowVars,
		)
		if workflowErr != nil {
			return bizerr.WrapErrorWithCode(workflowErr, bizerr.ErrCodeUnknown, "create async workflow failed")
		}

		return nil
	})

	if terr != nil {
		var be bizerr.BizError
		if errors.As(terr, &be) {
			return nil, be
		}
		return nil, bizerr.WrapGormError(terr, "create tidb group failed")
	}

	return &globalngv1.TiDBGroup{
		Id:                       id,
		ClusterId:                clusterId,
		DisplayName:              defaultTidbGroupName,
		DnsIdentity:              req.GetTidbGroup().GetDnsIdentity(),
		DisablePublicDnsIdentity: req.GetTidbGroup().GetDisablePublicDnsIdentity(),
	}, nil
}
