// Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.

package metamorphic

import (
	"fmt"
	"testing"
	"time"

	"github.com/cockroachdb/pebble"
	"github.com/cockroachdb/pebble/internal/randvar"
	"github.com/pmezard/go-difflib/difflib"
	"github.com/stretchr/testify/require"
	"golang.org/x/exp/rand"
)

func TestGenerator(t *testing.T) {
	rng := randvar.NewRand()
	g := newGenerator(rng, defaultConfig(), newKeyManager(1 /* numInstances */))

	g.newBatch()
	g.newBatch()
	g.newBatch()
	require.EqualValues(t, 3, len(g.liveBatches))
	require.EqualValues(t, 0, len(g.batches))

	g.newIndexedBatch()
	g.newIndexedBatch()
	g.newIndexedBatch()
	require.EqualValues(t, 6, len(g.liveBatches))
	require.EqualValues(t, 3, len(g.batches))
	require.EqualValues(t, 3, len(g.readers))

	g.newIter()
	g.newIter()
	g.newIter()
	require.EqualValues(t, 3, len(g.liveIters))

	g.batchAbort()
	g.batchAbort()
	g.batchAbort()
	g.batchAbort()
	g.batchAbort()
	g.batchAbort()

	require.EqualValues(t, 0, len(g.liveBatches))
	require.EqualValues(t, 0, len(g.batches))
	require.EqualValues(t, 0, len(g.iters))
	require.EqualValues(t, 1, len(g.liveReaders))
	require.EqualValues(t, 0, len(g.readers))
	require.EqualValues(t, 0, len(g.liveSnapshots))
	require.EqualValues(t, 0, len(g.snapshots))
	require.EqualValues(t, 1, len(g.liveWriters))

	g.randIter(g.iterClose)()
	g.randIter(g.iterClose)()
	g.randIter(g.iterClose)()
	require.EqualValues(t, 0, len(g.liveIters))

	if testing.Verbose() {
		t.Logf("\n%s", g)
	}

	g = newGenerator(rng, defaultConfig(), newKeyManager(1 /* numInstances */))

	g.newSnapshot()
	g.newSnapshot()
	g.newSnapshot()
	require.EqualValues(t, 3, len(g.liveSnapshots))
	require.EqualValues(t, 3, len(g.snapshots))

	g.newIter()
	g.newIter()
	g.newIter()
	g.snapshotClose()
	g.snapshotClose()
	g.snapshotClose()

	require.EqualValues(t, 0, len(g.liveBatches))
	require.EqualValues(t, 0, len(g.batches))
	require.EqualValues(t, 0, len(g.iters))
	require.EqualValues(t, 1, len(g.liveReaders))
	require.EqualValues(t, 0, len(g.readers))
	require.EqualValues(t, 0, len(g.liveSnapshots))
	require.EqualValues(t, 0, len(g.snapshots))
	require.EqualValues(t, 1, len(g.liveWriters))

	g.randIter(g.iterClose)()
	g.randIter(g.iterClose)()
	g.randIter(g.iterClose)()
	require.EqualValues(t, 0, len(g.liveIters))

	if testing.Verbose() {
		t.Logf("\n%s", g)
	}

	g = newGenerator(rng, defaultConfig(), newKeyManager(1 /* numInstances */))

	g.newIndexedBatch()
	g.newIndexedBatch()
	g.newIndexedBatch()
	g.newIter()
	g.newIter()
	g.newIter()
	g.writerApply()
	g.writerApply()
	g.writerApply()
	g.randIter(g.iterClose)()
	g.randIter(g.iterClose)()
	g.randIter(g.iterClose)()

	require.EqualValues(t, 0, len(g.liveBatches))
	require.EqualValues(t, 0, len(g.batches))
	require.EqualValues(t, 0, len(g.liveIters))
	require.EqualValues(t, 0, len(g.iters))
	require.EqualValues(t, 1, len(g.liveReaders))
	require.EqualValues(t, 0, len(g.readers))
	require.EqualValues(t, 0, len(g.liveSnapshots))
	require.EqualValues(t, 0, len(g.snapshots))
	require.EqualValues(t, 1, len(g.liveWriters))

	if testing.Verbose() {
		t.Logf("\n%s", g)
	}
}

func TestGeneratorRandom(t *testing.T) {
	seed := uint64(time.Now().UnixNano())
	ops := randvar.NewUniform(1000, 10000)
	cfgs := []string{"default", "multiInstance"}
	generateFromSeed := func(cfg config) string {
		rng := rand.New(rand.NewSource(seed))
		count := ops.Uint64(rng)
		return formatOps(generate(rng, count, cfg, newKeyManager(cfg.numInstances)))
	}

	for i := range cfgs {
		t.Run(fmt.Sprintf("config=%s", cfgs[i]), func(t *testing.T) {
			cfg := defaultConfig
			if cfgs[i] == "multiInstance" {
				cfg = func() config {
					cfg := multiInstanceConfig()
					cfg.numInstances = 2
					return cfg
				}
			}
			// Ensure that generate doesn't use any other source of randomness other
			// than rng.
			referenceOps := generateFromSeed(cfg())
			for i := 0; i < 10; i++ {
				regeneratedOps := generateFromSeed(cfg())
				diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
					A:       difflib.SplitLines(referenceOps),
					B:       difflib.SplitLines(regeneratedOps),
					Context: 1,
				})
				require.NoError(t, err)
				if len(diff) > 0 {
					t.Fatalf("Diff:\n%s", diff)
				}
			}
			if testing.Verbose() {
				t.Logf("\nOps:\n%s", referenceOps)
			}
		})
	}
}

func TestGenerateRandKeyToReadInRange(t *testing.T) {
	rng := randvar.NewRand()
	g := newGenerator(rng, defaultConfig(), newKeyManager(1 /* numInstances */))
	// Seed 100 initial keys.
	for i := 0; i < 100; i++ {
		_ = g.randKeyToWrite(1.0)
	}
	for i := 0; i < 100; i++ {
		a := g.randKeyToRead(0.01)
		b := g.randKeyToRead(0.01)
		// Ensure unique prefixes; required by randKeyToReadInRange.
		for g.equal(g.prefix(a), g.prefix(b)) {
			b = g.randKeyToRead(0.01)
		}
		if v := g.cmp(a, b); v > 0 {
			a, b = b, a
		}
		kr := pebble.KeyRange{Start: a, End: b}
		for j := 0; j < 10; j++ {
			k := g.randKeyToReadInRange(0.05, kr)
			if g.cmp(k, a) < 0 {
				t.Errorf("generated random key %q outside range %s", k, kr)
			} else if g.cmp(k, b) >= 0 {
				t.Errorf("generated random key %q outside range %s", k, kr)
			}
		}
	}
}

func TestGenerateDisjointKeyRanges(t *testing.T) {
	rng := randvar.NewRand()
	g := newGenerator(rng, defaultConfig(), newKeyManager(1 /* numInstances */))

	for i := 0; i < 10; i++ {
		keyRanges := g.generateDisjointKeyRanges(5)
		for j := range keyRanges {
			t.Logf("%d: %d: %s", i, j, keyRanges[j])
			for k := range keyRanges {
				if j == k {
					continue
				}
				if g.cmp(keyRanges[j].End, keyRanges[k].Start) <= 0 {
					require.Less(t, j, k)
					continue
				}
				if g.cmp(keyRanges[j].Start, keyRanges[k].End) >= 0 {
					require.Greater(t, j, k)
					continue
				}
				t.Fatalf("generated key ranges %q and %q overlap", keyRanges[j], keyRanges[k])
			}
		}
	}
}