package downloader

import (
	"fmt"
	"io"

	"gitlink.org.cn/cloudream/common/pkgs/iterator"
	"gitlink.org.cn/cloudream/common/pkgs/logger"
	cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
	"gitlink.org.cn/cloudream/common/utils/io2"
	"gitlink.org.cn/cloudream/common/utils/math2"
)

func (iter *DownloadObjectIterator) downloadLRCObject(req downloadReqeust2, red *cdssdk.LRCRedundancy) (io.ReadCloser, error) {
	allStgs, err := iter.sortDownloadStorages(req)
	if err != nil {
		return nil, err
	}

	var blocks []downloadBlock
	selectedBlkIdx := make(map[int]bool)
	for _, stg := range allStgs {
		for _, b := range stg.Blocks {
			if b.Index >= red.M() || selectedBlkIdx[b.Index] {
				continue
			}
			blocks = append(blocks, downloadBlock{
				Storage: stg.Storage,
				Block:   b,
			})
			selectedBlkIdx[b.Index] = true
		}
	}
	if len(blocks) < red.K {
		return nil, fmt.Errorf("not enough blocks to download lrc object")
	}

	var logStrs []any = []any{"downloading lrc object from blocks: "}
	for i, b := range blocks {
		if i > 0 {
			logStrs = append(logStrs, ", ")
		}
		logStrs = append(logStrs, fmt.Sprintf("%v@%v", b.Block.Index, b.Storage.Storage.String()))
	}
	logger.Debug(logStrs...)

	pr, pw := io.Pipe()
	go func() {
		readPos := req.Raw.Offset
		totalReadLen := req.Detail.Object.Size - req.Raw.Offset
		if req.Raw.Length >= 0 {
			totalReadLen = math2.Min(req.Raw.Length, totalReadLen)
		}

		firstStripIndex := readPos / int64(red.K) / int64(red.ChunkSize)
		stripIter := NewLRCStripIterator(iter.downloader, req.Detail.Object, blocks, red, firstStripIndex, iter.downloader.strips, iter.downloader.cfg.ECStripPrefetchCount)
		defer stripIter.Close()

		for totalReadLen > 0 {
			strip, err := stripIter.MoveNext()
			if err == iterator.ErrNoMoreItem {
				pw.CloseWithError(io.ErrUnexpectedEOF)
				return
			}
			if err != nil {
				pw.CloseWithError(err)
				return
			}

			readRelativePos := readPos - strip.Position
			nextStripPos := strip.Position + int64(red.K)*int64(red.ChunkSize)
			curReadLen := math2.Min(totalReadLen, nextStripPos-readPos)

			err = io2.WriteAll(pw, strip.Data[readRelativePos:readRelativePos+curReadLen])
			if err != nil {
				pw.CloseWithError(err)
				return
			}

			totalReadLen -= curReadLen
			readPos += curReadLen
		}
		pw.Close()
	}()

	return pr, nil
}
