package milvus

import (
	"bytes"
	"context"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/cockroachdb/errors"
	clientv3 "go.etcd.io/etcd/client/v3"
	"google.golang.org/protobuf/proto"

	"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
	etcdkv "github.com/milvus-io/milvus/internal/kv/etcd"
	"github.com/milvus-io/milvus/internal/storage"
	"github.com/milvus-io/milvus/pkg/v3/kv"
	"github.com/milvus-io/milvus/pkg/v3/mlog"
	"github.com/milvus-io/milvus/pkg/v3/proto/datapb"
	pb "github.com/milvus-io/milvus/pkg/v3/proto/etcdpb"
	"github.com/milvus-io/milvus/pkg/v3/proto/querypb"
	"github.com/milvus-io/milvus/pkg/v3/util/etcd"
	"github.com/milvus-io/milvus/pkg/v3/util/logutil"
	"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
)

const (
	MckCmd       = "mck"
	MckTypeRun   = "run"
	MckTypeClean = "cleanTrash"

	segmentPrefix     = "datacoord-meta/s"
	collectionPrefix  = "snapshots/root-coord/collection"
	triggerTaskPrefix = "queryCoord-triggerTask"
	activeTaskPrefix  = "queryCoord-activeTask"
	taskInfoPrefix    = "queryCoord-taskInfo"
	MckTrash          = "mck-trash"
)

type mck struct {
	params              *paramtable.ComponentParam
	taskKeyMap          map[int64]string
	taskNameMap         map[int64]string
	allTaskInfo         map[string]string
	partitionIDToTasks  map[int64][]int64
	segmentIDToTasks    map[int64][]int64
	taskIDToInvalidPath map[int64][]string
	segmentIDMap        map[int64]*datapb.SegmentInfo
	partitionIDMap      map[int64]struct{}
	metaKV              kv.MetaKv
	minioChunkManager   storage.ChunkManager

	etcdIP          string
	ectdRootPath    string
	minioAddress    string
	minioUsername   string
	minioPassword   string
	minioUseSSL     string
	minioBucketName string

	flagStartIndex int
}

func (c *mck) execute(args []string, flags *flag.FlagSet) {
	if len(args) < 3 {
		fmt.Fprintln(os.Stderr, mckLine)
		return
	}
	c.initParam()
	flags.Usage = func() {
		fmt.Fprintln(os.Stderr, mckLine)
	}

	logutil.SetupLogger(&mlog.Config{
		Level: "info",
		File: mlog.FileLogConfig{
			Filename: fmt.Sprintf("mck-%s.log", time.Now().Format("20060102150405.99")),
		},
	})

	mckType := args[2]
	c.flagStartIndex = 2
	isClean := mckType == MckTypeClean
	if isClean {
		c.flagStartIndex = 3
	}
	c.formatFlags(args, flags)
	c.connectEctd()

	switch mckType {
	case MckTypeRun:
		c.run()
	case MckTypeClean:
		c.cleanTrash()
		return
	default:
		fmt.Fprintln(os.Stderr, mckLine)
		return
	}
}

