package ticktock

import (
	"context"
	"fmt"
	"strconv"

	"github.com/samber/lo"
	"gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
	"gitlink.org.cn/cloudream/common/pkgs/logger"
	"gitlink.org.cn/cloudream/common/utils/math2"
	"gitlink.org.cn/cloudream/common/utils/sort2"
	"gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
	clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
	"gitlink.org.cn/cloudream/jcs-pub/common/models/datamap"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/ops2"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitchlrc"
	lrcparser "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitchlrc/parser"
	cortypes "gitlink.org.cn/cloudream/jcs-pub/coordinator/types"
)

func (t *ChangeRedundancy) chooseRedundancy(ctx *changeRedundancyContext, obj clitypes.ObjectDetail) (clitypes.Redundancy, []*userSpaceLoadInfo) {
	switch obj.Object.Redundancy.(type) {
	case *clitypes.NoneRedundancy:
		if obj.Object.Size > ctx.ticktock.cfg.ECFileSizeThreshold {
			newStgs := t.chooseNewUserSpacesForEC(ctx, &clitypes.DefaultECRedundancy)
			return &clitypes.DefaultECRedundancy, newStgs
		}

		return &clitypes.DefaultRepRedundancy, t.chooseNewUserSpacesForRep(ctx, &clitypes.DefaultRepRedundancy)

	case *clitypes.RepRedundancy:
		if obj.Object.Size >= ctx.ticktock.cfg.ECFileSizeThreshold {
			newStgs := t.chooseNewUserSpacesForEC(ctx, &clitypes.DefaultECRedundancy)
			return &clitypes.DefaultECRedundancy, newStgs
		}

		return clitypes.DefaultRepRedundancy, t.rechooseUserSpacesForRep(ctx, &clitypes.DefaultRepRedundancy)

	case *clitypes.ECRedundancy:
		if obj.Object.Size < ctx.ticktock.cfg.ECFileSizeThreshold {
			return &clitypes.DefaultRepRedundancy, t.chooseNewUserSpacesForRep(ctx, &clitypes.DefaultRepRedundancy)
		}

		return clitypes.DefaultECRedundancy, t.rechooseUserSpacesForEC(ctx, obj, &clitypes.DefaultECRedundancy)

	case *clitypes.LRCRedundancy:
		newLRCStgs := t.rechooseUserSpacesForLRC(ctx, obj, &clitypes.DefaultLRCRedundancy)
		return &clitypes.DefaultLRCRedundancy, newLRCStgs
	}
	return nil, nil
}

func (t *ChangeRedundancy) doChangeRedundancy(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, newRed clitypes.Redundancy, selectedUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	log := logger.WithType[ChangeRedundancy]("TickTock")

	var updating *db.UpdatingObjectRedundancy
	var evt datamap.SysEventBody
	var err error

	switch srcRed := obj.Object.Redundancy.(type) {
	case *clitypes.NoneRedundancy:
		switch newRed := newRed.(type) {
		case *clitypes.RepRedundancy:
			log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> rep")
			updating, evt, err = t.noneToRep(ctx, obj, newRed, selectedUserSpaces)

		case *clitypes.ECRedundancy:
			log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> ec")
			updating, evt, err = t.noneToEC(ctx, obj, newRed, selectedUserSpaces)

		case *clitypes.LRCRedundancy:
			log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> lrc")
			updating, evt, err = t.noneToLRC(ctx, obj, newRed, selectedUserSpaces)

		case *clitypes.SegmentRedundancy:
			log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: none -> segment")
			updating, evt, err = t.noneToSeg(ctx, obj, newRed, selectedUserSpaces)
		}

	case *clitypes.RepRedundancy:
		switch newRed := newRed.(type) {
		case *clitypes.RepRedundancy:
			updating, evt, err = t.repToRep(ctx, obj, srcRed, selectedUserSpaces)

		case *clitypes.ECRedundancy:
			log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: rep -> ec")
			updating, evt, err = t.repToEC(ctx, obj, newRed, selectedUserSpaces)
		}

	case *clitypes.ECRedundancy:
		switch newRed := newRed.(type) {
		case *clitypes.RepRedundancy:
			log.WithField("ObjectID", obj.Object.ObjectID).Debugf("redundancy: ec -> rep")
			updating, evt, err = t.ecToRep(ctx, obj, srcRed, newRed, selectedUserSpaces)

		case *clitypes.ECRedundancy:
			updating, evt, err = t.ecToEC(ctx, obj, srcRed, newRed, selectedUserSpaces)
		}

	case *clitypes.LRCRedundancy:
		switch newRed := newRed.(type) {
		case *clitypes.LRCRedundancy:
			updating, evt, err = t.lrcToLRC(ctx, obj, srcRed, newRed, selectedUserSpaces)
		}
	}

	return updating, evt, err
}

// 统计每个对象块所在的节点，选出块最多的不超过userspaceCnt个节点
func (t *ChangeRedundancy) summaryRepObjectBlockUserSpaces(ctx *changeRedundancyContext, objs []clitypes.ObjectDetail, userspaceCnt int) []clitypes.UserSpaceID {
	type stgBlocks struct {
		UserSpaceID clitypes.UserSpaceID
		Count       int
	}

	stgBlocksMap := make(map[clitypes.UserSpaceID]*stgBlocks)
	for _, obj := range objs {
		shouldUseEC := obj.Object.Size > ctx.ticktock.cfg.ECFileSizeThreshold
		if _, ok := obj.Object.Redundancy.(*clitypes.RepRedundancy); ok && !shouldUseEC {
			for _, block := range obj.Blocks {
				if _, ok := stgBlocksMap[block.UserSpaceID]; !ok {
					stgBlocksMap[block.UserSpaceID] = &stgBlocks{
						UserSpaceID: block.UserSpaceID,
						Count:       0,
					}
				}
				stgBlocksMap[block.UserSpaceID].Count++
			}
		}
	}

	userspaces := lo.Values(stgBlocksMap)
	sort2.Sort(userspaces, func(left *stgBlocks, right *stgBlocks) int {
		return right.Count - left.Count
	})

	ids := lo.Map(userspaces, func(item *stgBlocks, idx int) clitypes.UserSpaceID { return item.UserSpaceID })
	if len(ids) > userspaceCnt {
		ids = ids[:userspaceCnt]
	}
	return ids
}

