package central

import (
	"errors"
	"fmt"
	"strconv"

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

	bffclusterv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/bff/cluster_management/v1beta1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/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 CalClusterFee(ctx *gin.Context) {
	req := &centralapi.CalcClusterFeeReq{}
	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 {
		types.GrpcErrorResp(ctx, errors.New("serverless cluster is not supported"))
		return
	}

	allow, err := apiutils.IsDedicatedAllow(ctx, orgId, projectId)
	if err != nil {
		types.InternalErrorResp(ctx, err)
		return
	}
	if allow {
		bffReq, beer := convertCalReqToBff(req)
		if beer != nil {
			types.BizErrorRequestResp(ctx, beer)
			return
		}

		resp, err := mgmtgrpc.EstimateClusterPrice(grpc.NewContext(ctx), bffReq)
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}
		types.SuccessResp(ctx, &centralapi.CalcClusterFeeResp{
			Costs: lo.Map(
				resp.GetPrices(),
				func(item *bffclusterv1beta1.EstimatePriceResponse_Price, _ int) *centralapi.CalcClusterFeeResp_ComponentCost {
					return &centralapi.CalcClusterFeeResp_ComponentCost{
						ComponentType: cvtToCoreComponentType(item.GetComponentType()),
						Compute:       item.GetComputeDiscountedPrice(),
						Storage:       item.GetStorageDiscountedPrice(),
						RawCompute:    item.GetComputeOriginalPrice(),
						RawStorage:    item.GetStorageOriginalPrice(),
					}
				},
			),
			BaseResp: &centralapi.BaseResp{
				ErrCode: int64(bizerr.ErrCodeOK),
				ErrMsg:  "",
			},
		})
		return
	}

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

func cvtToCoreComponentType(t openapidedicatedv1beta1.ComponentType) (ct centralapi.ComponentType) {
	switch t {
	case openapidedicatedv1beta1.ComponentType_TIDB:
		ct = centralapi.ComponentType_COMPONENT_TYPE_TIDB
	case openapidedicatedv1beta1.ComponentType_TIKV:
		ct = centralapi.ComponentType_COMPONENT_TYPE_TIKV
	case openapidedicatedv1beta1.ComponentType_TIFLASH:
		ct = centralapi.ComponentType_COMPONENT_TYPE_TIFLASH
	case openapidedicatedv1beta1.ComponentType_PD:
		ct = centralapi.ComponentType_COMPONENT_TYPE_PD
	default:
		ct = centralapi.ComponentType_COMPONENT_TYPE_UNSPECIFIED
	}
	return ct
}

func convertCalReqToBff(req *centralapi.CalcClusterFeeReq) (*bffclusterv1beta1.EstimatePriceRequest, bizerr.BizError) {
	res := &bffclusterv1beta1.EstimatePriceRequest{
		ProjectId: strconv.FormatUint(req.GetProjectId(), 10),
		RegionId:  fmt.Sprintf("%s-%s", req.GetProvider(), req.GetRegion()),
		Paused:    req.GetIsClusterPaused(),
		TidbNodeSetting: &openapidedicatedv1beta1.Cluster_TidbNodeSetting{
			TidbNodeGroups: make([]*openapidedicatedv1beta1.TidbNodeGroup, 0),
		},
		TikvNodeSetting:    &openapidedicatedv1beta1.Cluster_StorageNodeSetting{},
		TiflashNodeSetting: &openapidedicatedv1beta1.Cluster_StorageNodeSetting{},
		// promoted=true means use old storage sku. [https://pingcap.feishu.cn/wiki/SWfnwtCBZid1bdk5a7FcamQcn5g#share-E8Uddcpw2onjx1xnUVuc9pcanSh](price promotion until 2025/May)
		// core api always use old storage sku, so we need to set it to true.
		Promoted: true,
	}

	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")
			}

			res.TidbNodeSetting.NodeSpecKey = component.GetNodeProfileName()
			res.TidbNodeSetting.TidbNodeGroups = append(
				res.TidbNodeSetting.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")
			}
			res.TikvNodeSetting.NodeSpecKey = component.GetNodeProfileName()
			res.TikvNodeSetting.NodeCount = component.GetReplicas()
			res.TikvNodeSetting.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())
				}
				res.TikvNodeSetting.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")
			}
			res.TiflashNodeSetting = &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())
				}
				res.TiflashNodeSetting.StorageType = openapidedicatedv1beta1.Cluster_StorageNodeSetting_StorageType(
					openapiStorageType,
				)
			}
		}
	}

	return res, nil
}