func (c *mck) run() {
	c.connectMinio()

	_, values, err := c.metaKV.LoadWithPrefix(context.TODO(), segmentPrefix)
	if err != nil {
		mlog.Fatal(context.TODO(), "failed to list the segment info", mlog.String("key", segmentPrefix), mlog.Err(err))
	}
	for _, value := range values {
		info := &datapb.SegmentInfo{}
		err = proto.Unmarshal([]byte(value), info)
		if err != nil {
			mlog.Warn(context.TODO(), "fail to unmarshal the segment info", mlog.Err(err))
			continue
		}
		c.segmentIDMap[info.ID] = info
	}

	_, values, err = c.metaKV.LoadWithPrefix(context.TODO(), collectionPrefix)
	if err != nil {
		mlog.Fatal(context.TODO(), "failed to list the collection info", mlog.String("key", collectionPrefix), mlog.Err(err))
	}
	for _, value := range values {
		collInfo := pb.CollectionInfo{}
		err = proto.Unmarshal([]byte(value), &collInfo)
		if err != nil {
			mlog.Warn(context.TODO(), "fail to unmarshal the collection info", mlog.Err(err))
			continue
		}
		for _, id := range collInfo.PartitionIDs {
			c.partitionIDMap[id] = struct{}{}
		}
	}

	// log Segment ids and partition ids
	var ids []int64
	for id := range c.segmentIDMap {
		ids = append(ids, id)
	}
	mlog.Info(context.TODO(), "segment ids", mlog.Int64s("ids", ids))

	ids = []int64{}
	for id := range c.partitionIDMap {
		ids = append(ids, id)
	}
	mlog.Info(context.TODO(), "partition ids", mlog.Int64s("ids", ids))

	keys, values, err := c.metaKV.LoadWithPrefix(context.TODO(), triggerTaskPrefix)
	if err != nil {
		mlog.Fatal(context.TODO(), "failed to list the trigger task info", mlog.Err(err))
	}
	c.extractTask(triggerTaskPrefix, keys, values)

	keys, values, err = c.metaKV.LoadWithPrefix(context.TODO(), activeTaskPrefix)
	if err != nil {
		mlog.Fatal(context.TODO(), "failed to list the active task info", mlog.Err(err))
	}
	c.extractTask(activeTaskPrefix, keys, values)

	// log all tasks
	if len(c.taskNameMap) > 0 {
		mlog.Info(context.TODO(), "all tasks")
		for taskID, taskName := range c.taskNameMap {
			mlog.Info(context.TODO(), "task info", mlog.String("name", taskName), mlog.Int64("id", taskID))
		}
	}

	// collect invalid tasks
	invalidTasks := c.collectInvalidTask()
	if len(invalidTasks) > 0 {
		line()
		fmt.Println("All invalid tasks")
		for _, invalidTask := range invalidTasks {
			line2()
			fmt.Printf("TaskID: %d\t%s\n", invalidTask, c.taskNameMap[invalidTask])
		}
	}
	c.handleUserEnter(invalidTasks)
}

func (c *mck) initParam() {
	c.taskKeyMap = make(map[int64]string)
	c.taskNameMap = make(map[int64]string)
	c.allTaskInfo = make(map[string]string)
	c.partitionIDToTasks = make(map[int64][]int64)
	c.segmentIDToTasks = make(map[int64][]int64)
	c.taskIDToInvalidPath = make(map[int64][]string)
	c.segmentIDMap = make(map[int64]*datapb.SegmentInfo)
	c.partitionIDMap = make(map[int64]struct{})
}

func (c *mck) formatFlags(args []string, flags *flag.FlagSet) {
	flags.StringVar(&c.etcdIP, "etcdIp", "", "Etcd endpoint to connect")
	flags.StringVar(&c.ectdRootPath, "etcdRootPath", "", "Etcd root path")
	flags.StringVar(&c.minioAddress, "minioAddress", "", "Minio endpoint to connect")
	flags.StringVar(&c.minioUsername, "minioUsername", "", "Minio username")
	flags.StringVar(&c.minioPassword, "minioPassword", "", "Minio password")
	flags.StringVar(&c.minioUseSSL, "minioUseSSL", "", "Minio to use ssl")
	flags.StringVar(&c.minioBucketName, "minioBucketName", "", "Minio bucket name")

	if err := flags.Parse(os.Args[2:]); err != nil {
		mlog.Fatal(context.TODO(), "failed to parse flags", mlog.Err(err))
	}
	mlog.Info(context.TODO(), "args", mlog.Strings("args", args))
}