func (t *ChangeRedundancy) chooseNewUserSpacesForRep(ctx *changeRedundancyContext, red *clitypes.RepRedundancy) []*userSpaceLoadInfo {
	sortedUserSpaces := sort2.Sort(lo.Values(ctx.allUserSpaces), func(left *userSpaceLoadInfo, right *userSpaceLoadInfo) int {
		return sort2.Cmp(right.AccessAmount, left.AccessAmount)
	})

	return t.chooseSoManyUserSpaces(red.RepCount, sortedUserSpaces)
}

func (t *ChangeRedundancy) chooseNewUserSpacesForEC(ctx *changeRedundancyContext, red *clitypes.ECRedundancy) []*userSpaceLoadInfo {
	sortedUserSpaces := sort2.Sort(lo.Values(ctx.allUserSpaces), func(left *userSpaceLoadInfo, right *userSpaceLoadInfo) int {
		return sort2.Cmp(right.AccessAmount, left.AccessAmount)
	})

	return t.chooseSoManyUserSpaces(red.N, sortedUserSpaces)
}

func (t *ChangeRedundancy) chooseNewUserSpacesForLRC(ctx *changeRedundancyContext, red *clitypes.LRCRedundancy) []*userSpaceLoadInfo {
	sortedUserSpaces := sort2.Sort(lo.Values(ctx.allUserSpaces), func(left *userSpaceLoadInfo, right *userSpaceLoadInfo) int {
		return sort2.Cmp(right.AccessAmount, left.AccessAmount)
	})

	return t.chooseSoManyUserSpaces(red.N, sortedUserSpaces)
}

func (t *ChangeRedundancy) chooseNewUserSpacesForSeg(ctx *changeRedundancyContext, segCount int) []*userSpaceLoadInfo {
	sortedUserSpaces := sort2.Sort(lo.Values(ctx.allUserSpaces), func(left *userSpaceLoadInfo, right *userSpaceLoadInfo) int {
		return sort2.Cmp(right.AccessAmount, left.AccessAmount)
	})

	return t.chooseSoManyUserSpaces(segCount, sortedUserSpaces)
}

func (t *ChangeRedundancy) rechooseUserSpacesForRep(ctx *changeRedundancyContext, red *clitypes.RepRedundancy) []*userSpaceLoadInfo {
	type rechooseUserSpace struct {
		*userSpaceLoadInfo
		HasBlock bool
	}

	var rechooseStgs []*rechooseUserSpace
	for _, stg := range ctx.allUserSpaces {
		hasBlock := false
		for _, id := range ctx.mostBlockStgIDs {
			if id == stg.UserSpace.UserSpace.UserSpaceID {
				hasBlock = true
				break
			}
		}

		rechooseStgs = append(rechooseStgs, &rechooseUserSpace{
			userSpaceLoadInfo: stg,
			HasBlock:          hasBlock,
		})
	}

	sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseUserSpace, right *rechooseUserSpace) int {
		// 已经缓存了文件块的节点优先选择
		v := sort2.CmpBool(right.HasBlock, left.HasBlock)
		if v != 0 {
			return v
		}

		return sort2.Cmp(right.AccessAmount, left.AccessAmount)
	})

	return t.chooseSoManyUserSpaces(red.RepCount, lo.Map(sortedStgs, func(userspace *rechooseUserSpace, idx int) *userSpaceLoadInfo { return userspace.userSpaceLoadInfo }))
}

func (t *ChangeRedundancy) rechooseUserSpacesForEC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.ECRedundancy) []*userSpaceLoadInfo {
	type rechooseStg struct {
		*userSpaceLoadInfo
		CachedBlockIndex int
	}

	var rechooseStgs []*rechooseStg
	for _, stg := range ctx.allUserSpaces {
		cachedBlockIndex := -1
		for _, block := range obj.Blocks {
			if block.UserSpaceID == stg.UserSpace.UserSpace.UserSpaceID {
				cachedBlockIndex = block.Index
				break
			}
		}

		rechooseStgs = append(rechooseStgs, &rechooseStg{
			userSpaceLoadInfo: stg,
			CachedBlockIndex:  cachedBlockIndex,
		})
	}

	sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseStg, right *rechooseStg) int {
		// 已经缓存了文件块的节点优先选择
		v := sort2.CmpBool(right.CachedBlockIndex > -1, left.CachedBlockIndex > -1)
		if v != 0 {
			return v
		}

		return sort2.Cmp(right.AccessAmount, left.AccessAmount)
	})

	// TODO 可以考虑选择已有块的节点时，能依然按照Index顺序选择
	return t.chooseSoManyUserSpaces(red.N, lo.Map(sortedStgs, func(userspace *rechooseStg, idx int) *userSpaceLoadInfo { return userspace.userSpaceLoadInfo }))
}

func (t *ChangeRedundancy) rechooseUserSpacesForLRC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.LRCRedundancy) []*userSpaceLoadInfo {
	type rechooseStg struct {
		*userSpaceLoadInfo
		CachedBlockIndex int
	}

	var rechooseStgs []*rechooseStg
	for _, stg := range ctx.allUserSpaces {
		cachedBlockIndex := -1
		for _, block := range obj.Blocks {
			if block.UserSpaceID == stg.UserSpace.UserSpace.UserSpaceID {
				cachedBlockIndex = block.Index
				break
			}
		}

		rechooseStgs = append(rechooseStgs, &rechooseStg{
			userSpaceLoadInfo: stg,
			CachedBlockIndex:  cachedBlockIndex,
		})
	}

	sortedStgs := sort2.Sort(rechooseStgs, func(left *rechooseStg, right *rechooseStg) int {
		// 已经缓存了文件块的节点优先选择
		v := sort2.CmpBool(right.CachedBlockIndex > -1, left.CachedBlockIndex > -1)
		if v != 0 {
			return v
		}

		return sort2.Cmp(right.AccessAmount, left.AccessAmount)
	})

	// TODO 可以考虑选择已有块的节点时，能依然按照Index顺序选择
	return t.chooseSoManyUserSpaces(red.N, lo.Map(sortedStgs, func(userspace *rechooseStg, idx int) *userSpaceLoadInfo { return userspace.userSpaceLoadInfo }))
}

