package cluster

import (
	"context"
	"errors"
	"slices"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"gorm.io/gorm"

	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

const (
	// open api max page size is 100
	// for core-proxy, will support 1000
	maxPageSize = 1000
)

func (c *serverImpl) ListClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.ListClustersRequest,
) (*tidbmgmtservicev1.ListClustersResponse, error) {
	count, err := c.clusterRepo.CountClusterByFilter(ctx, cvtFilter(request.Filter))
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to count clusters")
	}

	if count == 0 {
		return &tidbmgmtservicev1.ListClustersResponse{
			Clusters: []*tidbmgmtservicev1.Cluster{},
			Total:    0,
		}, nil
	}

	page := int(request.Page)
	if page == 0 {
		page = 1
	}

	pageSize := int(request.PageSize)
	if pageSize == 0 {
		pageSize = maxPageSize
	}

	if pageSize > maxPageSize {
		return nil, bizerr.New(bizerr.ErrCodeParams, "page size is too large")
	}

	clusters, err := c.clusterRepo.QueryClusterByFilterAndPage(
		ctx,
		cvtFilter(request.Filter),
		(page-1)*pageSize,
		pageSize,
	)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query clusters")
	}

	serverlessClusterIds := make([]uint64, 0, request.PageSize/2)
	dedicatedClusterIds := make([]uint64, 0, request.PageSize/2)
	nextgenClusterIds := make([]uint64, 0, request.PageSize/2)
	for _, cluster := range clusters {
		switch cluster.DeployType {
		case entity.DeployTypeServerless:
			serverlessClusterIds = append(serverlessClusterIds, cluster.ClusterId)
		case entity.DeployTypeDedicated:
			dedicatedClusterIds = append(dedicatedClusterIds, cluster.ClusterId)
		case entity.DeployTypeNextGen:
			// NextGen clusters are not supported in this API
			nextgenClusterIds = append(nextgenClusterIds, cluster.ClusterId)
		default:
			tilog.Warn(ctx, "unknown deploy type", zap.Any("cluster", cluster))
		}
	}

	// list serverless cluster
	var serverlessCluster *tidbmgmtservicev1.ListClustersResponse
	if len(serverlessClusterIds) > 0 {
		serverlessFilter, ok := proto.Clone(request.GetFilter()).(*tidbmgmtservicev1.ClusterFilter)
		if !ok {
			return nil, bizerror.NewInternal(errors.New("failed to clone filter"), "failed to clone filter")
		}
		serverlessFilter.ClusterType = tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS.Enum()
		serverlessFilter.ClusterIds = serverlessClusterIds

		serverlessCluster, err = c.listServerlessClusters(
			ctx,
			&tidbmgmtservicev1.ListClustersRequest{Filter: serverlessFilter, Page: 0, PageSize: request.PageSize},
		)
		if err != nil {
			return nil, err
		}
	}

	// list dedicated cluster
	var dedicatedCluster *tidbmgmtservicev1.ListClustersResponse
	if len(dedicatedClusterIds) > 0 {
		dedicatedFilter, ok := proto.Clone(request.GetFilter()).(*tidbmgmtservicev1.ClusterFilter)
		if !ok {
			return nil, bizerror.NewInternal(errors.New("failed to clone filter"), "failed to clone filter")
		}

		// original comment: "only full view will get cluster endpoint info, now only support full view"
		// Do not remove this compatibility logic directly: some upper-layer callers may rely on getting full view
		// without explicitly passing FULL. To reduce that regression risk, we add allow_non_full_view as an opt-in
		// escape hatch for callers that really need dedicated basic view.
		if dedicatedFilter.GetView() != tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL &&
			!dedicatedFilter.GetAllowNonFullView() {
			dedicatedFilter.View = tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_FULL
		}

		dedicatedFilter.ClusterType = tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED.Enum()
		dedicatedFilter.ClusterIds = dedicatedClusterIds
		dedicatedCluster, err = c.listDedicatedClusters(
			ctx,
			&tidbmgmtservicev1.ListClustersRequest{Filter: dedicatedFilter, Page: 0, PageSize: request.PageSize},
		)
		if err != nil {
			return nil, err
		}
	}

	var nextgenCluster *tidbmgmtservicev1.ListClustersResponse
	if len(nextgenClusterIds) > 0 {
		nextgenFilter, ok := proto.Clone(request.GetFilter()).(*tidbmgmtservicev1.ClusterFilter)
		if !ok {
			return nil, bizerror.NewInternal(errors.New("failed to clone filter"), "failed to clone filter")
		}
		nextgenFilter.ClusterType = tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN.Enum()
		nextgenFilter.ClusterIds = nextgenClusterIds
		nextgenCluster, err = c.listNextgenClusters(
			ctx,
			&tidbmgmtservicev1.ListClustersRequest{Filter: nextgenFilter, Page: 0, PageSize: request.PageSize},
		)
		if err != nil {
			return nil, err
		}
	}

	resp := &tidbmgmtservicev1.ListClustersResponse{
		Clusters: append(
			append(serverlessCluster.GetClusters(), dedicatedCluster.GetClusters()...),
			nextgenCluster.GetClusters()...),
		Total: uint32(count),
	}

	// default sort by create time asc
	// TODO: add sort by request
	slices.SortFunc(resp.Clusters, func(a, b *tidbmgmtservicev1.Cluster) int {
		ca, cb := a.CreateTime.AsTime(), b.CreateTime.AsTime()
		if ca.Before(cb) {
			return -1
		}
		if ca.After(cb) {
			return 1
		}
		return 0
	})

	return resp, nil
}

