package tidbgroup

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

	"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) UpdateTidbNodeGroup(
	ctx context.Context,
	req *openapidedicatedv1beta1.UpdateTidbNodeGroupRequest,
) (*openapidedicatedv1beta1.TidbNodeGroup, error) {
	ctx, span := tracer.Start(ctx, "UpdateTidbNodeGroup")
	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)
		if req.GetTidbNodeGroup().GetNodeCount() != 0 {
			details[auditlog.KeyNewTidbQuantity] = res.GetNodeCount()
		}
		details[auditlog.KeyTidbSize] = res.GetNodeSpecKey()
		if req.GetTidbNodeGroup().GetTiproxySetting() != nil {
			details[auditlog.KeyNewTiProxyQuantity] = res.GetTiproxySetting().GetNodeCount()
			if req.GetTidbNodeGroup().GetTiproxySetting().GetNodeSpecKey() != "" {
				details[auditlog.KeyNewTiProxySize] = res.GetTiproxySetting().GetNodeSpecKey()
			}
		}
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventUpdateTidbNodeGroup, 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")
	}

	var groupId uint64
	groupId, err = strconv.ParseUint(req.GetTidbNodeGroup().GetTidbNodeGroupId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid tidb node group id")
	}

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

	if req.GetTidbNodeGroup().NodeCount != nil && req.GetTidbNodeGroup().GetNodeCount() == 0 {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"can not set node count to 0 in update operation, please use tidbNodeGroup related method to delete the group",
		)
	}

	// TODO: remove domain service ?
	// scale out tidb node group need check scale action
	if req.GetTidbNodeGroup().GetNodeCount() != 0 {
		// check ClusterAction_CLUSTER_ACTION_SCALE_CLUSTER mutex
		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{
		Id:          groupId,
		DisplayName: req.GetTidbNodeGroup().GetDisplayName(),
		ClusterId:   clusterId,
		Count:       req.GetTidbNodeGroup().GetNodeCount(),
	}

	if req.GetTidbNodeGroup().GetTiproxySetting() != nil {
		tidbgroupReq.TiproxySetting = &globaldedicatedv1.TidbNodeGroup_TiProxySetting{
			SpuName: req.GetTidbNodeGroup().GetTiproxySetting().GetNodeSpecKey(),
			Count:   req.GetTidbNodeGroup().GetTiproxySetting().GetNodeCount(),
		}
	}

	// update tidb node group
	var tidbGroup *globaldedicatedv1.TidbNodeGroup
	tidbGroup, err = s.dedicatedCli.UpdateTidbNodeGroup(ctx, &globaldedicatedv1.UpdateTidbNodeGroupRequest{
		TidbNodeGroup: tidbgroupReq,
		ValidateOnly:  req.GetValidateOnly(),
	})

	if err != nil {
		return nil, err
	}

	res = cvtGlobalToTidbNodeGroup(tidbGroup)
	return res, nil
}
