package tidbgroup

import (
	"context"
	"errors"
	"fmt"
	"strconv"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"gorm.io/gorm"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (s serverImpl) CreateTidbNodeGroup(
	ctx context.Context,
	req *openapidedicatedv1beta1.CreateTidbNodeGroupRequest,
) (*openapidedicatedv1beta1.TidbNodeGroup, error) {
	ctx, span := tracer.Start(ctx, "CreateTidbNodeGroup")
	defer span.End()
	var (
		err     error
		reqMeta *metadata.RequestMeta
		res     *openapidedicatedv1beta1.TidbNodeGroup
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeDedicated)
		details = eventbus.AppendDedicatedTidbGroupDetails(details, res)
		details[auditlog.KeyTidbQuantity] = res.GetNodeCount()
		details[auditlog.KeyTidbSize] = res.GetNodeSpecKey()
		if res.GetTiproxySetting() != nil {
			details[auditlog.KeyTiProxyQuantity] = res.GetTiproxySetting().GetNodeCount()
			details[auditlog.KeyTiProxySize] = res.GetTiproxySetting().GetNodeSpecKey()
		}
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventCreateTidbNodeGroup, reqMeta, detailsFunc, err)
	}()

	// Extract request meta.
	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	// Validate request.
	if req.GetTidbNodeGroup() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "tidb Node group is required")
	}

	if req.GetTidbNodeGroup().GetNodeCount() <= 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "invalid node count, must be greater than 0")
	}

	clusterId, err := strconv.ParseUint(req.GetTidbNodeGroup().GetClusterId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cluster id")
	}

	// check ClusterAction_CLUSTER_ACTION_SCALE_CLUSTER mutex
	// create new tidb node group also means scale cluster
	// need to check if there is a mutex cluster action
	isMutex, status, err := s.customStatusRepo.IsMutexClusterAction(
		ctx,
		clusterId,
		tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_SCALE_CLUSTER,
	)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			err = fmt.Errorf("failed to check mutex cluster action: %w", err)
			return nil, bizerror.NewInternal(err, err.Error())
		}
	}

	if isMutex {
		tilog.Error(
			ctx,
			"mutex custom status is exist",
			zap.Uint64("cluster_id", clusterId),
			zap.Any("custom status", status),
		)
		return nil, bizerror.NewForbiddenForClusterCustomStatus(
			constant.ErrClusterCustomStatusForbidden,
			status.StatusType.String(),
		)
	}

	tidbgroupReq := &globaldedicatedv1.TidbNodeGroup{
		ClusterId:   clusterId,
		DisplayName: req.GetTidbNodeGroup().GetDisplayName(),
		Count:       req.TidbNodeGroup.GetNodeCount(),
		Endpoint: &globaldedicatedv1.TidbNodeGroup_Endpoint{
			PublicEndpoint: &globaldedicatedv1.TidbNodeGroup_Endpoint_PublicEndpoint{
				EnablePublicLb: lo.ToPtr(false), // new tidb node group does not enable public load balancer
			},
		},
	}

	if req.GetTidbNodeGroup().GetTiproxySetting() != nil {
		if req.GetTidbNodeGroup().GetTiproxySetting().GetNodeCount() <= 0 {
			return nil, bizerr.New(bizerr.ErrCodeParams, "invalid tiproxy node count, must be greater than 0")
		}
		tidbgroupReq.TiproxySetting = &globaldedicatedv1.TidbNodeGroup_TiProxySetting{
			SpuName: req.GetTidbNodeGroup().GetTiproxySetting().GetNodeSpecKey(),
			Count:   req.GetTidbNodeGroup().GetTiproxySetting().GetNodeCount(),
		}
	}

	// Create TiDB Node group.
	tidbGroup, err := s.dedicatedCli.CreateTidbNodeGroup(ctx, &globaldedicatedv1.CreateTidbNodeGroupRequest{
		TidbNodeGroup: tidbgroupReq,
		ValidateOnly:  req.GetValidateOnly(),
	})
	if err != nil {
		return nil, err
	}

	res = cvtGlobalToTidbNodeGroup(tidbGroup)
	return res, nil
}