func (c *mck) connectEctd() {
	c.params.Init(paramtable.NewBaseTable())
	var etcdCli *clientv3.Client
	var err error

	if c.etcdIP != "" {
		etcdCli, err = etcd.GetRemoteEtcdClient([]string{c.etcdIP}, c.params.EtcdCfg.ClientOptions()...)
	} else {
		etcdCli, err = etcd.CreateEtcdClient(
			c.params.EtcdCfg.UseEmbedEtcd.GetAsBool(),
			c.params.EtcdCfg.EtcdEnableAuth.GetAsBool(),
			c.params.EtcdCfg.EtcdAuthUserName.GetValue(),
			c.params.EtcdCfg.EtcdAuthPassword.GetValue(),
			c.params.EtcdCfg.EtcdUseSSL.GetAsBool(),
			c.params.EtcdCfg.Endpoints.GetAsStrings(),
			c.params.EtcdCfg.EtcdTLSCert.GetValue(),
			c.params.EtcdCfg.EtcdTLSKey.GetValue(),
			c.params.EtcdCfg.EtcdTLSCACert.GetValue(),
			c.params.EtcdCfg.EtcdTLSMinVersion.GetValue(),
			c.params.EtcdCfg.ClientOptions()...)
	}
	if err != nil {
		mlog.Fatal(context.TODO(), "failed to connect to etcd", mlog.Err(err))
	}

	rootPath := getConfigValue(c.ectdRootPath, c.params.EtcdCfg.MetaRootPath.GetValue(), "ectd_root_path")
	c.metaKV = etcdkv.NewEtcdKV(etcdCli, rootPath)
	mlog.Info(context.TODO(), "Etcd root path", mlog.String("root_path", rootPath))
}

func (c *mck) connectMinio() {
	chunkManagerFactory := storage.NewChunkManagerFactoryWithParam(c.params)

	var err error
	c.minioChunkManager, err = chunkManagerFactory.NewPersistentStorageChunkManager(context.Background())
	if err != nil {
		mlog.Fatal(context.TODO(), "failed to connect to minio", mlog.Err(err))
	}
}

func getConfigValue(a string, b string, name string) string {
	if a != "" {
		return a
	}
	if b != "" {
		return b
	}
	mlog.Panic(context.TODO(), fmt.Sprintf("the config '%s' is empty", name))
	return ""
}

func (c *mck) cleanTrash() {
	keys, _, err := c.metaKV.LoadWithPrefix(context.TODO(), MckTrash)
	if err != nil {
		mlog.Error(context.TODO(), "failed to load backup info", mlog.Err(err))
		return
	}
	if len(keys) == 0 {
		fmt.Println("Empty backup task info")
		return
	}
	fmt.Println(strings.Join(keys, "\n"))
	fmt.Print("Delete all backup infos, [Y/n]:")
	deleteAll := ""
	fmt.Scanln(&deleteAll)
	if deleteAll == "Y" {
		err = c.metaKV.RemoveWithPrefix(context.TODO(), MckTrash)
		if err != nil {
			mlog.Error(context.TODO(), "failed to remove backup infos", mlog.String("key", MckTrash), mlog.Err(err))
			return
		}
	}
}

func (c *mck) collectInvalidTask() []int64 {
	var invalidTasks []int64
	invalidTasks = append(invalidTasks, c.collectInvalidTaskForPartition()...)
	invalidTasks = append(invalidTasks, c.collectInvalidTaskForSegment()...)
	invalidTasks = append(invalidTasks, c.collectInvalidTaskForMinioPath()...)
	invalidTasks = removeRepeatElement(invalidTasks)
	return invalidTasks
}

// collect invalid tasks related with invalid partitions
func (c *mck) collectInvalidTaskForPartition() []int64 {
	var invalidTasksOfPartition []int64
	var buffer bytes.Buffer
	for id, tasks := range c.partitionIDToTasks {
		if _, ok := c.partitionIDMap[id]; !ok {
			invalidTasksOfPartition = append(invalidTasksOfPartition, tasks...)
			fmt.Fprintf(&buffer, "Partition ID: %d\n", id)
			fmt.Fprintf(&buffer, "Tasks: %v\n", tasks)
		}
	}
	invalidTasksOfPartition = removeRepeatElement(invalidTasksOfPartition)
	if len(invalidTasksOfPartition) > 0 {
		line()
		fmt.Println("Invalid Partitions")
		fmt.Println(buffer.String())
	}
	return invalidTasksOfPartition
}