func (t *ChangeRedundancy) chooseSoManyUserSpaces(count int, stgs []*userSpaceLoadInfo) []*userSpaceLoadInfo {
	repeateCount := (count + len(stgs) - 1) / len(stgs)
	extendStgs := make([]*userSpaceLoadInfo, repeateCount*len(stgs))

	// 使用复制的方式将节点数扩充到要求的数量
	// 复制之后的结构：ABCD -> AAABBBCCCDDD
	for p := 0; p < repeateCount; p++ {
		for i, userspace := range stgs {
			putIdx := i*repeateCount + p
			extendStgs[putIdx] = userspace
		}
	}
	extendStgs = extendStgs[:count]

	var chosen []*userSpaceLoadInfo
	for len(chosen) < count {
		// 在每一轮内都选不同地区的节点，如果节点数不够，那么就再来一轮
		chosenLocations := make(map[cortypes.LocationID]bool)
		for i, stg := range extendStgs {
			if stg == nil {
				continue
			}

			if chosenLocations[stg.UserSpace.MasterHub.LocationID] {
				continue
			}

			chosen = append(chosen, stg)
			chosenLocations[stg.UserSpace.MasterHub.LocationID] = true
			extendStgs[i] = nil
		}
	}

	return chosen
}

func (t *ChangeRedundancy) noneToRep(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.RepRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	if len(obj.Blocks) == 0 {
		return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to rep")
	}

	srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID]
	if !ok {
		return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID)
	}
	if srcStg.UserSpace.MasterHub == nil {
		return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID)
	}

	// 如果选择的备份节点都是同一个，那么就只要上传一次
	uploadStgs = lo.UniqBy(uploadStgs, func(item *userSpaceLoadInfo) clitypes.UserSpaceID { return item.UserSpace.UserSpace.UserSpaceID })

	ft := ioswitch2.NewFromTo()
	ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, ioswitch2.RawStream()))
	for i, stg := range uploadStgs {
		ft.AddTo(ioswitch2.NewToShardStore(*stg.UserSpace.MasterHub, *stg.UserSpace, ioswitch2.RawStream(), fmt.Sprintf("%d", i)))
	}

	plans := exec.NewPlanBuilder()
	err := parser.Parse(ft, plans)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing plan: %w", err)
	}

	// TODO 添加依赖
	execCtx := exec.NewExecContext()
	exec.SetValueByType(execCtx, ctx.ticktock.stgPool)
	ret, err := plans.Execute(execCtx).Wait(context.Background())
	if err != nil {
		return nil, nil, fmt.Errorf("executing io plan: %w", err)
	}

	var blocks []clitypes.ObjectBlock
	var blockChgs []datamap.BlockChange
	for i, stg := range uploadStgs {
		r := ret[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue)
		blocks = append(blocks, clitypes.ObjectBlock{
			ObjectID:    obj.Object.ObjectID,
			Index:       0,
			UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
			FileHash:    r.Hash,
			Size:        r.Size,
		})
		blockChgs = append(blockChgs, &datamap.BlockChangeClone{
			BlockType:         datamap.BlockTypeRaw,
			SourceUserSpaceID: obj.Blocks[0].UserSpaceID,
			TargetUserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
			TransferBytes:     1,
		})
	}

	// 删除原本的文件块
	blockChgs = append(blockChgs, &datamap.BlockChangeDeleted{
		Index:       0,
		UserSpaceID: obj.Blocks[0].UserSpaceID,
	})

	return &db.UpdatingObjectRedundancy{
			ObjectID:   obj.Object.ObjectID,
			FileHash:   obj.Object.FileHash,
			Size:       obj.Object.Size,
			Redundancy: red,
			Blocks:     blocks,
		}, &datamap.BodyBlockTransfer{
			ObjectID:     obj.Object.ObjectID,
			PackageID:    obj.Object.PackageID,
			BlockChanges: blockChgs,
		}, nil
}

