package central

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	centralapi "github.com/tidbcloud/core-svc/idl/pbgen/proto/central"

	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	openapiserverlessv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/serverless/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/internal/api/types"
	apiutils "github.com/tidbcloud/tidb-management-service/tempo-wrapper/internal/api/utils"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc"
	coregrpc "github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc/core_portal"
	mgmtgrpc "github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc/mgmt"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

func CreateCluster(ctx *gin.Context) {
	req := &centralapi.CreateClusterReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	// check org/project id
	orgId, err := strconv.ParseUint(ctx.Param("org_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid org id: %w", err))
		return
	}
	projectId, err := strconv.ParseUint(ctx.Param("project_id"), 10, 64)
	if err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("invalid project id: %w", err))
		return
	}

	// set org and project id
	req.OrgId = orgId
	req.ProjectId = projectId

	if req.IsDevTier {
		allow, err := apiutils.IsServerlessAllow(ctx, orgId)
		if err != nil {
			types.InternalErrorResp(ctx, err)
			return
		}
		if allow {
			c, err := mgmtgrpc.CreateServerlessCluster(
				grpc.NewContext(ctx),
				&openapiserverlessv1beta1.CreateClusterRequest{
					Cluster: convertCreateClusterReqToServerlessCluster(req),
				},
			)
			if err != nil {
				types.GrpcErrorResp(ctx, err)
				return
			}
			clusterID, err := strconv.ParseUint(c.ClusterId, 10, 64)
			if err != nil {
				types.InternalErrorResp(ctx, fmt.Errorf("parse cluster id failed: %w", err))
				return
			}
			types.SuccessResp(ctx, &centralapi.CreateClusterResp{
				Id: clusterID,
				BaseResp: &centralapi.BaseResp{
					ErrCode: int64(bizerr.ErrCodeOK),
					ErrMsg:  "",
				},
			})
			return
		}
	} else { // new dedicated cluster
		allow, err := apiutils.IsDedicatedAllow(ctx, orgId, projectId)
		if err != nil {
			types.InternalErrorResp(ctx, err)
			return
		}
		if allow {
			createReq, beer := convertCreateClusterReqToDedicatedCluster(req)
			if beer != nil {
				types.BizErrorRequestResp(ctx, beer)
				return
			}

			c, err := mgmtgrpc.CreateDedicatedCluster(grpc.NewContext(ctx), &openapidedicatedv1beta1.CreateClusterRequest{
				Cluster: createReq,
			})
			if err != nil {
				types.GrpcErrorResp(ctx, err)
				return
			}
			clusterID, err := strconv.ParseUint(c.ClusterId, 10, 64)
			if err != nil {
				types.InternalErrorResp(ctx, fmt.Errorf("parse cluster id failed: %w", err))
				return
			}
			types.SuccessResp(ctx, &centralapi.CreateClusterResp{
				Id: clusterID,
				BaseResp: &centralapi.BaseResp{
					ErrCode: int64(bizerr.ErrCodeOK),
					ErrMsg:  "",
				},
			})
			return
		}
	}

	resp, err := coregrpc.CreateCluster(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}

func convertCreateClusterReqToServerlessCluster(req *centralapi.CreateClusterReq) *openapiserverlessv1beta1.Cluster {
	provider := openapicommonv1beta1.Region_CLOUD_PROVIDER_UNSPECIFIED
	switch strings.ToLower(req.Provider) {
	case apiutils.AwsProvider:
		provider = openapicommonv1beta1.Region_aws
	case apiutils.GcpProvider:
		provider = openapicommonv1beta1.Region_gcp
	case apiutils.AzureProvider:
		provider = openapicommonv1beta1.Region_azure
	}

	if req.SpendLimit == nil {
		req.SpendLimit = new(uint32)
	}

	return &openapiserverlessv1beta1.Cluster{
		DisplayName: req.Name,
		Region: &openapicommonv1beta1.Region{
			CloudProvider: provider,
			Name:          apiutils.ConvertRegionToMgmt(req.Region, provider.String()),
		},
		Labels: map[string]string{
			apiutils.ProjectIDLabelKey: strconv.FormatUint(req.ProjectId, 10),
			apiutils.OrgIDLabelKey:     strconv.FormatUint(req.OrgId, 10),
		},
		SpendingLimit: &openapiserverlessv1beta1.Cluster_SpendingLimit{
			Monthly: int32(*req.SpendLimit),
		},
		Endpoints: &openapiserverlessv1beta1.Cluster_Endpoints{
			Public: &openapiserverlessv1beta1.Cluster_Endpoints_Public{
				Port: req.Port,
			},
		},
	}
}