// collect invalid tasks related with invalid segments
func (c *mck) collectInvalidTaskForSegment() []int64 {
	var invalidTasksOfSegment []int64
	var buffer bytes.Buffer
	if len(c.segmentIDToTasks) > 0 {
		for id, tasks := range c.segmentIDToTasks {
			if _, ok := c.segmentIDMap[id]; !ok {
				invalidTasksOfSegment = append(invalidTasksOfSegment, tasks...)
				fmt.Fprintf(&buffer, "Segment ID: %d\n", id)
				fmt.Fprintf(&buffer, "Tasks: %v\n", tasks)
			}
		}
		invalidTasksOfSegment = removeRepeatElement(invalidTasksOfSegment)
	}
	if len(invalidTasksOfSegment) > 0 {
		line()
		fmt.Println("Invalid Segments")
		fmt.Println(buffer.String())
	}
	return invalidTasksOfSegment
}

// collect invalid tasks related with incorrect file paths in minio
func (c *mck) collectInvalidTaskForMinioPath() []int64 {
	var invalidTasksOfPath []int64
	if len(c.taskIDToInvalidPath) > 0 {
		line()
		fmt.Println("Invalid Paths")
		for id, paths := range c.taskIDToInvalidPath {
			fmt.Printf("Task ID: %d\n", id)
			for _, path := range paths {
				fmt.Printf("\t%s\n", path)
			}
			invalidTasksOfPath = append(invalidTasksOfPath, id)
		}
		invalidTasksOfPath = removeRepeatElement(invalidTasksOfPath)
	}
	return invalidTasksOfPath
}

func redPrint(msg string) {
	fmt.Printf("\033[0;31;40m%s\033[0m", msg)
}

func line() {
	fmt.Println("================================================================================")
}

func line2() {
	fmt.Println("--------------------------------------------------------------------------------")
}

func getTrashKey(taskType, key string) string {
	return fmt.Sprintf("%s/%s/%s", MckTrash, taskType, key)
}

func (c *mck) extractTask(prefix string, keys []string, values []string) {
	for i := range keys {
		taskID, err := strconv.ParseInt(filepath.Base(keys[i]), 10, 64)
		if err != nil {
			mlog.Warn(context.TODO(), "failed to parse int", mlog.String("key", filepath.Base(keys[i])), mlog.String("tasks", filepath.Base(keys[i])))
			continue
		}

		taskName, pids, sids, err := c.unmarshalTask(taskID, values[i])
		if err != nil {
			mlog.Warn(context.TODO(), "failed to unmarshal task", mlog.FieldTaskID(taskID))
			continue
		}
		for _, pid := range pids {
			c.partitionIDToTasks[pid] = append(c.partitionIDToTasks[pid], taskID)
		}
		for _, sid := range sids {
			c.segmentIDToTasks[sid] = append(c.segmentIDToTasks[sid], taskID)
		}

		c.taskKeyMap[taskID] = fmt.Sprintf("%s/%d", prefix, taskID)
		c.allTaskInfo[c.taskKeyMap[taskID]] = values[i]
		c.taskNameMap[taskID] = taskName
	}
}

func (c *mck) removeTask(invalidTask int64) bool {
	taskType := c.taskNameMap[invalidTask]
	key := c.taskKeyMap[invalidTask]
	err := c.metaKV.Save(context.TODO(), getTrashKey(taskType, key), c.allTaskInfo[key])
	if err != nil {
		mlog.Warn(context.TODO(), "failed to backup task", mlog.String("key", getTrashKey(taskType, key)), mlog.FieldTaskID(invalidTask), mlog.Err(err))
		return false
	}
	fmt.Printf("Back up task successfully, back path: %s\n", getTrashKey(taskType, key))
	err = c.metaKV.Remove(context.TODO(), key)
	if err != nil {
		mlog.Warn(context.TODO(), "failed to remove task", mlog.FieldTaskID(invalidTask), mlog.Err(err))
		return false
	}

	key = fmt.Sprintf("%s/%d", taskInfoPrefix, invalidTask)
	taskInfo, err := c.metaKV.Load(context.TODO(), key)
	if err != nil {
		mlog.Warn(context.TODO(), "failed to load task info", mlog.FieldTaskID(invalidTask), mlog.Err(err))
		return false
	}
	err = c.metaKV.Save(context.TODO(), getTrashKey(taskType, key), taskInfo)
	if err != nil {
		mlog.Warn(context.TODO(), "failed to backup task info", mlog.FieldTaskID(invalidTask), mlog.Err(err))
		return false
	}
	fmt.Printf("Back up task info successfully, back path: %s\n", getTrashKey(taskType, key))
	err = c.metaKV.Remove(context.TODO(), key)
	if err != nil {
		mlog.Warn(context.TODO(), "failed to remove task info", mlog.FieldTaskID(invalidTask), mlog.Err(err))
	}
	return true
}

