package central

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	centralapi "github.com/tidbcloud/core-svc/idl/pbgen/proto/central"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

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

func GetTiDBNodes(ctx *gin.Context) {
	req := &centralapi.TiDBNodesReq{}

	// 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
	}

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

	page, perPage, err := apiutils.GetPageAndPageSize(ctx)
	if err != nil {
		tilog.Warn(ctx.Request.Context(), "get page and page size failed", zap.Error(err))
		types.BadRequestResp(ctx, fmt.Errorf("page or page_size is invalid: %w", err))
		return
	}

	// set cluster org and project id
	req.ClusterId = clusterId
	req.OrgId = orgId
	req.ProjectId = projectId
	req.Page = page
	req.PerPage = perPage

	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		if ok {
			types.GrpcErrorResp(ctx, err)
			return
		}
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		resp := &centralapi.TiDBNodes{
			Total: 1,
			Items: []*centralapi.TiDBNode{
				{
					Name:   ShareNodeName,
					Status: centralapi.TiDBNode_AVAILABLE,
					Compute: &centralapi.Compute{
						Cpu:      0,
						MemoryMi: 0,
					},
					Type: centralapi.TiDBNode_COMPUTING,
				},
			},
		}
		types.SuccessResp(ctx, resp)
		return
	}

	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		if ok {
			types.GrpcErrorResp(ctx, err)
			return
		}
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		nodeInfo, err := mgmtgrpc.GetDedicatedClusterNodeInfo(grpc.NewContext(ctx), &openapidedicatedv1beta1.ListNodeInstancesRequest{
			ClusterId:     strconv.FormatUint(clusterId, 10),
			ComponentType: openapidedicatedv1beta1.ComponentType_TIDB,
			PageSize:      int32(req.GetPerPage()),
			Skip:          int32((req.GetPage() - 1) * req.GetPerPage()),
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		if nodeInfo == nil || nodeInfo.GetTotalSize() == 0 {
			types.SuccessResp(ctx, &centralapi.TiDBNodes{})
			return
		}

		resp := &centralapi.TiDBNodes{
			Total: nodeInfo.GetTotalSize(),
		}
		for _, node := range nodeInfo.GetNodeInstances() {
			var tidbGroupNameSuffix string
			resp.Items = append(resp.Items, &centralapi.TiDBNode{
				Name:   fmt.Sprintf("%s%s", strings.TrimPrefix(node.GetInstanceId(), "db-"), tidbGroupNameSuffix),
				Status: cvtTiDBNodeState(node.State),
				Az:     node.AvailabilityZone,
				Compute: &centralapi.Compute{
					Cpu:      float32(node.GetVCpu()),
					MemoryMi: uint32(node.GetMemorySizeGi() * 1024),
				},
				Type: centralapi.TiDBNode_COMPUTING,
			})
		}
		types.SuccessResp(ctx, resp)
		return
	}

	// core-portal
	resp, err := coregrpc.GetTiDBNodes(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}

	types.SuccessResp(ctx, resp)
}

func GetTiKVNodes(ctx *gin.Context) {
	req := &centralapi.TiKVNodesReq{}

	// 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
	}

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

	page, perPage, err := apiutils.GetPageAndPageSize(ctx)
	if err != nil {
		tilog.Warn(ctx.Request.Context(), "get page and page size failed", zap.Error(err))
		types.BadRequestResp(ctx, fmt.Errorf("page or page_size is invalid: %w", err))
		return
	}

	// set cluster org and project id
	req.ClusterId = clusterId
	req.OrgId = orgId
	req.ProjectId = projectId
	req.Page = page
	req.PerPage = perPage

	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		if ok {
			types.GrpcErrorResp(ctx, err)
			return
		}
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		resp := &centralapi.TiKVNodes{
			Total: 1,
			Items: []*centralapi.TiKVNode{
				{
					Name:      ShareNodeName,
					StorageGi: 0,
					Status:    centralapi.TiKVNode_AVAILABLE,
					Compute: &centralapi.Compute{
						Cpu:      0,
						MemoryMi: 0,
					},
					Type: centralapi.TiKVNode_STORAGE,
				},
			},
		}
		types.SuccessResp(ctx, resp)
		return
	}

	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		if ok {
			types.GrpcErrorResp(ctx, err)
			return
		}
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		nodeInfo, err := mgmtgrpc.GetDedicatedClusterNodeInfo(grpc.NewContext(ctx), &openapidedicatedv1beta1.ListNodeInstancesRequest{
			ClusterId:     strconv.FormatUint(clusterId, 10),
			ComponentType: openapidedicatedv1beta1.ComponentType_TIKV,
			PageSize:      int32(req.GetPerPage()),
			Skip:          int32((req.GetPage() - 1) * req.GetPerPage()),
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		if nodeInfo == nil || nodeInfo.GetTotalSize() == 0 {
			types.SuccessResp(ctx, &centralapi.TiFlashNodes{})
			return
		}

		resp := &centralapi.TiKVNodes{
			Total: nodeInfo.GetTotalSize(),
		}
		for _, node := range nodeInfo.GetNodeInstances() {
			resp.Items = append(resp.Items, &centralapi.TiKVNode{
				Name:   strings.TrimPrefix(node.GetInstanceId(), "db-"),
				Status: cvtTiKVNodeState(node.State),
				Az:     node.AvailabilityZone,
				Compute: &centralapi.Compute{
					Cpu:      float32(node.GetVCpu()),
					MemoryMi: uint32(node.GetMemorySizeGi() * 1024),
				},
				StorageGi: uint32(node.GetStorageSizeGi()),
				Type:      centralapi.TiKVNode_STORAGE,
			})
		}
		types.SuccessResp(ctx, resp)
		return
	}

	// core-portal
	resp, err := coregrpc.GetTiKVNodes(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, resp)
}

