package cluster

import (
	"context"
	"fmt"
	"strconv"

	"github.com/samber/lo"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	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/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (c *serverImpl) CreateClusterWithClusterConfiguration(
	ctx context.Context,
	request *tidbmgmtservicev1.CreateClusterWithClusterConfigurationRequest,
) (*tidbmgmtservicev1.Cluster, error) {
	if request == nil || request.Cluster == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster is nil")
	}

	if request.GetCluster().GetDeployType() != tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "only dedicated cluster supprot restore")
	}

	// Extract request meta.
	requestMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	// Check cluster name format
	if !common.CheckDisplayNameFmt(request.GetCluster().GetClusterName()) {
		err := fmt.Errorf("cluster name not allowed: %s", request.GetCluster().GetClusterName())
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid display name")
	}

	// full global cluster
	clusterEntity, err := buildDedicatedGlobalCluster(request.GetCluster())
	if err != nil {
		return nil, err
	}
	clusterEntity.UserId = requestMeta.UserID
	clusterEntity.Labels[metadata.UserIDHeaderKey] = requestMeta.UserID

	// create cluster
	cluster, err := c.dedicatedClusterSvc.CreateCluster(ctx, clusterEntity, request.GetClusterConfiguration(), false)
	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 buildDedicatedGlobalCluster(cluster *tidbmgmtservicev1.Cluster) (*entity.Cluster, error) {
	// build global cluster
	globalCluster := cluster.GetDedicatedCluster()

	if globalCluster == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "dedicated cluster is nil")
	}

	openapiProvider, err := enum.FromString[openapicommonv1beta1.Region_CloudProvider](cluster.CloudProvider)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "provider %s is not supported", cluster.CloudProvider)
	}

	globalProvider, err := enum.FromString[globalcommonv1.CloudProvider_Name](cluster.CloudProvider)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "provider %s is not supported", cluster.CloudProvider)
	}

	cloudRegionId, err := enum.FromString[globalcommonv1.Region_Name](cluster.Region)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region %s is not supported", cluster.Region)
	}

	// full global cluster
	globalCluster.CloudProviderId = globalcommonv1.CloudProvider_Name(globalProvider)
	globalCluster.RegionId = globalcommonv1.Region_Name(cloudRegionId)
	globalCluster.OrganizationId = cluster.OrganizationId
	globalCluster.ProjectId = cluster.ProjectId

	if len(globalCluster.GetClusterComponents()) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster components is nil")
	}

	entityCluster := &entity.Cluster{
		ProjectId:      cluster.ProjectId,
		OrganizationId: cluster.OrganizationId,
		DisplayName:    cluster.ClusterName,
		CloudProvider:  openapicommonv1beta1.Region_CloudProvider(openapiProvider),
		Region:         cluster.Region,
		CreatedBy:      cluster.CreatedBy,
		ServicePlan:    openapicommonv1beta1.ServicePlan_Dedicated,
		Labels: map[string]string{
			common.ProjectIDLabelKey: strconv.FormatUint(cluster.ProjectId, 10),
			common.OrgIDLabelKey:     strconv.FormatUint(cluster.OrganizationId, 10),
		},
	}

	if cluster.CreateTime != nil {
		entityCluster.CreatedAt = cluster.CreateTime.AsTime()
	}

	if cluster.UpdateTime != nil {
		entityCluster.UpdatedAt = cluster.UpdateTime.AsTime()
	}

	if cluster.DeleteTime != nil {
		entityCluster.DeletedAt = lo.ToPtr(cluster.DeleteTime.AsTime())
	}

	entityCluster.DeployType = entity.DeployTypeDedicated
	entityCluster.GlobalCluster = globalCluster
	return entityCluster, nil
}

func (c *serverImpl) CreateServerlessCluster(
	ctx context.Context,
	request *tidbmgmtservicev1.CreateServerlessClusterRequest,
) (*tidbmgmtservicev1.Cluster, error) {
	if !common.CheckDisplayNameFmt(request.GetClusterName()) {
		err := fmt.Errorf("cluster name not allowed: %s", request.GetClusterName())
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid display name")
	}

	requestMeta := &metadata.RequestMeta{
		OrgID:            request.GetOrganizationId(),
		DefaultProjectID: request.GetProjectId(),
		ValidProjectList: []uint64{request.GetProjectId()},
	}
	requestMeta, err := c.fillUserInfo(ctx, requestMeta, request.GetCreatedBy())
	if err != nil {
		return nil, err
	}

	// gen request meta and append to incoming meta
	ctx = metadata.AppendToIncomingMeta(ctx, requestMeta)

	openapiProvider, err := enum.FromString[openapicommonv1beta1.Region_CloudProvider](request.GetCloudProvider())
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "provider %s is not supported", request.GetCloudProvider())
	}

	// full global cluster
	createCluster := &entity.Cluster{
		ProjectId:      request.GetProjectId(),
		OrganizationId: request.GetOrganizationId(),
		DisplayName:    request.GetClusterName(),
		Region:         request.GetRegion(),
		CloudProvider:  openapicommonv1beta1.Region_CloudProvider(openapiProvider),
		CreatedBy:      getCreatedBy(requestMeta, request.GetCreatedBy()),
		DeployType:     entity.DeployTypeServerless,
		UserId:         requestMeta.UserID,
		Labels: map[string]string{
			common.ProjectIDLabelKey: strconv.FormatUint(request.GetProjectId(), 10),
			common.OrgIDLabelKey:     strconv.FormatUint(request.GetOrganizationId(), 10),
			metadata.UserIDHeaderKey: requestMeta.UserID,
		},
		ServicePlan: openapicommonv1beta1.ServicePlan_Starter,
	}

	globalCluster := &globalserverlessv1.ServerlessServiceCluster{
		TenantId:      request.GetOrganizationId(),
		ProjectId:     request.GetProjectId(),
		CloudProvider: request.GetCloudProvider(),
		Region:        request.GetRegion(),
		SpendLimit:    request.GetSpendingLimit(),
		BackupPolicy:  request.GetAutomatedBackupPolicy(),
		Cluster: &regionalserverlessv1.ServerlessRegionalServiceClusterInfo{
			EnhancedEncryptionEnabled: request.GetEncryptionConfig().GetEnhancedEncryptionEnabled(),
			Endpoints: &regionalserverlessv1.Endpoints{
				Public: &regionalserverlessv1.PublicEndpoint{
					Disabled: request.GetDisablePublicEndpoint(),
				},
			},
			ZoneRedundantEnabled: request.GetZoneRedundantEnabled(),
		},
	}

	if request.GetAutoScaling().GetMinRcu() > 0 || request.GetAutoScaling().GetMaxRcu() > 0 {
		/* essential plan */
		globalCluster.Cluster.AutoScaling = &regionalserverlessv1.ServerlessRegionalServiceClusterInfo_AutoScaling{
			MinRcu: request.GetAutoScaling().GetMinRcu(),
			MaxRcu: request.GetAutoScaling().GetMaxRcu(),
		}
		globalCluster.SpendLimit = nil
		createCluster.ServicePlan = openapicommonv1beta1.ServicePlan_Essential
	}

	createCluster.GlobalCluster = globalCluster

	// restore cluster
	cluster, err := c.serverlessClusterSvc.CreateCluster(ctx, createCluster)
	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
}