func (t *ChangeRedundancy) noneToEC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.ECRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	if len(obj.Blocks) == 0 {
		return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to ec")
	}

	srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID]
	if !ok {
		return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID)
	}
	if srcStg.UserSpace.MasterHub == nil {
		return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID)
	}

	ft := ioswitch2.NewFromTo()
	ft.ECParam = red
	ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, ioswitch2.RawStream()))
	for i := 0; i < red.N; i++ {
		ft.AddTo(ioswitch2.NewToShardStore(*uploadStgs[i].UserSpace.MasterHub, *uploadStgs[i].UserSpace, ioswitch2.ECStream(i), fmt.Sprintf("%d", i)))
	}
	plans := exec.NewPlanBuilder()
	err := parser.Parse(ft, plans)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing plan: %w", err)
	}

	execCtx := exec.NewExecContext()
	exec.SetValueByType(execCtx, ctx.ticktock.stgPool)
	ioRet, err := plans.Execute(execCtx).Wait(context.Background())
	if err != nil {
		return nil, nil, fmt.Errorf("executing io plan: %w", err)
	}

	var blocks []clitypes.ObjectBlock
	var evtTargetBlocks []datamap.Block
	var evtBlockTrans []datamap.DataTransfer
	for i := 0; i < red.N; i++ {
		r := ioRet[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue)
		blocks = append(blocks, clitypes.ObjectBlock{
			ObjectID:    obj.Object.ObjectID,
			Index:       i,
			UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
			FileHash:    r.Hash,
			Size:        r.Size,
		})
		evtTargetBlocks = append(evtTargetBlocks, datamap.Block{
			BlockType:   datamap.BlockTypeEC,
			Index:       i,
			UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
		})
		evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{
			SourceUserSpaceID: obj.Blocks[0].UserSpaceID,
			TargetUserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
			TransferBytes:     1,
		})
	}

	return &db.UpdatingObjectRedundancy{
			ObjectID:   obj.Object.ObjectID,
			FileHash:   obj.Object.FileHash,
			Size:       obj.Object.Size,
			Redundancy: red,
			Blocks:     blocks,
		},
		&datamap.BodyBlockTransfer{
			ObjectID:  obj.Object.ObjectID,
			PackageID: obj.Object.PackageID,
			BlockChanges: []datamap.BlockChange{
				&datamap.BlockChangeEnDecode{
					SourceBlocks: []datamap.Block{{
						BlockType:   datamap.BlockTypeRaw,
						UserSpaceID: obj.Blocks[0].UserSpaceID,
					}},
					TargetBlocks:  evtTargetBlocks,
					DataTransfers: evtBlockTrans,
				},

				// 删除原本的文件块
				&datamap.BlockChangeDeleted{
					Index:       0,
					UserSpaceID: obj.Blocks[0].UserSpaceID,
				},
			},
		}, nil
}

func (t *ChangeRedundancy) noneToLRC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.LRCRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	if len(obj.Blocks) == 0 {
		return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to ec")
	}

	srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID]
	if !ok {
		return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID)
	}
	if srcStg.UserSpace.MasterHub == nil {
		return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID)
	}

	var toes []ioswitchlrc.To
	for i := 0; i < red.N; i++ {
		toes = append(toes, ioswitchlrc.NewToStorage(*uploadStgs[i].UserSpace.MasterHub, *uploadStgs[i].UserSpace, i, fmt.Sprintf("%d", i)))
	}

	plans := exec.NewPlanBuilder()
	err := lrcparser.Encode(ioswitchlrc.NewFromStorage(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, -1), toes, plans)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing plan: %w", err)
	}

	execCtx := exec.NewExecContext()
	exec.SetValueByType(execCtx, ctx.ticktock.stgPool)
	ioRet, err := plans.Execute(execCtx).Wait(context.Background())
	if err != nil {
		return nil, nil, fmt.Errorf("executing io plan: %w", err)
	}

	var blocks []clitypes.ObjectBlock
	var evtTargetBlocks []datamap.Block
	var evtBlockTrans []datamap.DataTransfer
	for i := 0; i < red.N; i++ {
		r := ioRet[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue)
		blocks = append(blocks, clitypes.ObjectBlock{
			ObjectID:    obj.Object.ObjectID,
			Index:       i,
			UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
			FileHash:    r.Hash,
			Size:        r.Size,
		})
		evtTargetBlocks = append(evtTargetBlocks, datamap.Block{
			BlockType:   datamap.BlockTypeEC,
			Index:       i,
			UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
		})
		evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{
			SourceUserSpaceID: obj.Blocks[0].UserSpaceID,
			TargetUserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
			TransferBytes:     1,
		})
	}

	return &db.UpdatingObjectRedundancy{
			ObjectID:   obj.Object.ObjectID,
			FileHash:   obj.Object.FileHash,
			Size:       obj.Object.Size,
			Redundancy: red,
			Blocks:     blocks,
		},
		&datamap.BodyBlockTransfer{
			ObjectID:  obj.Object.ObjectID,
			PackageID: obj.Object.PackageID,
			BlockChanges: []datamap.BlockChange{
				&datamap.BlockChangeEnDecode{
					SourceBlocks: []datamap.Block{{
						BlockType:   datamap.BlockTypeRaw,
						UserSpaceID: obj.Blocks[0].UserSpaceID,
					}},
					TargetBlocks:  evtTargetBlocks,
					DataTransfers: evtBlockTrans,
				},

				// 删除原本的文件块
				&datamap.BlockChangeDeleted{
					Index:       0,
					UserSpaceID: obj.Blocks[0].UserSpaceID,
				},
			},
		},
		nil
}

func (t *ChangeRedundancy) noneToSeg(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.SegmentRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	if len(obj.Blocks) == 0 {
		return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to rep")
	}

	srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID]
	if !ok {
		return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID)
	}
	if srcStg.UserSpace.MasterHub == nil {
		return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID)
	}

	// 如果选择的备份节点都是同一个，那么就只要上传一次
	uploadStgs = lo.UniqBy(uploadStgs, func(item *userSpaceLoadInfo) clitypes.UserSpaceID { return item.UserSpace.UserSpace.UserSpaceID })

	ft := ioswitch2.NewFromTo()
	ft.SegmentParam = red
	ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, ioswitch2.RawStream()))
	for i, stg := range uploadStgs {
		ft.AddTo(ioswitch2.NewToShardStore(*stg.UserSpace.MasterHub, *stg.UserSpace, ioswitch2.SegmentStream(i), fmt.Sprintf("%d", i)))
	}

	plans := exec.NewPlanBuilder()
	err := parser.Parse(ft, plans)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing plan: %w", err)
	}

	// TODO 添加依赖
	execCtx := exec.NewExecContext()
	exec.SetValueByType(execCtx, ctx.ticktock.stgPool)
	ret, err := plans.Execute(execCtx).Wait(context.Background())
	if err != nil {
		return nil, nil, fmt.Errorf("executing io plan: %w", err)
	}

	var blocks []clitypes.ObjectBlock
	var evtTargetBlocks []datamap.Block
	var evtBlockTrans []datamap.DataTransfer
	for i, stg := range uploadStgs {
		r := ret[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue)
		blocks = append(blocks, clitypes.ObjectBlock{
			ObjectID:    obj.Object.ObjectID,
			Index:       i,
			UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
			FileHash:    r.Hash,
			Size:        r.Size,
		})
		evtTargetBlocks = append(evtTargetBlocks, datamap.Block{
			BlockType:   datamap.BlockTypeSegment,
			Index:       i,
			UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
		})
		evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{
			SourceUserSpaceID: obj.Blocks[0].UserSpaceID,
			TargetUserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
			TransferBytes:     1,
		})
	}

	return &db.UpdatingObjectRedundancy{
			ObjectID:   obj.Object.ObjectID,
			FileHash:   obj.Object.FileHash,
			Size:       obj.Object.Size,
			Redundancy: red,
			Blocks:     blocks,
		},
		&datamap.BodyBlockTransfer{
			ObjectID:  obj.Object.ObjectID,
			PackageID: obj.Object.PackageID,
			BlockChanges: []datamap.BlockChange{
				&datamap.BlockChangeEnDecode{
					SourceBlocks: []datamap.Block{{
						BlockType:   datamap.BlockTypeRaw,
						UserSpaceID: obj.Blocks[0].UserSpaceID,
					}},
					TargetBlocks:  evtTargetBlocks,
					DataTransfers: evtBlockTrans,
				},

				// 删除原本的文件块
				&datamap.BlockChangeDeleted{
					Index:       0,
					UserSpaceID: obj.Blocks[0].UserSpaceID,
				},
			},
		},
		nil
}

