package v1beta

import (
	"cmp"
	"context"
	"fmt"
	"slices"
	"strconv"

	"github.com/gin-gonic/gin"
	openapilegacy "github.com/tidbcloud/idl/pbgen/proto/openapi/cm"
	"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/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"
)

func ListClusters(ctx *gin.Context) {
	req := &openapilegacy.ListClustersOfProjectReq{}

	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
	}

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

	req.ProjectId = projectId

	// nothing to do
	var resp *openapilegacy.ListClustersOfProjectResp

	// deidcated
	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.OpenAPIInternalErrorResp(ctx, err)
		return
	}

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

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

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

	// not in allow list
	resp, err = openapigrpc.ListClustersOfProject(grpc.NewContext(ctx), req)
	if err != nil {
		types.OpenAPIErrorResp(ctx, err)
		return
	}

	// serverless allow all
	serverlessClusers, err := mgmtgrpc.ListServerlessClusters(
		grpc.NewContext(ctx),
		&openapiserverlessv1beta1.ListClustersRequest{
			// PageSize: int32(req.PageSize),
			Filter: fmt.Sprintf("projectId = %d", projectId),
		},
	)
	if err != nil {
		types.OpenAPIErrorResp(ctx, err)
		return
	}
	resp = mergeListClustersResp(grpc.NewContext(ctx), resp, serverlessClusers)
	resp.Items = pagination(resp.Items, page, pageSize)

	types.SuccessResp(ctx, resp)
}

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

	// sort by CreateTimestamp
	slices.SortFunc(r2s.Items, func(a, b *openapilegacy.ClusterItem) int {
		return cmp.Compare(b.CreateTimestamp, a.CreateTimestamp)
	})

	return r2s
}

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

	resp := &openapilegacy.ListClustersOfProjectResp{
		Items: make([]*openapilegacy.ClusterItem, 0, len(r.Clusters)),
		Total: r.TotalSize,
	}

	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,
) *openapilegacy.ListClustersOfProjectResp {
	if r == nil || len(r.Clusters) == 0 {
		return &openapilegacy.ListClustersOfProjectResp{
			Total: uint32(r.GetTotalSize()),
		}
	}

	resp := &openapilegacy.ListClustersOfProjectResp{
		Items: make([]*openapilegacy.ClusterItem, 0, len(r.Clusters)),
		Total: uint32(r.GetTotalSize()),
	}

	for _, cluster := range r.Clusters {
		switch cluster.GetClusterType() {
		case bffclusterv1beta1.Cluster_DEDICATED:
			c, err := convertDedicatedCluster(ctx, cluster.GetDedicatedCluster())
			if err != nil {
				tilog.Warn(ctx, "convert dedicated cluster failed", zap.Any("cluster", cluster), zap.Error(err))
				continue
			}
			resp.Items = append(resp.Items, c)
		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)
		}
	}
	return resp
}

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