package cluster

import (
	"context"
	"errors"
	"strconv"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"gorm.io/gorm"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"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"
)

// UpdateServerlessRegionalClusterAnnotations updates the annotations of a serverless cluster, it need not generate events or check cluster custom status.
func (c *serverImpl) UpdateServerlessRegionalClusterAnnotations(
	ctx context.Context,
	request *tidbmgmtservicev1.UpdateServerlessRegionalClusterAnnotationsRequest,
) (*tidbmgmtservicev1.Cluster, error) {
	if request == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "update cluster request is nil")
	}
	if len(request.Annotations) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "annotations is empty")
	}

	// get basic view from domain service
	cluster, err := c.serverlessClusterSvc.GetCluster(ctx, request.ClusterId, entity.ClusterViewClusterViewBasic)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerror.NewNotFound(err, "cluster not found")
		}
		return nil, bizerror.NewInternal(err, "failed to get cluster")
	}
	// check cluster
	if cluster.DeployType != entity.DeployTypeServerless {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster is not serverless")
	}
	serverlessCluster, err := cluster.GetServerlessCluster()
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get serverless cluster")
	}
	if serverlessCluster.UserStatus == globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_DELETED {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster not find")
	}

	// gen request meta and append to outgoing meta
	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		OrgID:            cluster.OrganizationId,
		DefaultProjectID: cluster.ProjectId,
		ValidProjectList: []uint64{cluster.ProjectId},
		ClusterID:        request.ClusterId,
		UserID:           "0",
	})

	newCluster := buildNewEntityCluster(
		cluster,
		func(c *entity.Cluster, serverless *globalserverlessv1.ServerlessServiceCluster) {
			serverless.Cluster = &regionalserverlessv1.ServerlessRegionalServiceClusterInfo{
				Annotations: request.Annotations,
			}
			if rcuStr, ok := request.Annotations["free-tier.tidbcloud.com/max-rcu"]; ok {
				if rcu, _ := strconv.ParseUint(rcuStr, 10, 32); rcu > 0 {
					c.ServicePlan = openapicommonv1beta1.ServicePlan_Essential
				}
			} else if rcuStr, ok := request.Annotations["free-tier.tidbcloud.com/min-rcu"]; ok {
				if rcu, _ := strconv.ParseUint(rcuStr, 10, 32); rcu > 0 {
					c.ServicePlan = openapicommonv1beta1.ServicePlan_Essential
				}
			}
		},
	)

	cluster, err = c.serverlessClusterSvc.UpdateCluster(ctx, newCluster)
	if err != nil {
		return nil, err
	}

	cstatus, err := c.customStatusSvc.ListClusterCustomStatus(ctx, []uint64{cluster.ClusterId}, nil, false)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	return cvtToMgmtCluster(cluster, cstatus[cluster.ClusterId]), nil
}

func buildNewEntityCluster(
	cls *entity.Cluster,
	b func(c *entity.Cluster, serverless *globalserverlessv1.ServerlessServiceCluster),
) *entity.Cluster {
	cluster := &entity.Cluster{
		ClusterId:      cls.ClusterId,
		ProjectId:      cls.ProjectId,
		OrganizationId: cls.OrganizationId,
		DeployType:     entity.DeployTypeServerless,
	}
	serverless := &globalserverlessv1.ServerlessServiceCluster{
		ClusterId: strconv.FormatUint(cls.ClusterId, 10),
	}

	b(cluster, serverless)

	cluster.GlobalCluster = serverless
	return cluster
}