func (t *ChangeRedundancy) repToRep(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.RepRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	if len(obj.Blocks) == 0 {
		return nil, nil, fmt.Errorf("object is not cached on any userspaces, cannot change its redundancy to rep")
	}

	srcStg, ok := ctx.allUserSpaces[obj.Blocks[0].UserSpaceID]
	if !ok {
		return nil, nil, fmt.Errorf("userspace %v not found", obj.Blocks[0].UserSpaceID)
	}
	if srcStg.UserSpace.MasterHub == nil {
		return nil, nil, fmt.Errorf("userspace %v has no master hub", obj.Blocks[0].UserSpaceID)
	}

	// 如果选择的备份节点都是同一个，那么就只要上传一次
	uploadStgs = lo.UniqBy(uploadStgs, func(item *userSpaceLoadInfo) clitypes.UserSpaceID { return item.UserSpace.UserSpace.UserSpaceID })

	ft := ioswitch2.NewFromTo()
	ft.AddFrom(ioswitch2.NewFromShardstore(obj.Object.FileHash, *srcStg.UserSpace.MasterHub, *srcStg.UserSpace, ioswitch2.RawStream()))
	for i, stg := range uploadStgs {
		ft.AddTo(ioswitch2.NewToShardStore(*stg.UserSpace.MasterHub, *stg.UserSpace, ioswitch2.RawStream(), fmt.Sprintf("%d", i)))
	}

	plans := exec.NewPlanBuilder()
	err := parser.Parse(ft, plans)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing plan: %w", err)
	}

	// TODO 添加依赖
	execCtx := exec.NewExecContext()
	exec.SetValueByType(execCtx, ctx.ticktock.stgPool)
	ret, err := plans.Execute(execCtx).Wait(context.Background())
	if err != nil {
		return nil, nil, fmt.Errorf("executing io plan: %w", err)
	}

	var blocks []clitypes.ObjectBlock
	var blockChgs []datamap.BlockChange
	for i, stg := range uploadStgs {
		r := ret[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue)
		blocks = append(blocks, clitypes.ObjectBlock{
			ObjectID:    obj.Object.ObjectID,
			Index:       0,
			UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
			FileHash:    r.Hash,
			Size:        r.Size,
		})
		blockChgs = append(blockChgs, &datamap.BlockChangeClone{
			BlockType:         datamap.BlockTypeRaw,
			SourceUserSpaceID: obj.Blocks[0].UserSpaceID,
			TargetUserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
			TransferBytes:     1,
		})
	}

	// 删除原本的文件块
	blockChgs = append(blockChgs, &datamap.BlockChangeDeleted{
		Index:       0,
		UserSpaceID: obj.Blocks[0].UserSpaceID,
	})

	return &db.UpdatingObjectRedundancy{
			ObjectID:   obj.Object.ObjectID,
			FileHash:   obj.Object.FileHash,
			Size:       obj.Object.Size,
			Redundancy: red,
			Blocks:     blocks,
		},
		&datamap.BodyBlockTransfer{
			ObjectID:     obj.Object.ObjectID,
			PackageID:    obj.Object.PackageID,
			BlockChanges: blockChgs,
		},
		nil
}

func (t *ChangeRedundancy) repToEC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, red *clitypes.ECRedundancy, uploadUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	return t.noneToEC(ctx, obj, red, uploadUserSpaces)
}

