package central

import (
	"context"
	"fmt"
	"slices"
	"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"

	bffclusterv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/bff/cluster_management/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"
)

func ListClusters(ctx *gin.Context) {
	req := &centralapi.ClusterListReq{
		Filter: &centralapi.ClusterListReq_Filter{},
	}

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

	if ctx.Query("filter.is_serverless") != "" {
		isServerless, err := strconv.ParseBool(ctx.Query("filter.is_serverless"))
		if err != nil {
			types.BadRequestResp(ctx, fmt.Errorf("filter.is_serverless: %w", err))
			return
		}
		req.Filter.IsServerless = &isServerless
	}

	if ctx.Query("filter.statuses") != "" {
		for status := range strings.SplitSeq(ctx.Query("filter.statuses"), ",") {
			if clusterStatus, ok := centralapi.Cluster_Status_value[status]; ok {
				req.Filter.Statuses = append(req.Filter.Statuses, centralapi.Cluster_Status(clusterStatus))
			}
		}
	}

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

	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
	}

	resp := &centralapi.Clusters{}

	// if dedicated allow, use dedicated api to list clusters
	// use bff api to list clusters
	allow, err := apiutils.IsDedicatedAllow(ctx, orgId, projectId)
	if err != nil {
		tilog.Warn(ctx.Request.Context(), "check dedicated allow org id failed", zap.Error(err))
		types.InternalErrorResp(ctx, err)
		return
	}

	if allow {
		listClustersReq := &bffclusterv1beta1.ListClustersRequest{
			Skip:      int32((page - 1) * perPage),
			PageSize:  int32(perPage),
			PageToken: "",
			ProjectId: strconv.FormatUint(projectId, 10),
		}

		// if is_serverless is not nil, represent is_serverless filter is set, otherwise, list all clusters
		if req.Filter.IsServerless != nil {
			if *req.Filter.IsServerless {
				// if is_serverless is true, only list serverless cluster
				listClustersReq.ClusterType = bffclusterv1beta1.Cluster_SERVERLESS
			} else {
				// if is_serverless is false, only list dedicated cluster
				listClustersReq.ClusterType = bffclusterv1beta1.Cluster_DEDICATED
			}
		}

		// new dedicated cluster
		bffClusters, err := mgmtgrpc.ListClusters(grpc.NewContext(ctx), listClustersReq)
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}

		resp = convertListBffClustersResp(grpc.NewContext(ctx), bffClusters)
		types.SuccessResp(ctx, resp)
		return
	}

	// core-portal
	// if is_serverless is nil or false, list dedicated clusters, otherwise don't need to list dedicated clusters from core-portal
	if req.Filter.IsServerless == nil || !*req.Filter.IsServerless {
		resp, err = coregrpc.ListClusters(grpc.NewContext(ctx), req)
		if err != nil {
			types.GrpcErrorResp(ctx, err)
			return
		}
	}

	// serverless allow all
	if req.Filter.IsServerless == nil || *req.Filter.IsServerless {
		serverlessClusers, err := mgmtgrpc.ListServerlessClusters(
			grpc.NewContext(ctx),
			&openapiserverlessv1beta1.ListClustersRequest{
				// PageSize:  int32(req.PerPage),
				Filter: fmt.Sprintf("projectId = %d", projectId),
			},
		)
		if err != nil {
			tilog.Warn(
				ctx.Request.Context(),
				"list serverless clusters failed",
				zap.Any("clusters", req),
				zap.Error(err),
			)
			types.GrpcErrorResp(ctx, err)
			return
		}
		resp = mergeListClustersResp(ctx.Request.Context(), resp, serverlessClusers)
	}

	// pagination
	// resp.Items has all the clusters
	resp.Items = pagination(resp.Items, page, perPage)

	types.SuccessResp(ctx, resp)
}

func mergeListClustersResp(
	ctx context.Context,
	r1 *centralapi.Clusters,
	r2 *openapiserverlessv1beta1.ListClustersResponse,
) *centralapi.Clusters {
	r2s := convertListServerlessClustersResp(ctx, r2)
	r2s.Total += r1.Total
	r2s.Items = append(r2s.Items, r1.Items...)

	// sort by CreatedAt
	slices.SortFunc(r2s.Items, func(a, b *centralapi.Cluster) int {
		ca, cb := a.CreatedAt.AsTime(), b.CreatedAt.AsTime()
		if ca.After(cb) {
			return -1
		}
		if ca.Before(cb) {
			return 1
		}
		return 0
	})

	return r2s
}

func convertListServerlessClustersResp(
	ctx context.Context,
	r *openapiserverlessv1beta1.ListClustersResponse,
) *centralapi.Clusters {
	if r == nil || len(r.Clusters) == 0 {
		return &centralapi.Clusters{}
	}

	resp := &centralapi.Clusters{
		Items: make([]*centralapi.Cluster, 0, len(r.Clusters)),
		Total: int32(len(r.Clusters)),
	}

	for _, cluster := range r.Clusters {
		c, err := convertServerlessCluster(cluster)
		if err != nil {
			tilog.Warn(ctx, "convert serverless cluster failed", zap.Any("cluster", cluster), zap.Error(err))
			continue
		}
		resp.Items = append(resp.Items, c)
	}

	return resp
}

func convertListBffClustersResp(ctx context.Context, r *bffclusterv1beta1.ListClustersResponse) *centralapi.Clusters {
	if r == nil || len(r.Clusters) == 0 {
		return &centralapi.Clusters{
			Total: r.GetTotalSize(),
		}
	}

	resp := &centralapi.Clusters{
		Items: make([]*centralapi.Cluster, 0, len(r.Clusters)),
		Total: r.GetTotalSize(),
	}

	for _, cluster := range r.Clusters {
		switch cluster.ClusterType {
		case bffclusterv1beta1.Cluster_SERVERLESS:
			c, err := convertServerlessCluster(cluster.GetServerlessCluster())
			if err != nil {
				tilog.Warn(ctx, "convert serverless cluster failed", zap.Any("cluster", cluster), zap.Error(err))
				continue
			}
			resp.Items = append(resp.Items, c)
		case bffclusterv1beta1.Cluster_DEDICATED:
			c, err := convertDedicatedCluster(cluster.GetDedicatedCluster())
			if err != nil {
				tilog.Warn(ctx, "convert serverless cluster failed", zap.Any("cluster", cluster), zap.Error(err))
				continue
			}
			resp.Items = append(resp.Items, c)
		}
	}

	return resp
}

func pagination(items []*centralapi.Cluster, page, pageSize uint32) []*centralapi.Cluster {
	if page == 0 || pageSize == 0 {
		return items
	}
	start := (page - 1) * pageSize
	end := page * pageSize
	if start >= uint32(len(items)) {
		return []*centralapi.Cluster{}
	}
	if end > uint32(len(items)) {
		end = uint32(len(items))
	}
	return items[start:end]
}
