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"
	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"
)

func (c *serverImpl) UpdateClusterRuVersion(
	ctx context.Context,
	request *tidbmgmtservicev1.UpdateClusterRuVersionRequest,
) (*tidbmgmtservicev1.Cluster, error) {
	if request == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "request is nil")
	}
	if request.ClusterId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster_id is required")
	}
	if request.RuVersion <= 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "ru_version must be a positive integer")
	}

	// Get cluster from DB
	cluster, err := c.clusterRepo.GetCluster(ctx, request.ClusterId, false)
	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")
	}

	// Update ru_version in tidb-mgmt-service DB
	if err := c.clusterRepo.UpdateRuVersion(ctx, request.ClusterId, request.RuVersion); err != nil {
		return nil, bizerror.NewInternal(err, "failed to update ru_version in database")
	}

	// Route to the appropriate global service based on deploy type
	switch cluster.DeployType {
	case entity.DeployTypeServerless:
		if err := c.updateServerlessClusterRuVersion(ctx, cluster, request.RuVersion); err != nil {
			return nil, err
		}
	case entity.DeployTypeNextGen:
		if err := c.updateNextGenClusterRuVersion(ctx, cluster, request.RuVersion); err != nil {
			return nil, err
		}
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "ru_version update is not supported for deploy type: %s", cluster.DeployType)
	}

	// Re-fetch cluster to get updated state
	cluster, err = c.clusterRepo.GetCluster(ctx, request.ClusterId, false)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get cluster after update")
	}

	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 (c *serverImpl) updateServerlessClusterRuVersion(
	ctx context.Context,
	cluster *entity.Cluster,
	ruVersion int32,
) error {
	// Append metadata for the sgs interceptor
	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		OrgID:            cluster.OrganizationId,
		DefaultProjectID: cluster.ProjectId,
		ValidProjectList: []uint64{cluster.ProjectId},
		ClusterID:        cluster.ClusterId,
		UserID:           "0",
	})

	newCluster := buildNewEntityCluster(
		cluster,
		func(c *entity.Cluster, serverless *globalserverlessv1.ServerlessServiceCluster) {
			serverless.RuVersion = ruVersion
			serverless.Cluster = &regionalserverlessv1.ServerlessRegionalServiceClusterInfo{
				Annotations: map[string]string{
					"free-tier.tidbcloud.com/ru-version": strconv.Itoa(int(ruVersion)),
				},
			}
		},
	)

	_, err := c.serverlessClusterSvc.UpdateCluster(ctx, newCluster)
	return err
}

func (c *serverImpl) updateNextGenClusterRuVersion(
	ctx context.Context,
	cluster *entity.Cluster,
	ruVersion int32,
) error {
	// Append metadata for the nextgen-global-service interceptor
	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		OrgID:            cluster.OrganizationId,
		DefaultProjectID: cluster.ProjectId,
		ValidProjectList: []uint64{cluster.ProjectId},
		ClusterID:        cluster.ClusterId,
		UserID:           "0",
	})
	return c.nextgenClusterSvc.UpdateClusterRuVersion(ctx, cluster.ClusterId, ruVersion)
}