func (t *ChangeRedundancy) ecToRep(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, srcRed *clitypes.ECRedundancy, tarRed *clitypes.RepRedundancy, uploadStgs []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	var chosenBlocks []clitypes.GrouppedObjectBlock
	var chosenBlockIndexes []int
	var chosenBlockStg []clitypes.UserSpaceDetail
	for _, block := range obj.GroupBlocks() {
		if len(block.UserSpaceIDs) > 0 {
			// TODO 考虑选择最优的节点
			stg, ok := ctx.allUserSpaces[block.UserSpaceIDs[0]]
			if !ok {
				continue
			}
			if stg.UserSpace.MasterHub == nil {
				continue
			}

			chosenBlocks = append(chosenBlocks, block)
			chosenBlockIndexes = append(chosenBlockIndexes, block.Index)
			chosenBlockStg = append(chosenBlockStg, *stg.UserSpace)
		}

		if len(chosenBlocks) == srcRed.K {
			break
		}
	}

	if len(chosenBlocks) < srcRed.K {
		return nil, nil, fmt.Errorf("no enough blocks to reconstruct the original file data")
	}

	// 如果选择的备份节点都是同一个，那么就只要上传一次
	uploadStgs = lo.UniqBy(uploadStgs, func(item *userSpaceLoadInfo) clitypes.UserSpaceID { return item.UserSpace.UserSpace.UserSpaceID })

	planBlder := exec.NewPlanBuilder()
	ft := ioswitch2.NewFromTo()
	ft.ECParam = srcRed

	for i, block := range chosenBlocks {
		ft.AddFrom(ioswitch2.NewFromShardstore(block.FileHash, *chosenBlockStg[i].MasterHub, chosenBlockStg[i], ioswitch2.ECStream(block.Index)))
	}

	for i := range uploadStgs {
		ft.AddTo(ioswitch2.NewToShardStoreWithRange(*uploadStgs[i].UserSpace.MasterHub, *uploadStgs[i].UserSpace, ioswitch2.RawStream(), fmt.Sprintf("%d", i), math2.NewRange(0, obj.Object.Size)))
	}

	err := parser.Parse(ft, planBlder)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing plan: %w", err)
	}

	// TODO 添加依赖
	execCtx := exec.NewExecContext()
	exec.SetValueByType(execCtx, ctx.ticktock.stgPool)
	ioRet, err := planBlder.Execute(execCtx).Wait(context.Background())
	if err != nil {
		return nil, nil, fmt.Errorf("executing io plan: %w", err)
	}

	var blocks []clitypes.ObjectBlock

	for i := range uploadStgs {
		r := ioRet[fmt.Sprintf("%d", i)].(*ops2.ShardInfoValue)
		blocks = append(blocks, clitypes.ObjectBlock{
			ObjectID:    obj.Object.ObjectID,
			Index:       0,
			UserSpaceID: uploadStgs[i].UserSpace.UserSpace.UserSpaceID,
			FileHash:    r.Hash,
			Size:        r.Size,
		})
	}

	var evtSrcBlocks []datamap.Block
	var evtTargetBlocks []datamap.Block
	for i2, block := range chosenBlocks {
		evtSrcBlocks = append(evtSrcBlocks, datamap.Block{
			BlockType:   datamap.BlockTypeEC,
			Index:       block.Index,
			UserSpaceID: chosenBlockStg[i2].UserSpace.UserSpaceID,
		})
	}

	for _, stg := range uploadStgs {
		evtTargetBlocks = append(evtTargetBlocks, datamap.Block{
			BlockType:   datamap.BlockTypeRaw,
			Index:       0,
			UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
		})
	}

	var evtBlockTrans []datamap.DataTransfer
	for _, stg := range uploadStgs {
		for i2 := range chosenBlocks {
			evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{
				SourceUserSpaceID: chosenBlockStg[i2].UserSpace.UserSpaceID,
				TargetUserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
				TransferBytes:     1,
			})
		}
	}

	var blockChgs []datamap.BlockChange
	blockChgs = append(blockChgs, &datamap.BlockChangeEnDecode{
		SourceBlocks:  evtSrcBlocks,
		TargetBlocks:  evtTargetBlocks,
		DataTransfers: evtBlockTrans,
	})

	for _, block := range obj.Blocks {
		blockChgs = append(blockChgs, &datamap.BlockChangeDeleted{
			Index:       block.Index,
			UserSpaceID: block.UserSpaceID,
		})
	}

	return &db.UpdatingObjectRedundancy{
			ObjectID:   obj.Object.ObjectID,
			FileHash:   obj.Object.FileHash,
			Size:       obj.Object.Size,
			Redundancy: tarRed,
			Blocks:     blocks,
		},
		&datamap.BodyBlockTransfer{
			ObjectID:     obj.Object.ObjectID,
			PackageID:    obj.Object.PackageID,
			BlockChanges: blockChgs,
		},
		nil
}

