package v1beta

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/samber/lo"
	openapilegacy "github.com/tidbcloud/idl/pbgen/proto/openapi/cm"
	"google.golang.org/protobuf/types/known/emptypb"

	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/internal/auth"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc"
	mgmtgrpc "github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc/mgmt"
	openapigrpc "github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc/openapi"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func UpdateCluster(ctx *gin.Context) {
	req := &openapilegacy.UpdateClusterReq{}
	err := types.ShouldBindJSON(ctx, req)
	if err != nil {
		types.OpenAPIInvalidArgumentResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}

	orgId, err := auth.GetOrgIDByHeader(ctx)
	if err != nil {
		types.OpenAPIInvalidArgumentResp(ctx, err)
		return
	}

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

	clusterId, err := strconv.ParseUint(ctx.Param("cluster_id"), 10, 64)
	if err != nil {
		types.OpenAPIInvalidArgumentResp(ctx, fmt.Errorf("invalid cluster id: %w", err))
		return
	}

	req.ProjectId = projectId
	req.ClusterId = clusterId

	if req.GetConfig() == nil {
		types.OpenAPIInvalidArgumentResp(ctx, errors.New("config is required"))
		return
	}

	// serverless can not update with v1beta1 api
	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.OpenAPIInternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		if req.GetConfig().Paused != nil {
			types.OpenAPIInvalidArgumentResp(ctx, fmt.Errorf("%s Tier cluster does not support pause or resume", "Serverless"))
			return
		}
		if req.GetConfig().Components != nil {
			types.OpenAPIInvalidArgumentResp(ctx, errors.New("can't scale Developer Tier cluster"))
			return
		}
		types.OpenAPIInvalidArgumentResp(ctx, errors.New("serverless cluster can not patch"))
		return
	}

	// dedicated cluster
	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		types.OpenAPIInternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		var err error
		switch {
		case req.Config.Paused != nil && *req.Config.Paused:
			// pause
			if req.Config.Components != nil {
				types.OpenAPIWithBizErrorResp(ctx, bizerr.New(bizerr.ErrCodeParams, "paused and components cannot be set at the same time"))
				return
			}
			_, err = mgmtgrpc.PauseDedicatedCluster(grpc.NewContext(ctx), &openapidedicatedv1beta1.PauseClusterRequest{
				ClusterId: strconv.FormatUint(clusterId, 10),
			})
		case req.Config.Paused != nil && !*req.Config.Paused:
			// resume
			if req.Config.Components != nil {
				types.OpenAPIWithBizErrorResp(ctx, bizerr.New(bizerr.ErrCodeParams, "paused and components cannot be set at the same time"))
				return
			}
			_, err = mgmtgrpc.ResumeDedicatedCluster(grpc.NewContext(ctx), &openapidedicatedv1beta1.ResumeClusterRequest{
				ClusterId: strconv.FormatUint(clusterId, 10),
			})
			// scale
		case req.Config.Components != nil:
			scaleReq := &openapidedicatedv1beta1.UpdateClusterRequest_Cluster{
				ClusterId: strconv.FormatUint(clusterId, 10),
			}

			if req.Config.Components.Tidb != nil {
				scaleReq.TidbNodeSetting = &openapidedicatedv1beta1.UpdateClusterRequest_TidbNodeSetting{}
				if req.Config.Components.Tidb.NodeSize != nil {
					scaleReq.TidbNodeSetting.NodeSpecKey = lo.FromPtr(req.Config.Components.Tidb.NodeSize)
				}

				// set tidb node quantity
				var groupId string
				if req.Config.Components.Tidb.AccesspointId != nil {
					groupId = strconv.FormatUint(*req.Config.Components.Tidb.AccesspointId, 10)
				}
				if req.Config.Components.Tidb.NodeQuantity != nil {
					if req.Config.Components.Tidb.GetNodeQuantity() < 0 {
						types.OpenAPIWithBizErrorResp(ctx, bizerr.New(bizerr.ErrCodeInvalidNodeQuantity, " tidb node_quantity is invalid"))
						return
					}

					scaleReq.TidbNodeSetting.TidbNodeGroups = []*openapidedicatedv1beta1.UpdateClusterRequest_TidbNodeSetting_TidbNodeGroup{
						{
							NodeCount:       lo.ToPtr(req.Config.Components.Tidb.GetNodeQuantity()),
							TidbNodeGroupId: groupId,
						},
					}
				}
			}

			if req.Config.Components.Tikv != nil {
				scaleReq.TikvNodeSetting = &openapidedicatedv1beta1.UpdateClusterRequest_StorageNodeSetting{}

				if req.Config.Components.Tikv.NodeSize != nil {
					scaleReq.TikvNodeSetting.NodeSpecKey = lo.FromPtr(req.Config.Components.Tikv.NodeSize)
				}
				if req.Config.Components.Tikv.NodeQuantity != nil {
					if req.Config.Components.Tikv.GetNodeQuantity() < 0 {
						types.OpenAPIWithBizErrorResp(ctx, bizerr.New(bizerr.ErrCodeInvalidNodeQuantity, " tikv node_quantity is invalid"))
						return
					}
					scaleReq.TikvNodeSetting.NodeCount = lo.ToPtr(req.Config.Components.Tikv.GetNodeQuantity())
				}
				if req.Config.Components.Tikv.StorageSizeGib != nil {
					if req.Config.Components.Tikv.GetStorageSizeGib() <= 0 {
						types.OpenAPIWithBizErrorResp(ctx, bizerr.New(bizerr.ErrCodeInvalidNodeSize, " tikv node_storage_size is invalid"))
						return
					}
					scaleReq.TikvNodeSetting.StorageSizeGi = req.Config.Components.Tikv.GetStorageSizeGib()
				}
			}

			if req.Config.Components.Tiflash != nil {
				scaleReq.TiflashNodeSetting = &openapidedicatedv1beta1.UpdateClusterRequest_StorageNodeSetting{}

				if req.Config.Components.Tiflash.NodeSize != nil {
					scaleReq.TiflashNodeSetting.NodeSpecKey = lo.FromPtr(req.Config.Components.Tiflash.NodeSize)
				}
				if req.Config.Components.Tiflash.NodeQuantity != nil {
					if req.Config.Components.Tiflash.GetNodeQuantity() < 0 {
						types.OpenAPIWithBizErrorResp(ctx, bizerr.New(bizerr.ErrCodeInvalidNodeQuantity, " tiflash node_quantity is invalid"))
						return
					}
					scaleReq.TiflashNodeSetting.NodeCount = lo.ToPtr(req.Config.Components.Tiflash.GetNodeQuantity())
				}
				if req.Config.Components.Tiflash.StorageSizeGib != nil {
					if req.Config.Components.Tiflash.GetStorageSizeGib() < 0 {
						types.OpenAPIWithBizErrorResp(ctx, bizerr.New(bizerr.ErrCodeInvalidNodeSize, " tiflash node_storage_size is invalid"))
						return
					}
					scaleReq.TiflashNodeSetting.StorageSizeGi = req.Config.Components.Tiflash.GetStorageSizeGib()
				}
			}
			_, err = mgmtgrpc.UpdateDedicatedCluster(grpc.NewContext(ctx), &openapidedicatedv1beta1.UpdateClusterRequest{Cluster: scaleReq})
		}

		if err != nil {
			types.OpenAPIErrorResp(ctx, err)
			return
		}

		types.SuccessResp(ctx, &emptypb.Empty{})
		return
	}

	// OpenAPI
	resp, err := openapigrpc.UpdateCluster(grpc.NewContext(ctx), req)
	if err != nil {
		types.OpenAPIErrorResp(ctx, err)
		return
	}

	types.SuccessResp(ctx, resp)
}