func (c *serverImpl) CountClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.CountClustersRequest,
) (*tidbmgmtservicev1.CountClustersResponse, error) {
	serverlessCount, err := c.countServerlessClusters(
		ctx,
		&tidbmgmtservicev1.CountClustersRequest{Filter: request.Filter},
	)
	if err != nil {
		return nil, err
	}

	dedicatedCount, err := c.countDedicatedClusters(
		ctx,
		&tidbmgmtservicev1.CountClustersRequest{Filter: request.Filter},
	)
	if err != nil {
		return nil, err
	}

	nextgenCount, err := c.countNextgenClusters(
		ctx,
		&tidbmgmtservicev1.CountClustersRequest{Filter: request.Filter},
	)

	if err != nil {
		return nil, err
	}

	return &tidbmgmtservicev1.CountClustersResponse{
		Count: serverlessCount.Count + dedicatedCount.Count + nextgenCount.Count,
	}, nil
}

func (c *serverImpl) listServerlessClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.ListClustersRequest,
) (*tidbmgmtservicev1.ListClustersResponse, error) {
	// the first page is 1
	if request.Page == 0 {
		request.Page = 1
	}

	if request.PageSize > maxPageSize || request.PageSize == 0 {
		request.PageSize = maxPageSize
	}

	if request.Filter != nil && request.Filter.ClusterType != nil &&
		*request.Filter.ClusterType != tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS {
		tilog.Info(ctx, "skip list serverless cluster", zap.Any("filter", request.Filter))
		return &tidbmgmtservicev1.ListClustersResponse{
			Clusters: []*tidbmgmtservicev1.Cluster{},
			Total:    0,
		}, nil
	}

	filter := cvtFilter(request.Filter)
	filter.DeployType = entity.DeployTypeServerless

	// count cluster from domain service
	count, err := c.serverlessClusterSvc.CountClusters(ctx, filter)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to count serverless clusters")
	}

	if count == 0 {
		return &tidbmgmtservicev1.ListClustersResponse{
			Clusters: []*tidbmgmtservicev1.Cluster{},
			Total:    0,
		}, nil
	}

	// list cluster from domain
	clusters, err := c.serverlessClusterSvc.ListClusters(
		ctx,
		filter,
		&common.Page{Offset: int64(request.Page-1) * int64(request.PageSize), PageSize: int64(request.PageSize)},
	)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query serverless clusters")
	}

	clusterIds := lo.FlatMap(clusters, func(cluster *entity.Cluster, _ int) []uint64 {
		return []uint64{cluster.ClusterId}
	})

	// Fetch custom status
	statusMap, err := c.customStatusSvc.ListClusterCustomStatus(ctx, clusterIds, nil, false)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) && !errors.Is(err, constant.ErrClusterNotFound) {
		return nil, bizerror.NewInternal(err, "failed to get cluster custom status record")
	}

	resp := &tidbmgmtservicev1.ListClustersResponse{
		Clusters: []*tidbmgmtservicev1.Cluster{},
		Total:    uint32(count),
	}

	resp.Clusters = lo.Map(clusters, func(cluster *entity.Cluster, _ int) *tidbmgmtservicev1.Cluster {
		return cvtToMgmtCluster(cluster, statusMap[cluster.ClusterId])
	})

	// default sort by create time asc
	// TODO: add sort by request
	// sort.Slice(resp.Clusters, func(i, j int) bool {
	// 	return resp.Clusters[i].CreateTime.AsTime().Before(resp.Clusters[j].CreateTime.AsTime())
	// })

	return resp, nil
}