func (t *ChangeRedundancy) ecToEC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, srcRed *clitypes.ECRedundancy, tarRed *clitypes.ECRedundancy, uploadUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	grpBlocks := obj.GroupBlocks()

	var chosenBlocks []clitypes.GrouppedObjectBlock
	var chosenBlockStg []clitypes.UserSpaceDetail
	for _, block := range grpBlocks {
		if len(block.UserSpaceIDs) > 0 {
			stg, ok := ctx.allUserSpaces[block.UserSpaceIDs[0]]
			if !ok {
				continue
			}
			if stg.UserSpace.MasterHub == nil {
				continue
			}

			chosenBlocks = append(chosenBlocks, block)
			chosenBlockStg = append(chosenBlockStg, *stg.UserSpace)
		}

		if len(chosenBlocks) == srcRed.K {
			break
		}
	}

	if len(chosenBlocks) < srcRed.K {
		return nil, nil, fmt.Errorf("no enough blocks to reconstruct the original file data")
	}

	// 目前EC的参数都相同，所以可以不用重建出完整数据然后再分块，可以直接构建出目的节点需要的块
	planBlder := exec.NewPlanBuilder()

	var evtSrcBlocks []datamap.Block
	var evtTargetBlocks []datamap.Block

	ft := ioswitch2.NewFromTo()
	ft.ECParam = srcRed

	for i, block := range chosenBlocks {
		ft.AddFrom(ioswitch2.NewFromShardstore(block.FileHash, *chosenBlockStg[i].MasterHub, chosenBlockStg[i], ioswitch2.ECStream(block.Index)))

		evtSrcBlocks = append(evtSrcBlocks, datamap.Block{
			BlockType:   datamap.BlockTypeEC,
			Index:       block.Index,
			UserSpaceID: chosenBlockStg[i].UserSpace.UserSpaceID,
		})
	}

	var newBlocks []clitypes.ObjectBlock
	shouldUpdateBlocks := false
	for i, stg := range uploadUserSpaces {
		newBlock := clitypes.ObjectBlock{
			ObjectID:    obj.Object.ObjectID,
			Index:       i,
			UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
		}

		grp, ok := lo.Find(grpBlocks, func(grp clitypes.GrouppedObjectBlock) bool { return grp.Index == i })

		// 如果新选中的节点已经记录在Block表中，那么就不需要任何变更
		if ok && lo.Contains(grp.UserSpaceIDs, stg.UserSpace.UserSpace.UserSpaceID) {
			newBlock.FileHash = grp.FileHash
			newBlock.Size = grp.Size
			newBlocks = append(newBlocks, newBlock)
			continue
		}

		shouldUpdateBlocks = true

		// 否则就要重建出这个节点需要的块
		// 输出只需要自己要保存的那一块
		ft.AddTo(ioswitch2.NewToShardStore(*stg.UserSpace.MasterHub, *stg.UserSpace, ioswitch2.ECStream(i), fmt.Sprintf("%d", i)))

		evtTargetBlocks = append(evtTargetBlocks, datamap.Block{
			BlockType:   datamap.BlockTypeEC,
			Index:       i,
			UserSpaceID: stg.UserSpace.UserSpace.UserSpaceID,
		})

		newBlocks = append(newBlocks, newBlock)
	}

	err := parser.Parse(ft, planBlder)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing plan: %w", err)
	}

	// 如果没有任何Plan，Wait会直接返回成功
	execCtx := exec.NewExecContext()
	exec.SetValueByType(execCtx, ctx.ticktock.stgPool)
	ret, err := planBlder.Execute(execCtx).Wait(context.Background())
	if err != nil {
		return nil, nil, fmt.Errorf("executing io plan: %w", err)
	}

	if !shouldUpdateBlocks {
		return nil, nil, nil
	}

	for k, v := range ret {
		idx, err := strconv.ParseInt(k, 10, 64)
		if err != nil {
			return nil, nil, fmt.Errorf("parsing result key %s as index: %w", k, err)
		}

		r := v.(*ops2.ShardInfoValue)
		newBlocks[idx].FileHash = r.Hash
		newBlocks[idx].Size = r.Size
	}

	var evtBlockTrans []datamap.DataTransfer
	for _, src := range evtSrcBlocks {
		for _, tar := range evtTargetBlocks {
			evtBlockTrans = append(evtBlockTrans, datamap.DataTransfer{
				SourceUserSpaceID: src.UserSpaceID,
				TargetUserSpaceID: tar.UserSpaceID,
				TransferBytes:     1,
			})
		}
	}

	var blockChgs []datamap.BlockChange
	for _, block := range obj.Blocks {
		keep := lo.ContainsBy(newBlocks, func(newBlock clitypes.ObjectBlock) bool {
			return newBlock.Index == block.Index && newBlock.UserSpaceID == block.UserSpaceID
		})
		if !keep {
			blockChgs = append(blockChgs, &datamap.BlockChangeDeleted{
				Index:       block.Index,
				UserSpaceID: block.UserSpaceID,
			})
		}
	}
	blockChgs = append(blockChgs, &datamap.BlockChangeEnDecode{
		SourceBlocks:  evtSrcBlocks,
		TargetBlocks:  evtTargetBlocks,
		DataTransfers: evtBlockTrans,
	})

	return &db.UpdatingObjectRedundancy{
			ObjectID:   obj.Object.ObjectID,
			FileHash:   obj.Object.FileHash,
			Size:       obj.Object.Size,
			Redundancy: tarRed,
			Blocks:     newBlocks,
		},
		&datamap.BodyBlockTransfer{
			ObjectID:     obj.Object.ObjectID,
			PackageID:    obj.Object.PackageID,
			BlockChanges: blockChgs,
		},
		nil
}

func (t *ChangeRedundancy) lrcToLRC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, srcRed *clitypes.LRCRedundancy, tarRed *clitypes.LRCRedundancy, uploadUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {

	blocksGrpByIndex := obj.GroupBlocks()

	var lostBlocks []int
	var lostBlockGrps []int
	canGroupReconstruct := true

	allBlockFlags := make([]bool, srcRed.N)
	for _, block := range blocksGrpByIndex {
		allBlockFlags[block.Index] = true
	}

	for i, ok := range allBlockFlags {
		grpID := srcRed.FindGroup(i)
		if !ok {
			if grpID == -1 {
				canGroupReconstruct = false
				break
			}

			if len(lostBlocks) > 0 && lostBlockGrps[len(lostBlockGrps)-1] == grpID {
				canGroupReconstruct = false
				break
			}

			lostBlocks = append(lostBlocks, i)
			lostBlockGrps = append(lostBlockGrps, grpID)
		}
	}

	// TODO 产生BlockTransfer事件

	if canGroupReconstruct {
		// 	return t.groupReconstructLRC(obj, lostBlocks, lostBlockGrps, blocksGrpByIndex, srcRed, uploadUserSpaces)
	}

	return t.reconstructLRC(ctx, obj, blocksGrpByIndex, srcRed, uploadUserSpaces)
}

