package tsdb

import (
	"reflect"
	"testing"
	"time"

	"github.com/influxdata/influxdb/v2/models"
)

func TestSeriesCollection(t *testing.T) {
	// some helper functions. short names because local scope and frequently used.
	var (
		equal = reflect.DeepEqual
		b     = func(s string) []byte { return []byte(s) }
		bs    = func(s ...string) [][]byte {
			out := make([][]byte, len(s))
			for i := range s {
				out[i] = b(s[i])
			}
			return out
		}

		assertEqual = func(t *testing.T, name string, got, wanted interface{}) {
			t.Helper()
			if !equal(got, wanted) {
				t.Fatalf("bad %s: got: %v but wanted: %v", name, got, wanted)
			}
		}
	)

	t.Run("New", func(t *testing.T) {
		points := []models.Point{
			models.MustNewPoint("a", models.Tags{}, models.Fields{"f": 1.0}, time.Now()),
			models.MustNewPoint("b", models.Tags{}, models.Fields{"b": true}, time.Now()),
			models.MustNewPoint("c", models.Tags{}, models.Fields{"i": int64(1)}, time.Now()),
		}
		collection := NewSeriesCollection(points)

		assertEqual(t, "length", collection.Length(), 3)

		for iter := collection.Iterator(); iter.Next(); {
			ipt, spt := iter.Point(), points[iter.Index()]
			fi := spt.FieldIterator()
			fi.Next()

			assertEqual(t, "point", ipt, spt)
			assertEqual(t, "key", iter.Key(), spt.Key())
			assertEqual(t, "name", iter.Name(), spt.Name())
			assertEqual(t, "tags", iter.Tags(), spt.Tags())
			assertEqual(t, "type", iter.Type(), fi.Type())
		}
	})

	t.Run("Copy", func(t *testing.T) {
		collection := &SeriesCollection{
			Keys:  bs("ka", "kb", "kc"),
			Names: bs("na", "nb", "nc"),
		}

		collection.Copy(0, 2)
		assertEqual(t, "keys", collection.Keys, bs("kc", "kb", "kc"))
		assertEqual(t, "names", collection.Names, bs("nc", "nb", "nc"))

		collection.Copy(0, 4) // out of bounds
		assertEqual(t, "keys", collection.Keys, bs("kc", "kb", "kc"))
		assertEqual(t, "names", collection.Names, bs("nc", "nb", "nc"))
	})

	t.Run("Swap", func(t *testing.T) {
		collection := &SeriesCollection{
			Keys:  bs("ka", "kb", "kc"),
			Names: bs("na", "nb", "nc"),
		}

		collection.Swap(0, 2)
		assertEqual(t, "keys", collection.Keys, bs("kc", "kb", "ka"))
		assertEqual(t, "names", collection.Names, bs("nc", "nb", "na"))

		collection.Swap(0, 4) // out of bounds
		assertEqual(t, "keys", collection.Keys, bs("kc", "kb", "ka"))
		assertEqual(t, "names", collection.Names, bs("nc", "nb", "na"))
	})

	t.Run("Truncate", func(t *testing.T) {
		collection := &SeriesCollection{
			Keys:  bs("ka", "kb", "kc"),
			Names: bs("na", "nb", "nc"),
		}

		collection.Truncate(1)
		assertEqual(t, "keys", collection.Keys, bs("ka"))
		assertEqual(t, "names", collection.Names, bs("na"))

		collection.Truncate(0)
		assertEqual(t, "keys", collection.Keys, bs())
		assertEqual(t, "names", collection.Names, bs())
	})

	t.Run("Advance", func(t *testing.T) {
		collection := &SeriesCollection{
			Keys:  bs("ka", "kb", "kc"),
			Names: bs("na", "nb", "nc"),
		}

		collection.Advance(1)
		assertEqual(t, "keys", collection.Keys, bs("kb", "kc"))
		assertEqual(t, "names", collection.Names, bs("nb", "nc"))

		collection.Advance(1)
		assertEqual(t, "keys", collection.Keys, bs("kc"))
		assertEqual(t, "names", collection.Names, bs("nc"))
	})

	t.Run("InvalidateAll", func(t *testing.T) {
		collection := &SeriesCollection{Keys: bs("ka", "kb", "kc")}

		collection.InvalidateAll("test reason")
		assertEqual(t, "length", collection.Length(), 0)
		assertEqual(t, "error", collection.PartialWriteError(), PartialWriteError{
			Reason:      "test reason",
			Dropped:     3,
			DroppedKeys: bs("ka", "kb", "kc"),
		})
	})

	t.Run("Invalid", func(t *testing.T) {
		collection := &SeriesCollection{Keys: bs("ka", "kb", "kc")}

		// invalidate half the entries
		for iter := collection.Iterator(); iter.Next(); {
			if iter.Index()%2 == 0 {
				iter.Invalid("test reason")
			}
		}

		// nothing happens yet: all values are staged
		assertEqual(t, "length", collection.Length(), 3)

		// apply all of the invalid calls
		collection.ApplyConcurrentDrops()
		assertEqual(t, "length", collection.Length(), 1)
		assertEqual(t, "error", collection.PartialWriteError(), PartialWriteError{
			Reason:      "test reason",
			Dropped:     2,
			DroppedKeys: bs("ka", "kc"),
		})
	})
}