func emptyInt64() []int64 {
	return []int64{}
}

func errReturn(taskID int64, pbName string, err error) (string, []int64, []int64, error) {
	return "", emptyInt64(), emptyInt64(), fmt.Errorf("task id: %d, failed to unmarshal %s, err %s ", taskID, pbName, err.Error())
}

func int64Map(ids []int64) map[int64]interface{} {
	idMap := make(map[int64]interface{})
	for _, id := range ids {
		idMap[id] = nil
	}
	return idMap
}

func removeRepeatElement(ids []int64) []int64 {
	idMap := int64Map(ids)
	elements := make([]int64, 0, len(idMap))
	for k := range idMap {
		elements = append(elements, k)
	}
	return elements
}

func (c *mck) extractDataSegmentInfos(taskID int64, infos []*datapb.SegmentInfo) ([]int64, []int64) {
	var partitionIDs []int64
	var segmentIDs []int64
	for _, info := range infos {
		partitionIDs = append(partitionIDs, info.PartitionID)
		segmentIDs = append(segmentIDs, info.ID)
		c.extractFieldBinlog(taskID, info.Binlogs)
		c.extractFieldBinlog(taskID, info.Statslogs)
		c.extractFieldBinlog(taskID, info.Deltalogs)
	}

	return partitionIDs, segmentIDs
}

func (c *mck) extractQuerySegmentInfos(taskID int64, infos []*querypb.SegmentInfo) ([]int64, []int64) {
	var partitionIDs []int64
	var segmentIDs []int64
	for _, info := range infos {
		partitionIDs = append(partitionIDs, info.PartitionID)
		segmentIDs = append(segmentIDs, info.SegmentID)
		c.extractVecFieldIndexInfo(taskID, info.IndexInfos)
	}

	return partitionIDs, segmentIDs
}

func (c *mck) extractVchannelInfo(taskID int64, infos []*datapb.VchannelInfo) ([]int64, []int64) {
	var partitionIDs []int64
	var segmentIDs []int64
	for _, info := range infos {
		pids, sids := c.extractDataSegmentInfos(taskID, info.DroppedSegments)
		partitionIDs = append(partitionIDs, pids...)
		segmentIDs = append(segmentIDs, sids...)

		pids, sids = c.extractDataSegmentInfos(taskID, info.FlushedSegments)
		partitionIDs = append(partitionIDs, pids...)
		segmentIDs = append(segmentIDs, sids...)

		pids, sids = c.extractDataSegmentInfos(taskID, info.UnflushedSegments)
		partitionIDs = append(partitionIDs, pids...)
		segmentIDs = append(segmentIDs, sids...)
	}
	return partitionIDs, segmentIDs
}

func (c *mck) extractFieldBinlog(taskID int64, fieldBinlogList []*datapb.FieldBinlog) {
	for _, fieldBinlog := range fieldBinlogList {
		for _, binlog := range fieldBinlog.Binlogs {
			ok, _ := c.minioChunkManager.Exist(context.Background(), binlog.LogPath)
			if !ok {
				c.taskIDToInvalidPath[taskID] = append(c.taskIDToInvalidPath[taskID], binlog.LogPath)
			}
		}
	}
}

func (c *mck) extractVecFieldIndexInfo(taskID int64, infos []*querypb.FieldIndexInfo) {
	for _, info := range infos {
		for _, indexPath := range info.IndexFilePaths {
			ok, _ := c.minioChunkManager.Exist(context.Background(), indexPath)
			if !ok {
				c.taskIDToInvalidPath[taskID] = append(c.taskIDToInvalidPath[taskID], indexPath)
			}
		}
	}
}

