package leveldb

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"github.com/syndtr/goleveldb/leveldb/storage"
	"github.com/syndtr/goleveldb/leveldb/util"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

func TestDB(t *testing.T) {
	testAction(t, "CompactRange", func(mt mocktracer.Tracer, db *DB) {
		db.CompactRange(util.Range{})
	})

	testAction(t, "Delete", func(mt mocktracer.Tracer, db *DB) {
		db.Delete([]byte("hello"), nil)
	})

	testAction(t, "Has", func(mt mocktracer.Tracer, db *DB) {
		db.Has([]byte("hello"), nil)
	})

	testAction(t, "Get", func(mt mocktracer.Tracer, db *DB) {
		db.Get([]byte("hello"), nil)
	})

	testAction(t, "Put", func(mt mocktracer.Tracer, db *DB) {
		span, ctx := tracer.StartSpanFromContext(context.Background(), "http.request",
			tracer.ServiceName("my-http-server"))

		err := db.WithContext(ctx).
			Put([]byte("hello"), []byte("world"), nil)
		assert.NoError(t, err)

		span.Finish()

		spans := mt.FinishedSpans()
		assert.Len(t, spans, 2)
		assert.Equal(t, spans[0].TraceID(), spans[1].TraceID())
	})

	testAction(t, "Write", func(mt mocktracer.Tracer, db *DB) {
		var batch leveldb.Batch
		batch.Put([]byte("hello"), []byte("world"))
		db.Write(&batch, nil)
	})
}

func TestSnapshot(t *testing.T) {
	testAction(t, "Get", func(mt mocktracer.Tracer, db *DB) {
		snapshot, err := db.GetSnapshot()
		assert.NoError(t, err)
		defer snapshot.Release()

		snapshot.Get([]byte("hello"), nil)
	})

	testAction(t, "Has", func(mt mocktracer.Tracer, db *DB) {
		snapshot, err := db.GetSnapshot()
		assert.NoError(t, err)
		defer snapshot.Release()

		span, ctx := tracer.StartSpanFromContext(context.Background(), "http.request",
			tracer.ServiceName("my-http-server"))

		_, err = snapshot.WithContext(ctx).
			Has([]byte("hello"), nil)
		assert.NoError(t, err)

		span.Finish()

		spans := mt.FinishedSpans()
		assert.Len(t, spans, 2)
		assert.Equal(t, spans[0].TraceID(), spans[1].TraceID())
	})
}

func TestTransaction(t *testing.T) {
	testAction(t, "Commit", func(mt mocktracer.Tracer, db *DB) {
		transaction, err := db.OpenTransaction()
		assert.NoError(t, err)
		transaction.Commit()
	})

	testAction(t, "Get", func(mt mocktracer.Tracer, db *DB) {
		transaction, err := db.OpenTransaction()
		assert.NoError(t, err)
		defer transaction.Discard()

		transaction.Get([]byte("hello"), nil)
	})

	testAction(t, "Has", func(mt mocktracer.Tracer, db *DB) {
		transaction, err := db.OpenTransaction()
		assert.NoError(t, err)
		defer transaction.Discard()

		span, ctx := tracer.StartSpanFromContext(context.Background(), "http.request",
			tracer.ServiceName("my-http-server"))

		_, err = transaction.WithContext(ctx).
			Has([]byte("hello"), nil)
		assert.NoError(t, err)

		span.Finish()

		spans := mt.FinishedSpans()
		assert.Len(t, spans, 2)
		assert.Equal(t, spans[0].TraceID(), spans[1].TraceID())
	})
}

func TestIterator(t *testing.T) {
	testAction(t, "Iterator", func(mt mocktracer.Tracer, db *DB) {
		iterator := db.NewIterator(nil, nil)
		iterator.Release()
	})
}

func testAction(t *testing.T, name string, f func(mt mocktracer.Tracer, db *DB)) {
	t.Run(name, func(t *testing.T) {
		mt := mocktracer.Start()
		defer mt.Stop()

		db, err := Open(storage.NewMemStorage(), &opt.Options{},
			WithServiceName("my-database"))
		assert.NoError(t, err)
		defer db.Close()

		f(mt, db)

		spans := mt.FinishedSpans()
		assert.Equal(t, "leveldb.query", spans[0].OperationName())
		assert.Equal(t, ext.SpanTypeLevelDB, spans[0].Tag(ext.SpanType))
		assert.Equal(t, "my-database", spans[0].Tag(ext.ServiceName))
		assert.Equal(t, name, spans[0].Tag(ext.ResourceName))
	})
}
