package cluster

import (
	"context"
	"strconv"
	"time"

	"github.com/tidbcloud/tiinf/sdk/auditlog"

	openapiserverlessv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/serverless/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (c serverImpl) ClaimClusterFromZero(
	ctx context.Context,
	request *openapiserverlessv1beta1.ClaimClusterFromZeroRequest,
) (respCluster *openapiserverlessv1beta1.Cluster, err error) {
	var (
		reqMeta *metadata.RequestMeta
	)
	tenantID := unknownTenantID
	startTime := time.Now()
	defer func() {
		recordWriteClusterAPIMetrics(apiClaimClusterFromZero, tenantID, time.Since(startTime).Seconds(), err)
	}()

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeServerless)
		details = eventbus.AppendServerlessClusterDetails(details, request.Cluster)
		return details
	}
	defer func() {
		c.auditLogSender.SubmitAuditLog(ctx, auditlog.EventCreateCluster, reqMeta, detailsFunc, err)
	}()

	if request.Cluster == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster object should not be nil")
	}

	if request.GetZeroId() == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "zero id must not be empty")
	}

	// Extract request meta and verify the caller has permission on their own tenant.
	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}
	tenantID = strconv.FormatUint(reqMeta.OrgID, 10)

	if request.Cluster.GetSpendingLimit().GetMonthly() < 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "spending limit should be greater than or equal to 0")
	} else if request.Cluster.GetSpendingLimit().GetMonthly() > int32(1000000) {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"Spending limit cannot exceed $10000. If you require a higher limit, please contact our sales team for assistance.",
		)
	}

	// Resolve project ID (fall back to the organisation's default project)
	projectId := reqMeta.DefaultProjectID
	if request.GetCluster().Labels != nil {
		projectIdStr, ok := request.Cluster.Labels[common.ProjectIDLabelKey]
		if ok {
			projectId, err = strconv.ParseUint(projectIdStr, 10, 64)
			if err != nil {
				return nil, bizerr.WrapErrorWithCode(
					err,
					bizerr.ErrCodeParams,
					"invalid %s label",
					common.ProjectIDLabelKey,
				)
			}
		}
	}

	// Parse the region field
	if request.GetCluster().Region == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region is invalid")
	}
	provider, region, err := common.ParseOpenApiRegionNameOrId(request.GetCluster().GetRegion().GetName())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region, please check and try again.")
	}

	// Validate cluster display name format
	if !common.CheckDisplayNameFmt(request.GetCluster().GetDisplayName()) {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster name not allowed: %s", request.GetCluster().GetDisplayName())
	}

	// Build the entity cluster object (same as CreateCluster)
	cluster, err := buildCreateCluster(projectId, provider, region, reqMeta, request.GetCluster())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "request for claim cluster is invalid")
	}

	// Delegate to domain service – zero id decryption and state transitions happen there
	cluster, err = c.clusterSvc.ClaimClusterFromZero(ctx, cluster, request.GetZeroId())
	if err != nil {
		return nil, err
	}
	reqMeta.ClusterID = cluster.ClusterId

	respCluster, err = buildClusterFromEntity(cluster, nil, openapiserverlessv1beta1.ClusterView_BASIC)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to build cluster response")
	}

	return respCluster, nil
}