func GetTiFlashNodes(ctx *gin.Context) {
	req := &centralapi.TiFlashNodesReq{}

	// 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
	}

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

	ctx.Query("page")

	page, perPage, err := apiutils.GetPageAndPageSize(ctx)
	if err != nil {
		tilog.Warn(ctx.Request.Context(), "get page and page size failed", zap.Error(err))
		types.BadRequestResp(ctx, fmt.Errorf("page or page_size is invalid: %w", err))
		return
	}

	// set cluster org and project id
	req.ClusterId = clusterId
	req.OrgId = orgId
	req.ProjectId = projectId
	req.Page = page
	req.PerPage = perPage

	if ok, err := apiutils.IsNewServerlessCluster(ctx, clusterId, projectId, orgId); err != nil {
		if ok {
			types.GrpcErrorResp(ctx, err)
			return
		}
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		resp := &centralapi.TiFlashNodes{
			Total: 1,
			Items: []*centralapi.TiFlashNode{
				{
					Name:      ShareNodeName,
					StorageGi: 0,
					Status:    centralapi.TiFlashNode_AVAILABLE,
					Compute: &centralapi.Compute{
						Cpu:      0,
						MemoryMi: 0,
					},
					Type: centralapi.TiFlashNode_STORAGE,
				},
			},
		}
		types.SuccessResp(ctx, resp)
		return
	}

	if ok, err := apiutils.IsNewDedicatedCluster(ctx, clusterId, projectId, orgId); err != nil {
		if ok {
			types.GrpcErrorResp(ctx, err)
			return
		}
		types.InternalErrorResp(ctx, fmt.Errorf("check cluster failed: %w", err))
		return
	} else if ok {
		nodeInfo, err := mgmtgrpc.GetDedicatedClusterNodeInfo(grpc.NewContext(ctx), &openapidedicatedv1beta1.ListNodeInstancesRequest{
			ClusterId:     strconv.FormatUint(clusterId, 10),
			ComponentType: openapidedicatedv1beta1.ComponentType_TIFLASH,
			PageSize:      int32(req.GetPerPage()),
			Skip:          int32((req.GetPage() - 1) * req.GetPerPage()),
		})
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		if nodeInfo == nil || nodeInfo.GetTotalSize() == 0 {
			types.SuccessResp(ctx, &centralapi.TiFlashNodes{})
			return
		}

		resp := &centralapi.TiFlashNodes{
			Total: nodeInfo.GetTotalSize(),
		}
		for _, node := range nodeInfo.GetNodeInstances() {
			resp.Items = append(resp.Items, &centralapi.TiFlashNode{
				Name:   strings.TrimPrefix(node.GetInstanceId(), "db-"),
				Status: cvtTiFlashNodeState(node.GetState()),
				Az:     node.AvailabilityZone,
				Compute: &centralapi.Compute{
					Cpu:      float32(node.GetVCpu()),
					MemoryMi: uint32(node.GetMemorySizeGi() * 1024),
				},
				StorageGi: uint32(node.GetStorageSizeGi()),
				Type:      centralapi.TiFlashNode_STORAGE,
			})
		}
		types.SuccessResp(ctx, resp)
		return
	}

	// core-portal
	resp, err := coregrpc.GetTiFlashNodes(grpc.NewContext(ctx), req)
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}

	types.SuccessResp(ctx, resp)
}

func cvtTiDBNodeState(state openapidedicatedv1beta1.NodeInstance_State) centralapi.TiDBNode_Status {
	switch state {
	case openapidedicatedv1beta1.NodeInstance_AVAILABLE:
		return centralapi.TiDBNode_AVAILABLE
	case openapidedicatedv1beta1.NodeInstance_UNAVAILABLE:
		return centralapi.TiDBNode_UNAVAILABLE
	case openapidedicatedv1beta1.NodeInstance_CREATING:
		return centralapi.TiDBNode_CREATING
	case openapidedicatedv1beta1.NodeInstance_DELETING:
		return centralapi.TiDBNode_DELETING
	}
	return centralapi.TiDBNode_UNAVAILABLE
}

func cvtTiKVNodeState(state openapidedicatedv1beta1.NodeInstance_State) centralapi.TiKVNode_Status {
	switch state {
	case openapidedicatedv1beta1.NodeInstance_AVAILABLE:
		return centralapi.TiKVNode_AVAILABLE
	case openapidedicatedv1beta1.NodeInstance_UNAVAILABLE:
		return centralapi.TiKVNode_UNAVAILABLE
	case openapidedicatedv1beta1.NodeInstance_CREATING:
		return centralapi.TiKVNode_CREATING
	case openapidedicatedv1beta1.NodeInstance_DELETING:
		return centralapi.TiKVNode_DELETING
	}
	return centralapi.TiKVNode_UNAVAILABLE
}

func cvtTiFlashNodeState(state openapidedicatedv1beta1.NodeInstance_State) centralapi.TiFlashNode_Status {
	switch state {
	case openapidedicatedv1beta1.NodeInstance_AVAILABLE:
		return centralapi.TiFlashNode_AVAILABLE
	case openapidedicatedv1beta1.NodeInstance_UNAVAILABLE:
		return centralapi.TiFlashNode_UNAVAILABLE
	case openapidedicatedv1beta1.NodeInstance_CREATING:
		return centralapi.TiFlashNode_CREATING
	case openapidedicatedv1beta1.NodeInstance_DELETING:
		return centralapi.TiFlashNode_DELETING
	}
	return centralapi.TiFlashNode_UNAVAILABLE
}
