package cluster

import (
	"context"
	"errors"
	"fmt"

	"braces.dev/errtrace"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/serverless"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
)

func (svc *service) UpdateCluster(ctx context.Context, cluster *entity.Cluster) (*entity.Cluster, error) {
	ctx, span := serverless.Tracer.Start(ctx, "ClusterService.UpdateCluster")
	defer span.End()

	if cluster == nil {
		return nil, errors.New("cluster is nil")
	}

	serverlessCluster, err := cluster.GetServerlessCluster()
	if err != nil {
		return nil, err
	}

	if err := svc.transaction.Transaction(ctx, func(ctx context.Context) error {
		// First make sure cluster exists in database
		dbCluster, err := svc.clusterRepo.GetCluster(ctx, cluster.ClusterId, false)
		if err != nil {
			return fmt.Errorf("failed to find cluster in database: %w", err)
		}

		if cluster.DeployType != entity.DeployTypeServerless {
			return fmt.Errorf("cluster deploy type is not serverless")
		}

		// propagate original_project_id from the DB record so the global service
		// interceptor can resolve the cluster even after a project move.
		// Fall back to project_id for legacy clusters where original_project_id is 0.
		originalProjectId := dbCluster.OriginalProjectId
		if originalProjectId == 0 {
			originalProjectId = dbCluster.ProjectId
		}
		serverlessCluster.OriginalProjectId = originalProjectId

		// update cluster in global serverless service
		req := &globalserverlessv1.ServerlessServiceUpdateRequest{
			Cluster:      serverlessCluster,
			ValidateOnly: false,
		}
		resp, err := svc.serverlessCli.UpdateCluster(ctx, req)
		if err != nil {
			return errtrace.Wrap(err)
		}

		if resp.Cluster == nil {
			return fmt.Errorf("cluster in update response should not be nil")
		}

		serverlessCluster = resp.Cluster

		if cluster.DisplayName != "" {
			// Check cluster name is unique under the project
			count, err := svc.clusterRepo.CountDisplayNameUnderProject(ctx, cluster.ProjectId, cluster.DisplayName)
			if err != nil {
				return fmt.Errorf("failed to update cluster display name in database: %w", err)
			}
			if count > 0 {
				return bizerror.NewDisplayNameAlreadyExists(constant.ErrDuplicatedClusterName)
			}

			err = svc.clusterRepo.UpdateDisplayName(ctx, cluster.ClusterId, cluster.DisplayName)
			if err != nil {
				return fmt.Errorf("failed to update cluster display name in database: %w", err)
			}
		}

		if cluster.Labels != nil {
			err := svc.clusterRepo.UpdateLabels(ctx, cluster.ClusterId, cluster.Labels)
			if err != nil {
				return fmt.Errorf("failed to update cluster labels in database: %w", err)
			}
		}
		if cluster.ServicePlan == openapicommonv1beta1.ServicePlan_Starter ||
			cluster.ServicePlan == openapicommonv1beta1.ServicePlan_Essential {
			err := svc.clusterRepo.UpdateServicePlan(ctx, cluster.ClusterId, cluster.ServicePlan.String())
			if err != nil {
				return fmt.Errorf("failed to update cluster service_plan in database: %w", err)
			}
		}

		// Finally, update cluster record in schema.
		err = svc.clusterRepo.TouchCluster(ctx, cluster.ClusterId)
		if err != nil {
			return fmt.Errorf("failed to touch cluster record in database: %w", err)
		}
		cluster, err = svc.clusterRepo.GetCluster(ctx, cluster.ClusterId, false)
		if err != nil {
			return fmt.Errorf("failed to find cluster in database: %w", err)
		}

		cluster = cvtServerlessCluster(cluster, serverlessCluster)

		return nil
	}); err != nil {
		return cluster, err
	}

	return cluster, nil
}
