package cluster

import (
	"context"
	"errors"
	"fmt"
	"slices"
	"strconv"
	"strings"
	"time"

	"braces.dev/errtrace"
	"github.com/samber/lo"
	"gorm.io/gorm"

	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapiserverlessv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/serverless/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/filter"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (c serverImpl) ListClusters(
	ctx context.Context,
	req *openapiserverlessv1beta1.ListClustersRequest,
) (resp *openapiserverlessv1beta1.ListClustersResponse, err error) {
	startTime := time.Now()
	defer func() {
		recordQueryClusterAPIMetrics(apiListClusters, time.Since(startTime).Seconds(), err)
	}()

	// Extract request meta.
	reqMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	// Create empty request.
	clusterFilter := &repository.ClusterFilter{
		Labels:       make(map[string]string),
		DeployType:   entity.DeployTypeServerless,
		View:         entity.ClusterViewClusterViewBasic,
		QueryDeleted: false,
	}

	var filterParams map[string]any
	if filterParams, err = filter.TranspileFilter(req); err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed parse filter expression")
	}

	if err := cvtGlobalFilter(filterParams, clusterFilter); err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid filter expression")
	}

	applyOrgAndProjectFilter(reqMeta, clusterFilter)

	// Validate the project
	if err := reqMeta.CheckProjectIDsPermission(clusterFilter.ProjectIds...); err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodePermission, bizerr.ErrMsgResourceNoPermission)
	}

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

	// not need to query domain service if cluster count equals 0
	if count == 0 {
		return &openapiserverlessv1beta1.ListClustersResponse{
			Clusters:      []*openapiserverlessv1beta1.Cluster{},
			TotalSize:     uint32(count),
			NextPageToken: "",
		}, nil
	}

	// then query cluster from domain service
	clusters, nextPageToken, err := common.RemotePagination(func(page common.Page) ([]*entity.Cluster, error) {
		cs, err := c.clusterSvc.ListClusters(ctx, clusterFilter, &page)
		if err != nil {
			return nil, bizerror.NewInternal(err, "failed to query clusters")
		}

		// sort by create time desc
		slices.SortFunc(cs, func(a, b *entity.Cluster) int {
			if a.CreatedAt.After(b.CreatedAt) {
				return -1
			}
			if a.CreatedAt.Before(b.CreatedAt) {
				return 1
			}
			return 0
		})

		return cs, nil
	}, int(count), req, common.ListDefaultPageSize, common.ListMaxPageSize)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query clusters")
	}

	enrichedClusters := make([]*openapiserverlessv1beta1.Cluster, 0, len(clusters))

	clusterIds := lo.Map(clusters, func(item *entity.Cluster, _ int) uint64 {
		return item.ClusterId
	})

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

	// Enrich the response with cluster entities.
	for _, cluster := range clusters {
		enrichedCluster, err := buildClusterFromEntity(
			cluster,
			statusMap[cluster.ClusterId],
			openapiserverlessv1beta1.ClusterView_BASIC,
		)
		if err != nil {
			return nil, bizerror.NewInternal(err, "failed to get cluster")
		}
		enrichedClusters = append(enrichedClusters, enrichedCluster)
	}

	resp = &openapiserverlessv1beta1.ListClustersResponse{
		Clusters:      enrichedClusters,
		TotalSize:     uint32(count),
		NextPageToken: nextPageToken,
	}
	return resp, nil
}

