package tidbgroup

import (
	"context"

	"github.com/samber/lo"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/tidbgroup"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func (s *service) ListTidbNodeGroups(
	ctx context.Context,
	req *globaldedicatedv1.ListTidbNodeGroupsRequest,
) (*globaldedicatedv1.ListTidbNodeGroupsResponse, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.listTidbNodeGroups")
	defer span.End()

	if len(req.GetClusterIds()) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster_ids filter is required")
	}

	filter := &tidbgroup.Filter{
		ClusterIds: req.GetClusterIds(),
		Ids:        req.GetIds(),
	}

	count, err := s.tidbGroupRepo.Count(ctx, filter)
	if err != nil {
		return nil, err
	}

	if count == 0 {
		return &globaldedicatedv1.ListTidbNodeGroupsResponse{
			TidbNodeGroups: nil,
			Total:          0,
		}, nil
	}

	// get tidb node groups
	tidbGroups, err := s.tidbGroupRepo.Query(ctx, filter)
	if err != nil {
		return nil, err
	}

	// get cluster ids
	clusterIds := lo.Map(tidbGroups, func(tg *entity.TidbNodeGroup, _ int) uint64 {
		return tg.ClusterId
	})

	// get cluster info
	// cluster info includes tidb node group info
	globalClusters, err := s.clusterService.List(ctx, &globaldedicatedv1.ListClustersRequest{
		ClusterIds:  clusterIds,
		ShowDetails: req.GetShowDetails(),
	})
	if err != nil {
		return nil, err
	}
	if len(globalClusters) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "clusters not found from global service")
	}

	tidbGroupIdToComp := map[uint64]*globaldedicatedv1.Cluster_ClusterComponent{}
	tiproxyIdtoComp := map[uint64]*globaldedicatedv1.Cluster_ClusterComponent{}
	tidbGroupIdToEndpoint := map[uint64]*globaldedicatedv1.TidbNodeGroup_Endpoint{}
	tidbGroupIdToCluster := map[uint64]*entity.Cluster{}
	for _, cluster := range globalClusters {
		for _, comp := range cluster.GetClusterComponents() {
			if comp.GetComponentType() == globaldedicatedv1.ComponentType_TIDB {
				tidbGroupIdToComp[comp.GetTidbGroupId()] = comp
				tidbGroupIdToCluster[comp.GetTidbGroupId()] = cluster
			}
			if comp.GetComponentType() == globaldedicatedv1.ComponentType_TIPROXY {
				tiproxyIdtoComp[comp.GetTidbGroupId()] = comp
			}
		}

		for _, endpoint := range cluster.TidbNodeGroupEndpoints {
			tidbGroupIdToEndpoint[endpoint.GetTidbGroupId()] = endpoint
		}
	}

	// build response
	tidbNodeGroups := lo.Map(tidbGroups, func(tg *entity.TidbNodeGroup, _ int) *globaldedicatedv1.TidbNodeGroup {
		comp := tidbGroupIdToComp[tg.Id]
		endpoint := tidbGroupIdToEndpoint[tg.Id]
		cluster := tidbGroupIdToCluster[tg.Id]
		g := buildGlobalTidbNodeGroup(tg, comp, endpoint, cluster)
		if tiproxyComp, ok := tiproxyIdtoComp[tg.Id]; ok {
			g.TiproxySetting = &globaldedicatedv1.TidbNodeGroup_TiProxySetting{
				SpuName:        tiproxyComp.GetSpuName(),
				Count:          int32(tiproxyComp.GetCount()),
				SpuDisplayName: tiproxyComp.GetSpuDisplayName(),
				SpuVersion:     tiproxyComp.GetSpuVersion(),
			}
			if tiproxyComp.GetModifyProgress() != nil {
				g.State = globaldedicatedv1.TidbNodeGroup_MODIFYING
				g.TiproxyModifyProgress = tiproxyComp.GetModifyProgress()
			}
		}
		return g
	})

	return &globaldedicatedv1.ListTidbNodeGroupsResponse{
		TidbNodeGroups: tidbNodeGroups,
		Total:          uint32(count),
	}, nil
}

func buildGlobalTidbNodeGroup(
	tg *entity.TidbNodeGroup,
	comp *globaldedicatedv1.Cluster_ClusterComponent,
	endpoint *globaldedicatedv1.TidbNodeGroup_Endpoint,
	cluster *entity.Cluster,
) *globaldedicatedv1.TidbNodeGroup {
	state := globaldedicatedv1.TidbNodeGroup_ACTIVE
	// if modify progress is not nil, then the state is modifying
	if comp.GetModifyProgress() != nil {
		state = globaldedicatedv1.TidbNodeGroup_MODIFYING
	}

	// override state if cluster is not available
	switch cluster.GetState() {
	case globaldedicatedv1.Cluster_CREATING:
		state = globaldedicatedv1.TidbNodeGroup_MODIFYING
	case globaldedicatedv1.Cluster_RESUMING,
		globaldedicatedv1.Cluster_PAUSED,
		globaldedicatedv1.Cluster_PAUSING:
		state = globaldedicatedv1.TidbNodeGroup_PAUSED
	}

	return &globaldedicatedv1.TidbNodeGroup{
		Id:             tg.Id,
		ClusterId:      tg.ClusterId,
		DisplayName:    tg.DisplayName,
		IsDefault:      tg.Default,
		Count:          int32(comp.GetCount()),
		Endpoint:       endpoint,
		SpuName:        comp.GetSpuName(),
		SpuDisplayName: comp.GetSpuDisplayName(),
		SpuVersion:     comp.GetSpuVersion(),
		ModifyProgress: comp.GetModifyProgress(),
		State:          state,
	}
}
