package tsm1

import (
	"runtime"
	"testing"
	"time"

	"github.com/influxdata/influxdb/v2/influxql/query"
	"github.com/influxdata/influxql"
	"go.uber.org/zap/zaptest"
)

func BenchmarkIntegerIterator_Next(b *testing.B) {
	opt := query.IteratorOptions{
		Aux: []influxql.VarRef{{Val: "f1"}, {Val: "f1"}, {Val: "f1"}, {Val: "f1"}},
	}
	aux := []cursorAt{
		&literalValueCursor{value: "foo bar"},
		&literalValueCursor{value: int64(1e3)},
		&literalValueCursor{value: float64(1e3)},
		&literalValueCursor{value: true},
	}

	cur := newIntegerIterator("m0", query.Tags{}, opt, &infiniteIntegerCursor{}, aux, nil, nil)

	b.ResetTimer()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		cur.Next()
	}
}

type infiniteIntegerCursor struct{}

func (*infiniteIntegerCursor) close() error {
	return nil
}

func (*infiniteIntegerCursor) next() (t int64, v interface{}) {
	return 0, 0
}

func (*infiniteIntegerCursor) nextInteger() (t int64, v int64) {
	return 0, 0
}

type testFinalizerIterator struct {
	OnClose func()
}

func (itr *testFinalizerIterator) Next() (*query.FloatPoint, error) {
	return nil, nil
}

func (itr *testFinalizerIterator) Close() error {
	// Act as if this is a slow finalizer and ensure that it doesn't block
	// the finalizer background thread.
	itr.OnClose()
	return nil
}

func (itr *testFinalizerIterator) Stats() query.IteratorStats {
	return query.IteratorStats{}
}

func TestFinalizerIterator(t *testing.T) {
	var (
		step1 = make(chan struct{})
		step2 = make(chan struct{})
		step3 = make(chan struct{})
	)

	l := zaptest.NewLogger(t)
	done := make(chan struct{})
	func() {
		itr := &testFinalizerIterator{
			OnClose: func() {
				// Simulate a slow closing iterator by waiting for the done channel
				// to be closed. The done channel is closed by a later finalizer.
				close(step1)
				<-done
				close(step3)
			},
		}
		newFinalizerIterator(itr, l)
	}()

	// Thrash the GC here and hope it eventually notices that itr (above) should be
	// cleaned up, triggering the finalizer.
	for i := 0; i < 500; i++ {
		runtime.GC()
	}

	// Wait for the finalizer to close the 1st channel.
	timer := time.NewTimer(1000 * time.Millisecond)
	select {
	case <-timer.C:
		defer close(done)
		t.Fatal("The finalizer for the iterator did not run")
	case <-step1:
		// The finalizer has successfully started running, but it won't complete
		// until we close the done channel.
		timer.Stop()
	}

	select {
	case <-step3:
		t.Fatal("The finalizer should not have finished yet")
	default:
	}

	// Use a fake value that will be collected by the garbage collector and have
	// the finalizer close the channel. This finalizer should run after the iterator's
	// finalizer.
	value := func() int {
		foo := &struct {
			value int
		}{value: 1}
		runtime.SetFinalizer(foo, func(value interface{}) {
			close(done)
			close(step2)
		})
		return foo.value + 2
	}()
	if value < 2 {
		t.Log("This should never be output")
	}

	for i := 0; i < 500; i++ {
		runtime.GC()
	}

	timer.Reset(1000 * time.Millisecond)
	select {
	case <-timer.C:
		t.Fatal("The second finalizer did not run")
	case <-step2:
		// The finalizer has successfully run and should have
		// closed the done channel.
		timer.Stop()
	}

	// Wait for step3 to finish where the closed value should be set.
	timer.Reset(1000 * time.Millisecond)
	select {
	case <-timer.C:
		t.Fatal("The iterator was not finalized")
	case <-step3:
		timer.Stop()
	}
}

func TestBufCursor_DoubleClose(t *testing.T) {
	c := newBufCursor(nilCursor{}, true)
	if err := c.close(); err != nil {
		t.Fatalf("error closing: %v", err)
	}

	// This shouldn't panic
	if err := c.close(); err != nil {
		t.Fatalf("error closing: %v", err)
	}

}