func convertCreateClusterReqToDedicatedCluster(
	req *centralapi.CreateClusterReq,
) (*openapidedicatedv1beta1.Cluster, bizerr.BizError) {
	tidbSetting := &openapidedicatedv1beta1.Cluster_TidbNodeSetting{
		TidbNodeGroups: make([]*openapidedicatedv1beta1.TidbNodeGroup, 0),
	}
	tikvSetting := &openapidedicatedv1beta1.Cluster_StorageNodeSetting{}
	var tiflashSetting *openapidedicatedv1beta1.Cluster_StorageNodeSetting

	for _, component := range req.GetComponents() {
		switch component.ComponentType {
		case centralapi.ComponentType_COMPONENT_TYPE_TIDB:
			if component.GetReplicas() <= 0 {
				return nil, bizerr.New(bizerr.ErrCodeInvalidNodeQuantity, "tidb node_quantity is invalid")
			}

			if component.GetNodeProfileName() == "" {
				return nil, bizerr.New(bizerr.ErrCodeInvalidNodeSize, "tidb node_size is required")
			}

			tidbSetting.NodeSpecKey = component.GetNodeProfileName()
			tidbSetting.TidbNodeGroups = append(tidbSetting.TidbNodeGroups, &openapidedicatedv1beta1.TidbNodeGroup{
				NodeCount: component.GetReplicas(),
			})

		case centralapi.ComponentType_COMPONENT_TYPE_TIKV:
			if component.GetReplicas() <= 0 {
				return nil, bizerr.New(bizerr.ErrCodeInvalidNodeQuantity, "tikv node_quantity is invalid")
			}

			if component.GetNodeProfileName() == "" {
				return nil, bizerr.New(bizerr.ErrCodeInvalidNodeSize, "tikv node_size is required")
			}

			if component.GetStorageSizeGi() <= 0 {
				return nil, bizerr.New(bizerr.ErrCodeInvalidNodeSize, "tikv node_storage_size is invalid")
			}
			tikvSetting.NodeSpecKey = component.GetNodeProfileName()
			tikvSetting.NodeCount = component.GetReplicas()
			tikvSetting.StorageSizeGi = component.GetStorageSizeGi()
			if component.GetStorageType() != "" {
				openapiStorageType, err := enum.FromString[openapidedicatedv1beta1.Cluster_StorageNodeSetting_StorageType](
					component.GetStorageType(),
				)
				if err != nil {
					return nil, bizerr.New(bizerr.ErrCodeParams, "invalid storage type: %s", component.GetStorageType())
				}
				tikvSetting.StorageType = openapidedicatedv1beta1.Cluster_StorageNodeSetting_StorageType(
					openapiStorageType,
				)
			}

		case centralapi.ComponentType_COMPONENT_TYPE_TIFLASH:
			if component.GetReplicas() < 0 {
				return nil, bizerr.New(bizerr.ErrCodeInvalidNodeQuantity, "tiflash node_quantity is invalid")
			}

			if component.GetStorageSizeGi() < 0 {
				return nil, bizerr.New(bizerr.ErrCodeInvalidNodeSize, "tiflash node_storage_size is invalid")
			}
			tiflashSetting = &openapidedicatedv1beta1.Cluster_StorageNodeSetting{
				NodeCount:     component.GetReplicas(),
				NodeSpecKey:   component.GetNodeProfileName(),
				StorageSizeGi: component.GetStorageSizeGi(),
			}

			if component.GetStorageType() != "" {
				openapiStorageType, err := enum.FromString[openapidedicatedv1beta1.Cluster_StorageNodeSetting_StorageType](
					component.GetStorageType(),
				)
				if err != nil {
					return nil, bizerr.New(bizerr.ErrCodeParams, "invalid storage type: %s", component.GetStorageType())
				}
				tiflashSetting.StorageType = openapidedicatedv1beta1.Cluster_StorageNodeSetting_StorageType(
					openapiStorageType,
				)
			}
		}
	}

	return &openapidedicatedv1beta1.Cluster{
		DisplayName:        req.Name,
		RootPassword:       req.RootPassword,
		Port:               req.Port,
		TidbNodeSetting:    tidbSetting,
		TikvNodeSetting:    tikvSetting,
		TiflashNodeSetting: tiflashSetting,
		RegionId:           strings.ToLower(fmt.Sprintf("%s-%s", req.Provider, req.Region)),
		Labels: map[string]string{
			apiutils.ProjectIDLabelKey: strconv.FormatUint(req.ProjectId, 10),
			apiutils.OrgIDLabelKey:     strconv.FormatUint(req.OrgId, 10),
		},
	}, nil
}
