package cluster

import (
	"context"
	"errors"

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

	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"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/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (c *serverImpl) ApplyCluster(
	ctx context.Context,
	request *tidbmgmtservicev1.ApplyClusterRequest,
) (*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 invalid")
	}

	if request.GetClusterApplyMask() == nil ||
		(!request.GetClusterApplyMask().GetVersion() && len(request.GetClusterApplyMask().GetComponentApplyMasks()) == 0) {
		return nil, status.Error(codes.InvalidArgument, "cluster apply mask not set")
	}

	if request.StatusId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "status id is invalid")
	}

	cluster, err := c.dedicatedClusterSvc.GetCluster(
		ctx,
		request.GetClusterId(),
		entity.ClusterViewClusterViewLocal,
		false,
		false,
	)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerror.NewNotFound(err, "dedicated cluster not found")
		}
		return nil, bizerror.NewInternal(err, "failed to get dedicated cluster")
	}

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

	cluster, customStatus, err := c.dedicatedClusterSvc.ApplyCluster(
		ctx,
		request.ClusterId,
		request.ClusterApplyMask,
		false,
	)

	if err != nil {
		if errors.Is(err, constant.ErrClusterCustomStatusForbidden) {
			return nil, bizerror.NewForbiddenForClusterCustomStatus(err, customStatus.StatusType.String())
		}
		return nil, err
	}

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