func cvtGlobalFilter(filterParams map[string]any, clusterFilter *repository.ClusterFilter) error {
	if filterParams == nil {
		return nil
	}

	if clusterFilter == nil {
		clusterFilter = &repository.ClusterFilter{
			Labels: make(map[string]string),
		}
	}

	// get labels from filterParams
	for k, v := range filterParams {
		if strings.HasPrefix(k, "labels.") {
			s, ok := v.(string)
			if !ok {
				return fmt.Errorf("failed to parse %s label: %v", k, v)
			}
			clusterFilter.Labels[k[7:]] = s
		}
	}

	if filterParams["region.provider"] != nil && filterParams["region.provider"] != "" {
		provider, err := enum.FromString[openapicommonv1beta1.Region_CloudProvider](
			filterParams["region.provider"].(string),
		)
		if err != nil {
			return errtrace.Wrap(err)
		}
		clusterFilter.CloudProviders = []string{openapicommonv1beta1.Region_CloudProvider(provider).String()}
	}

	if filterParams["region.name"] != nil && filterParams["region.name"] != "" {
		provider, region, err := common.ParseOpenApiRegionNameOrId(filterParams["region.name"].(string))
		if err != nil {
			return errtrace.Wrap(errors.New("invalid region name, format should be regions/<provider>-<region>"))
		}

		var includeProvider bool
		if slices.Contains(clusterFilter.CloudProviders, provider) {
			includeProvider = true
		}

		if !includeProvider {
			clusterFilter.CloudProviders = append(clusterFilter.CloudProviders, provider)
		}

		clusterFilter.Regions = []string{region}
	}

	if filterParams["state"] != nil && filterParams["state"] != "" {
		var states []openapicommonv1beta1.ClusterState
		for state := range strings.SplitSeq(filterParams["state"].(string), ",") {
			st, ok := openapicommonv1beta1.ClusterState_value[strings.ToUpper(state)]
			if !ok {
				return fmt.Errorf("invalid state: %s is not supported", state)
			}
			states = append(states, openapicommonv1beta1.ClusterState(st))
		}
		clusterFilter.States = states
	}

	if filterParams["projectId"] != nil && filterParams["projectId"] != "" {
		var projectIds []uint64
		for projectId := range strings.SplitSeq(filterParams["projectId"].(string), ",") {
			id, err := strconv.ParseUint(projectId, 10, 64)
			if err != nil {
				return errtrace.Wrap(err)
			}
			projectIds = append(projectIds, id)
		}
		clusterFilter.ProjectIds = projectIds
	}

	if filterParams["clusterId"] != nil && filterParams["clusterId"] != "" {
		var clusterIds []uint64
		for clusterId := range strings.SplitSeq(filterParams["clusterId"].(string), ",") {
			id, err := strconv.ParseUint(clusterId, 10, 64)
			if err != nil {
				return errtrace.Wrap(err)
			}
			clusterIds = append(clusterIds, id)
		}
		clusterFilter.ClusterIds = clusterIds
	}
	if filterParams["displayName"] != nil && filterParams["displayName"] != "" {
		if name, ok := filterParams["displayName"].(string); ok {
			clusterFilter.Name = name
		}
	}

	if filterParams["servicePlan"] != nil && filterParams["servicePlan"] != "" {
		var servicePlans []string
		for plan := range strings.SplitSeq(filterParams["servicePlan"].(string), ",") {
			if _, ok := openapicommonv1beta1.ServicePlan_value[plan]; !ok {
				return fmt.Errorf("invalid service_plan: %s is not supported", plan)
			}
			servicePlans = append(servicePlans, plan)
		}
		clusterFilter.ServicePlans = servicePlans
	}

	if filterParams["engineType"] != nil && filterParams["engineType"] != "" {
		var engineTypes []string
		for engineType := range strings.SplitSeq(filterParams["engineType"].(string), ",") {
			if _, ok := openapicommonv1beta1.EngineType_value[engineType]; !ok {
				return fmt.Errorf("invalid engine_type: %s is not supported", engineType)
			}
			engineTypes = append(engineTypes, engineType)
		}
		clusterFilter.EngineTypes = engineTypes
	}

	if filterParams["tags"] != nil && filterParams["tags"] != "" {
		tags, err := entity.NormalizeCanonicalTags(strings.Split(filterParams["tags"].(string), ","))
		if err != nil {
			return errtrace.Wrap(err)
		}
		clusterFilter.TagCanonicalNames = entity.CanonicalTags(tags)
	}

	return nil
}

func applyOrgAndProjectFilter(srcReqMeta *metadata.RequestMeta, clusterFilter *repository.ClusterFilter) {
	if clusterFilter == nil {
		clusterFilter = &repository.ClusterFilter{}
	}

	clusterFilter.OrganizationIds = []uint64{srcReqMeta.OrgID}

	if len(clusterFilter.ProjectIds) == 0 {
		// If project id not is specified in source request filter, use it.
		// Otherwise, use all valid projects for the user.
		clusterFilter.ProjectIds = srcReqMeta.ValidProjectList
	}
}