func (c *serverImpl) listDedicatedClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.ListClustersRequest,
) (*tidbmgmtservicev1.ListClustersResponse, error) {
	// the first page is 1
	if request.Page == 0 {
		request.Page = 1
	}

	if request.PageSize > maxPageSize || request.PageSize == 0 {
		request.PageSize = maxPageSize
	}

	if request.Filter != nil && request.Filter.ClusterType != nil &&
		*request.Filter.ClusterType != tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED {
		tilog.Info(ctx, "skip list dedicated cluster", zap.Any("filter", request.Filter))
		return &tidbmgmtservicev1.ListClustersResponse{
			Clusters: []*tidbmgmtservicev1.Cluster{},
			Total:    0,
		}, nil
	}

	filter := cvtFilter(request.Filter)
	filter.DeployType = entity.DeployTypeDedicated
	count, err := c.dedicatedClusterSvc.CountClusters(ctx, filter)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to count dedicated clusters")
	}

	if count == 0 {
		return &tidbmgmtservicev1.ListClustersResponse{
			Clusters: []*tidbmgmtservicev1.Cluster{},
			Total:    0,
		}, nil
	}

	// list cluster from domain
	clusters, err := c.dedicatedClusterSvc.ListClusters(
		ctx,
		filter,
		&common.Page{Offset: int64(request.Page-1) * int64(request.PageSize), PageSize: int64(request.PageSize)},
	)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query dedicated clusters")
	}

	clusterIds := lo.FlatMap(clusters, func(cluster *entity.Cluster, _ int) []uint64 {
		return []uint64{cluster.ClusterId}
	})

	// Fetch custom status
	statusMap, err := c.customStatusSvc.ListClusterCustomStatus(ctx, clusterIds, nil, false)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) && !errors.Is(err, constant.ErrClusterNotFound) {
		return nil, bizerror.NewInternal(err, "failed to get cluster custom status record")
	}

	resp := &tidbmgmtservicev1.ListClustersResponse{
		Clusters: []*tidbmgmtservicev1.Cluster{},
		Total:    uint32(count),
	}

	resp.Clusters = lo.Map(clusters, func(cluster *entity.Cluster, _ int) *tidbmgmtservicev1.Cluster {
		return cvtToMgmtCluster(cluster, statusMap[cluster.ClusterId])
	})

	// default sort by create time asc
	// TODO: add sort by request
	// sort.Slice(resp.Clusters, func(i, j int) bool {
	// 	return resp.Clusters[i].CreateTime.AsTime().Before(resp.Clusters[j].CreateTime.AsTime())
	// })

	return resp, nil
}