/*
TODO2 修复这一块的代码

	func (t *ChangeRedundancy) groupReconstructLRC(obj clitypes.ObjectDetail, lostBlocks []int, lostBlockGrps []int, grpedBlocks []clitypes.GrouppedObjectBlock, red *clitypes.LRCRedundancy, uploadUserSpaces []*UserSpaceLoadInfo) (*db.UpdatingObjectRedundancy, error) {
		grped := make(map[int]clitypes.GrouppedObjectBlock)
		for _, b := range grpedBlocks {
			grped[b.Index] = b
		}

		plans := exec.NewPlanBuilder()

		for i := 0; i < len(lostBlocks); i++ {
			var froms []ioswitchlrc.From
			grpEles := red.GetGroupElements(lostBlockGrps[i])
			for _, ele := range grpEles {
				if ele == lostBlocks[i] {
					continue
				}

				froms = append(froms, ioswitchlrc.NewFromUserSpace(grped[ele].FileHash, nil, ele))
			}

			err := lrcparser.ReconstructGroup(froms, []ioswitchlrc.To{
				ioswitchlrc.NewToUserSpace(uploadUserSpaces[i].UserSpace, lostBlocks[i], fmt.Sprintf("%d", lostBlocks[i])),
			}, plans)
			if err != nil {
				return nil, fmt.Errorf("parsing plan: %w", err)
			}
		}

		fmt.Printf("plans: %v\n", plans)

		// 如果没有任何Plan，Wait会直接返回成功
		// TODO 添加依赖
		ret, err := plans.Execute(exec.NewExecContext()).Wait(context.TODO())
		if err != nil {
			return nil, fmt.Errorf("executing io plan: %w", err)
		}

		var newBlocks []clitypes.ObjectBlock
		for _, i := range lostBlocks {
			newBlocks = append(newBlocks, clitypes.ObjectBlock{
				ObjectID:  obj.Object.ObjectID,
				Index:     i,
				UserSpaceID: uploadUserSpaces[i].UserSpace.UserSpace.UserSpaceID,
				FileHash:  ret[fmt.Sprintf("%d", i)].(*ops2.FileHashValue).Hash,
			})
		}
		for _, b := range grpedBlocks {
			for _, hubID := range b.UserSpaceIDs {
				newBlocks = append(newBlocks, clitypes.ObjectBlock{
					ObjectID:  obj.Object.ObjectID,
					Index:     b.Index,
					UserSpaceID: hubID,
					FileHash:  b.FileHash,
				})
			}
		}

		return &db.UpdatingObjectRedundancy{
			ObjectID:   obj.Object.ObjectID,
			Redundancy: red,
			Blocks:     newBlocks,
		}, nil
	}
*/
func (t *ChangeRedundancy) reconstructLRC(ctx *changeRedundancyContext, obj clitypes.ObjectDetail, grpBlocks []clitypes.GrouppedObjectBlock, red *clitypes.LRCRedundancy, uploadUserSpaces []*userSpaceLoadInfo) (*db.UpdatingObjectRedundancy, datamap.SysEventBody, error) {
	var chosenBlocks []clitypes.GrouppedObjectBlock
	var chosenBlockStg []clitypes.UserSpaceDetail

	for _, block := range grpBlocks {
		if len(block.UserSpaceIDs) > 0 && block.Index < red.M() {
			stg, ok := ctx.allUserSpaces[block.UserSpaceIDs[0]]
			if !ok {
				continue
			}
			if stg.UserSpace.MasterHub == nil {
				continue
			}

			chosenBlocks = append(chosenBlocks, block)
			chosenBlockStg = append(chosenBlockStg, *stg.UserSpace)
		}

		if len(chosenBlocks) == red.K {
			break
		}
	}

	if len(chosenBlocks) < red.K {
		return nil, nil, fmt.Errorf("no enough blocks to reconstruct the original file data")
	}

	// 目前LRC的参数都相同，所以可以不用重建出完整数据然后再分块，可以直接构建出目的节点需要的块
	planBlder := exec.NewPlanBuilder()

	var froms []ioswitchlrc.From
	var toes []ioswitchlrc.To
	var newBlocks []clitypes.ObjectBlock
	shouldUpdateBlocks := false
	for i, userspace := range uploadUserSpaces {
		newBlock := clitypes.ObjectBlock{
			ObjectID:    obj.Object.ObjectID,
			Index:       i,
			UserSpaceID: userspace.UserSpace.UserSpace.UserSpaceID,
		}

		grp, ok := lo.Find(grpBlocks, func(grp clitypes.GrouppedObjectBlock) bool { return grp.Index == i })

		// 如果新选中的节点已经记录在Block表中，那么就不需要任何变更
		if ok && lo.Contains(grp.UserSpaceIDs, userspace.UserSpace.UserSpace.UserSpaceID) {
			newBlock.FileHash = grp.FileHash
			newBlock.Size = grp.Size
			newBlocks = append(newBlocks, newBlock)
			continue
		}

		shouldUpdateBlocks = true

		// 否则就要重建出这个节点需要的块

		for i2, block := range chosenBlocks {
			froms = append(froms, ioswitchlrc.NewFromStorage(block.FileHash, *chosenBlockStg[i2].MasterHub, chosenBlockStg[i2], block.Index))
		}

		// 输出只需要自己要保存的那一块
		toes = append(toes, ioswitchlrc.NewToStorage(*userspace.UserSpace.MasterHub, *userspace.UserSpace, i, fmt.Sprintf("%d", i)))

		newBlocks = append(newBlocks, newBlock)
	}

	err := lrcparser.ReconstructAny(froms, toes, planBlder)
	if err != nil {
		return nil, nil, fmt.Errorf("parsing plan: %w", err)
	}

	fmt.Printf("plans: %v\n", planBlder)

	// 如果没有任何Plan，Wait会直接返回成功
	execCtx := exec.NewExecContext()
	exec.SetValueByType(execCtx, ctx.ticktock.stgPool)
	ret, err := planBlder.Execute(execCtx).Wait(context.Background())
	if err != nil {
		return nil, nil, fmt.Errorf("executing io plan: %w", err)
	}

	if !shouldUpdateBlocks {
		return nil, nil, nil
	}

	for k, v := range ret {
		idx, err := strconv.ParseInt(k, 10, 64)
		if err != nil {
			return nil, nil, fmt.Errorf("parsing result key %s as index: %w", k, err)
		}

		r := v.(*ops2.ShardInfoValue)
		newBlocks[idx].FileHash = r.Hash
		newBlocks[idx].Size = r.Size
	}

	// TODO 产生系统事件
	return &db.UpdatingObjectRedundancy{
		ObjectID:   obj.Object.ObjectID,
		FileHash:   obj.Object.FileHash,
		Size:       obj.Object.Size,
		Redundancy: red,
		Blocks:     newBlocks,
	}, nil, nil
}
