package server

import (
	"context"
	"strings"

	"github.com/samber/lo"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow/dag"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
)

func (s *serverImpl) ListTiDBGroups(ctx context.Context, clusterId string) ([]*entity.TiDBGroup, error) {

	clusterIds := make([]string, 0)

	if clusterId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "clusterId is required")
	}

	clusterIds = append(clusterIds, clusterId)

	// check clusterId
	tidbCluster, err := s.tidbClusterRepo.Load(ctx, clusterId, false)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to load tidb cluster: %s", err)
	}

	tidbGroups, err := s.tidbGroupRepo.List(ctx, nil, nil, clusterIds, nil, false)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list tidb groups: %s", err)
	}

	tidbGroupMap := lo.SliceToMap(tidbGroups, func(tidbGroup *entity.TiDBGroup) (string, *entity.TiDBGroup) {
		return tidbGroup.Id, tidbGroup
	})

	// if not found, check if there is a running dag instance
	runningDagIns, err := s.getRunningDagIns(ctx, []string{dag.PlanCreateTiDBGroup, dag.PlanUpdateTiDBGroup}, nil, nil, clusterIds, nil)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get running create/update tidb group dag instance: %s", err)
	}

	for _, ins := range runningDagIns {
		tidbGroupId, err := ins.ExecTask.Args.GetString(workflow.TiDBGroupIdKey)
		if err != nil {
			tilog.Warn(ctx, "failed to get tidb group id from exec task args", zap.String("task name", ins.ExecTask.ExecId), zap.String("tidb group id", tidbGroupId))
			continue
		}

		tidbGroupStr, err := ins.ExecTask.Args.GetString(workflow.TiDBGroupKey)
		if err != nil {
			tilog.Error(ctx, "failed to get tidb group", zap.String("task name", ins.ExecTask.ExecId), zap.Error(err))
			return nil, status.Errorf(codes.Internal, "failed to get tidb group: %s", err)
		}

		var tidbGroup entity.TiDBGroup
		err = protojson.Unmarshal([]byte(tidbGroupStr), &tidbGroup)
		if err != nil {
			tilog.Error(ctx, "failed to unmarshal tidb group", zap.Error(err))
		}

		if len(clusterIds) > 0 {
			// if clusterId is specified, check if the tidbGroup matches the clusterId
			if !lo.Contains(clusterIds, tidbGroup.ClusterId) {
				// if not, skip this tidbGroup
				tilog.Warn(ctx, "tidb group cluster id not match", zap.String("tidbGroupId", tidbGroupId), zap.String("clusterId", tidbGroup.ClusterId))
				continue
			}
		}

		tidbGroupMap[tidbGroupId] = &tidbGroup
	}

	// Check for running scale/apply/create component DAGs and set matching tidbGroups to MODIFYING
	componentDagIns, err := s.getRunningDagIns(ctx, []string{dag.PlanUpdateComponent, dag.PlanApplyComponent, dag.PlanCreateComponent}, []string{tidbCluster.GetSharedPoolId()}, nil, nil, nil)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get running component dag instance: %s", err)
	}
	for _, ins := range componentDagIns {
		compIds, err := ins.ExecTask.Args.GetString(workflow.ComponentIdsKey)
		if err != nil || compIds == "" {
			continue
		}
		for _, compId := range strings.Split(compIds, ",") {
			if tg, ok := tidbGroupMap[compId]; ok {
				tg.Phase = apiv1.TiDBGroup_MODIFYING
			}
		}
	}

	// if group in delete task, not return
	deleteDagIns, err := s.getRunningDagIns(ctx, []string{dag.PlanDeleteTiDBGroup}, nil, nil, clusterIds, nil)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get running delete tidb group dag instance: %s", err)
	}

	for _, ins := range deleteDagIns {
		tidbGroupId, err := ins.ExecTask.Args.GetString(workflow.TiDBGroupIdKey)
		if err != nil {
			tilog.Error(ctx, "failed to get tidb group id from exec task args", zap.String("task name", ins.ExecTask.ExecId), zap.Error(err))
			return nil, status.Errorf(codes.Internal, "failed to get tidb group id from exec task args: %s", err)
		}
		delete(tidbGroupMap, tidbGroupId)
	}

	return lo.Values(tidbGroupMap), nil
}