func (c *serverImpl) listNextgenClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.ListClustersRequest,
) (*tidbmgmtservicev1.ListClustersResponse, error) {
	// the first page is 1
	if request.Page == 0 {
		request.Page = 1
	}

	if request.PageSize > maxPageSize || request.PageSize == 0 {
		request.PageSize = maxPageSize
	}

	if request.Filter != nil && request.Filter.ClusterType != nil &&
		*request.Filter.ClusterType != tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN {
		tilog.Info(ctx, "skip list next gen cluster", zap.Any("filter", request.Filter))
		return &tidbmgmtservicev1.ListClustersResponse{
			Clusters: []*tidbmgmtservicev1.Cluster{},
			Total:    0,
		}, nil
	}

	filter := cvtFilter(request.Filter)
	filter.DeployType = entity.DeployTypeNextGen
	count, berr := c.nextgenClusterSvc.CountClusters(ctx, filter)
	if berr != nil {
		return nil, bizerror.NewInternal(berr, "failed to count nextgen clusters")
	}

	if count == 0 {
		return &tidbmgmtservicev1.ListClustersResponse{
			Clusters: []*tidbmgmtservicev1.Cluster{},
			Total:    0,
		}, nil
	}

	// list cluster from domain
	clusters, berr := c.nextgenClusterSvc.ListClusters(
		ctx,
		filter,
		&common.Page{Offset: int64(request.Page-1) * int64(request.PageSize), PageSize: int64(request.PageSize)},
	)
	if berr != nil {
		return nil, bizerror.NewInternal(berr, "failed to query nextgen clusters")
	}

	clusterIds := lo.FlatMap(clusters, func(cluster *entity.Cluster, _ int) []uint64 {
		return []uint64{cluster.ClusterId}
	})

	// Fetch custom status
	statusMap, err := c.customStatusSvc.ListClusterCustomStatus(ctx, clusterIds, nil, false)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) && !errors.Is(err, constant.ErrClusterNotFound) {
		return nil, bizerror.NewInternal(err, "failed to get cluster custom status record")
	}

	resp := &tidbmgmtservicev1.ListClustersResponse{
		Clusters: []*tidbmgmtservicev1.Cluster{},
		Total:    uint32(count),
	}

	resp.Clusters = lo.Map(clusters, func(cluster *entity.Cluster, _ int) *tidbmgmtservicev1.Cluster {
		return cvtToMgmtCluster(cluster, statusMap[cluster.ClusterId])
	})

	// default sort by create time asc
	// TODO: add sort by request
	// sort.Slice(resp.Clusters, func(i, j int) bool {
	// 	return resp.Clusters[i].CreateTime.AsTime().Before(resp.Clusters[j].CreateTime.AsTime())
	// })

	return resp, nil
}

func (c *serverImpl) countServerlessClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.CountClustersRequest,
) (*tidbmgmtservicev1.CountClustersResponse, error) {
	if request.Filter != nil && request.Filter.ClusterType != nil &&
		*request.Filter.ClusterType != tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS {
		return &tidbmgmtservicev1.CountClustersResponse{
			Count: 0,
		}, nil
	}

	filter := cvtFilter(request.Filter)
	filter.DeployType = entity.DeployTypeServerless

	// count cluster from domain service
	count, err := c.serverlessClusterSvc.CountClusters(ctx, filter)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to count serverless clusters")
	}
	return &tidbmgmtservicev1.CountClustersResponse{Count: uint32(count)}, nil
}

func (c *serverImpl) countDedicatedClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.CountClustersRequest,
) (*tidbmgmtservicev1.CountClustersResponse, error) {
	if request.Filter != nil && request.Filter.ClusterType != nil &&
		*request.Filter.ClusterType != tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED {
		return &tidbmgmtservicev1.CountClustersResponse{
			Count: 0,
		}, nil
	}

	filter := cvtFilter(request.Filter)
	filter.DeployType = entity.DeployTypeDedicated

	// count cluster from domain service
	count, err := c.dedicatedClusterSvc.CountClusters(ctx, filter)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to count dedicated clusters")
	}

	return &tidbmgmtservicev1.CountClustersResponse{Count: uint32(count)}, nil
}

func (c *serverImpl) countNextgenClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.CountClustersRequest,
) (*tidbmgmtservicev1.CountClustersResponse, error) {
	if request.Filter != nil && request.Filter.ClusterType != nil &&
		*request.Filter.ClusterType != tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN {
		return &tidbmgmtservicev1.CountClustersResponse{
			Count: 0,
		}, nil
	}

	filter := cvtFilter(request.Filter)
	filter.DeployType = entity.DeployTypeNextGen

	// count cluster from domain service
	count, err := c.nextgenClusterSvc.CountClusters(ctx, filter)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to count nextgen clusters")
	}

	return &tidbmgmtservicev1.CountClustersResponse{Count: uint32(count)}, nil
}