// return partitionIDs,segmentIDs,error
func (c *mck) unmarshalTask(taskID int64, t string) (string, []int64, []int64, error) {
	header := commonpb.MsgHeader{}
	err := proto.Unmarshal([]byte(t), &header)
	if err != nil {
		return errReturn(taskID, "MsgHeader", err)
	}

	switch header.Base.MsgType {
	case commonpb.MsgType_LoadCollection:
		loadReq := &querypb.LoadCollectionRequest{}
		err = proto.Unmarshal([]byte(t), loadReq)
		if err != nil {
			return errReturn(taskID, "LoadCollectionRequest", err)
		}
		mlog.Info(context.TODO(), "LoadCollection", mlog.String("detail", fmt.Sprintf("+%v", loadReq)))
		return "LoadCollection", emptyInt64(), emptyInt64(), nil
	case commonpb.MsgType_LoadPartitions:
		loadReq := &querypb.LoadPartitionsRequest{}
		err = proto.Unmarshal([]byte(t), loadReq)
		if err != nil {
			return errReturn(taskID, "LoadPartitionsRequest", err)
		}
		mlog.Info(context.TODO(), "LoadPartitions", mlog.String("detail", fmt.Sprintf("+%v", loadReq)))
		return "LoadPartitions", loadReq.PartitionIDs, emptyInt64(), nil
	case commonpb.MsgType_ReleaseCollection:
		loadReq := &querypb.ReleaseCollectionRequest{}
		err = proto.Unmarshal([]byte(t), loadReq)
		if err != nil {
			return errReturn(taskID, "ReleaseCollectionRequest", err)
		}
		mlog.Info(context.TODO(), "ReleaseCollection", mlog.String("detail", fmt.Sprintf("+%v", loadReq)))
		return "ReleaseCollection", emptyInt64(), emptyInt64(), nil
	case commonpb.MsgType_ReleasePartitions:
		loadReq := &querypb.ReleasePartitionsRequest{}
		err = proto.Unmarshal([]byte(t), loadReq)
		if err != nil {
			return errReturn(taskID, "ReleasePartitionsRequest", err)
		}
		mlog.Info(context.TODO(), "ReleasePartitions", mlog.String("detail", fmt.Sprintf("+%v", loadReq)))
		return "ReleasePartitions", loadReq.PartitionIDs, emptyInt64(), nil
	case commonpb.MsgType_LoadSegments:
		loadReq := &querypb.LoadSegmentsRequest{}
		err = proto.Unmarshal([]byte(t), loadReq)
		if err != nil {
			return errReturn(taskID, "LoadSegmentsRequest", err)
		}

		fmt.Printf("LoadSegments, task-id: %d, %+v\n", taskID, loadReq)

		var partitionIDs []int64
		var segmentIDs []int64
		if loadReq.LoadMeta != nil {
			partitionIDs = append(partitionIDs, loadReq.LoadMeta.PartitionIDs...)
		}
		for _, info := range loadReq.Infos {
			partitionIDs = append(partitionIDs, info.PartitionID)
			segmentIDs = append(segmentIDs, info.SegmentID)
			c.extractFieldBinlog(taskID, info.BinlogPaths)
			c.extractFieldBinlog(taskID, info.Statslogs)
			c.extractFieldBinlog(taskID, info.Deltalogs)
			c.extractVecFieldIndexInfo(taskID, info.IndexInfos)
		}
		mlog.Info(context.TODO(), "LoadSegments", mlog.String("detail", fmt.Sprintf("+%v", loadReq)))
		return "LoadSegments", removeRepeatElement(partitionIDs), removeRepeatElement(segmentIDs), nil
	case commonpb.MsgType_ReleaseSegments:
		loadReq := &querypb.ReleaseSegmentsRequest{}
		err = proto.Unmarshal([]byte(t), loadReq)
		if err != nil {
			return errReturn(taskID, "ReleaseSegmentsRequest", err)
		}
		mlog.Info(context.TODO(), "ReleaseSegments", mlog.String("detail", fmt.Sprintf("+%v", loadReq)))
		return "ReleaseSegments", loadReq.PartitionIDs, loadReq.SegmentIDs, nil
	case commonpb.MsgType_WatchDmChannels:
		loadReq := &querypb.WatchDmChannelsRequest{}
		err = proto.Unmarshal([]byte(t), loadReq)
		if err != nil {
			return errReturn(taskID, "WatchDmChannelsRequest", err)
		}

		var partitionIDs []int64
		var segmentIDs []int64
		if loadReq.LoadMeta != nil {
			partitionIDs = append(partitionIDs, loadReq.LoadMeta.PartitionIDs...)
		}

		pids, sids := c.extractVchannelInfo(taskID, loadReq.Infos)
		partitionIDs = append(partitionIDs, pids...)
		segmentIDs = append(segmentIDs, sids...)
		pids, sids = c.extractDataSegmentInfos(taskID, loadReq.ExcludeInfos)
		partitionIDs = append(partitionIDs, pids...)
		segmentIDs = append(segmentIDs, sids...)
		mlog.Info(context.TODO(), "WatchDmChannels", mlog.String("detail", fmt.Sprintf("+%v", loadReq)))
		return "WatchDmChannels", removeRepeatElement(partitionIDs), removeRepeatElement(segmentIDs), nil
	case commonpb.MsgType_WatchDeltaChannels:
		mlog.Warn(context.TODO(), "legacy WatchDeltaChannels type found, ignore")
		return "WatchQueryChannels", emptyInt64(), emptyInt64(), nil
	case commonpb.MsgType_WatchQueryChannels:
		mlog.Warn(context.TODO(), "legacy WatchQueryChannels type found, ignore")
		return "WatchQueryChannels", emptyInt64(), emptyInt64(), nil
	case commonpb.MsgType_LoadBalanceSegments:
		loadReq := &querypb.LoadBalanceRequest{}
		err = proto.Unmarshal([]byte(t), loadReq)
		if err != nil {
			return errReturn(taskID, "LoadBalanceRequest", err)
		}
		mlog.Info(context.TODO(), "LoadBalanceSegments", mlog.String("detail", fmt.Sprintf("+%v", loadReq)))
		return "LoadBalanceSegments", emptyInt64(), loadReq.SealedSegmentIDs, nil
	case commonpb.MsgType_HandoffSegments:
		handoffReq := &querypb.HandoffSegmentsRequest{}
		err = proto.Unmarshal([]byte(t), handoffReq)
		if err != nil {
			return errReturn(taskID, "HandoffSegmentsRequest", err)
		}
		pids, sids := c.extractQuerySegmentInfos(taskID, handoffReq.SegmentInfos)
		mlog.Info(context.TODO(), "HandoffSegments", mlog.String("detail", fmt.Sprintf("+%v", handoffReq)))
		return "HandoffSegments", pids, sids, nil
	default:
		err = errors.New("inValid msg type when unMarshal task")
		mlog.Error(context.TODO(), "invalid message task", mlog.Int("type", int(header.Base.MsgType)), mlog.Err(err))
		return "", emptyInt64(), emptyInt64(), err
	}
}

func (c *mck) handleUserEnter(invalidTasks []int64) {
	if len(invalidTasks) > 0 {
		fmt.Print("Delete all invalid tasks, [Y/n]:")
		deleteAll := ""
		fmt.Scanln(&deleteAll)
		if deleteAll == "Y" {
			for _, invalidTask := range invalidTasks {
				if !c.removeTask(invalidTask) {
					continue
				}
				fmt.Println("Delete task id: ", invalidTask)
			}
		} else {
			deleteTask := ""
			idMap := int64Map(invalidTasks)
			fmt.Println("Enter 'Exit' to end")
			for len(idMap) > 0 {
				fmt.Print("Enter a invalid task id to delete, [Exit]:")
				fmt.Scanln(&deleteTask)
				if deleteTask == "Exit" {
					return
				}
				invalidTask, err := strconv.ParseInt(deleteTask, 10, 64)
				if err != nil {
					fmt.Println("Invalid task id.")
					continue
				}
				if _, ok := idMap[invalidTask]; !ok {
					fmt.Println("This is not a invalid task id.")
					continue
				}
				if !c.removeTask(invalidTask) {
					continue
				}
				delete(idMap, invalidTask)
				fmt.Println("Delete task id: ", invalidTask)
			}
		}
	}
}
