package iterator

import (
	"sync"

	"go.uber.org/atomic"
	"go.uber.org/zap"

	"github.com/milvus-io/milvus/internal/storage"
	"github.com/milvus-io/milvus/pkg/log"
)

var _ Iterator = (*DeltalogIterator)(nil)

type DeltalogIterator struct {
	disposeCh    chan struct{}
	disposedOnce sync.Once
	disposed     atomic.Bool

	data  *storage.DeleteData
	blobs []*storage.Blob
	label *Label
	pos   int
}

func NewDeltalogIterator(v [][]byte, label *Label) *DeltalogIterator {
	blobs := make([]*storage.Blob, len(v))
	for i := range blobs {
		blobs[i] = &storage.Blob{Value: v[i]}
	}

	return &DeltalogIterator{
		disposeCh: make(chan struct{}),
		blobs:     blobs,
		label:     label,
	}
}

func (d *DeltalogIterator) HasNext() bool {
	return !d.isDisposed() && d.hasNext()
}

func (d *DeltalogIterator) Next() (*LabeledRowData, error) {
	if d.isDisposed() {
		return nil, ErrDisposed
	}

	if !d.hasNext() {
		return nil, ErrNoMoreRecord
	}

	row := &DeltalogRow{
		Pk:        d.data.Pks[d.pos],
		Timestamp: d.data.Tss[d.pos],
	}
	d.pos++

	return NewLabeledRowData(row, d.label), nil
}

func (d *DeltalogIterator) Dispose() {
	d.disposed.CompareAndSwap(false, true)
	d.disposedOnce.Do(func() {
		close(d.disposeCh)
	})
}

func (d *DeltalogIterator) hasNext() bool {
	if d.data == nil && d.blobs != nil {
		reader := storage.NewDeleteCodec()
		_, _, dData, err := reader.Deserialize(d.blobs)
		if err != nil {
			log.Warn("Deltalog iterator failed to deserialize blobs", zap.Error(err))
			return false
		}
		d.data = dData
		d.blobs = nil
	}
	return int64(d.pos) < d.data.RowCount
}

func (d *DeltalogIterator) isDisposed() bool {
	return d.disposed.Load()
}

func (d *DeltalogIterator) WaitForDisposed() {
	<-d.disposeCh
}
