package dsindex

import (
	"context"
	"testing"

	ds "github.com/ipfs/go-datastore"
)

func createIndexer() Indexer {
	dstore := ds.NewMapDatastore()
	nameIndex := New(dstore, ds.NewKey("/data/nameindex"))

	ctx := context.Background()
	nameIndex.Add(ctx, "alice", "a1")
	nameIndex.Add(ctx, "bob", "b1")
	nameIndex.Add(ctx, "bob", "b2")
	nameIndex.Add(ctx, "cathy", "c1")

	return nameIndex
}

func TestAdd(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	nameIndex := createIndexer()
	err := nameIndex.Add(ctx, "someone", "s1")
	if err != nil {
		t.Fatal(err)
	}
	err = nameIndex.Add(ctx, "someone", "s1")
	if err != nil {
		t.Fatal(err)
	}

	err = nameIndex.Add(ctx, "", "noindex")
	if err != ErrEmptyKey {
		t.Fatal("unexpected error:", err)
	}

	err = nameIndex.Add(ctx, "nokey", "")
	if err != ErrEmptyValue {
		t.Fatal("unexpected error:", err)
	}
}

func TestHasValue(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	nameIndex := createIndexer()

	ok, err := nameIndex.HasValue(ctx, "bob", "b1")
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("missing index")
	}

	ok, err = nameIndex.HasValue(ctx, "bob", "b3")
	if err != nil {
		t.Fatal(err)
	}
	if ok {
		t.Fatal("should not have index")
	}

	_, err = nameIndex.HasValue(ctx, "", "b1")
	if err != ErrEmptyKey {
		t.Fatal("unexpected error:", err)
	}

	_, err = nameIndex.HasValue(ctx, "bob", "")
	if err != ErrEmptyValue {
		t.Fatal("unexpected error:", err)
	}
}

func TestHasAny(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	nameIndex := createIndexer()

	ok, err := nameIndex.HasAny(ctx, "nothere")
	if err != nil {
		t.Fatal(err)
	}
	if ok {
		t.Fatal("should return false")
	}

	for _, idx := range []string{"alice", "bob", ""} {
		ok, err = nameIndex.HasAny(ctx, idx)
		if err != nil {
			t.Fatal(err)
		}
		if !ok {
			t.Fatal("missing index", idx)
		}
	}

	count, err := nameIndex.DeleteAll(ctx)
	if err != nil {
		t.Fatal(err)
	}
	if count != 4 {
		t.Fatal("expected 4 deletions")
	}

	ok, err = nameIndex.HasAny(ctx, "")
	if err != nil {
		t.Fatal(err)
	}
	if ok {
		t.Fatal("should return false")
	}
}

func TestForEach(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	nameIndex := createIndexer()

	found := make(map[string]struct{})
	err := nameIndex.ForEach(ctx, "bob", func(key, value string) bool {
		found[value] = struct{}{}
		return true
	})
	if err != nil {
		t.Fatal(err)
	}

	for _, value := range []string{"b1", "b2"} {
		_, ok := found[value]
		if !ok {
			t.Fatal("missing key for value", value)
		}
	}

	values := map[string]string{}
	err = nameIndex.ForEach(ctx, "", func(key, value string) bool {
		values[value] = key
		return true
	})
	if err != nil {
		t.Fatal(err)
	}
	if len(values) != 4 {
		t.Fatal("expected 4 keys")
	}

	if values["a1"] != "alice" {
		t.Error("expected a1: alice")
	}
	if values["b1"] != "bob" {
		t.Error("expected b1: bob")
	}
	if values["b2"] != "bob" {
		t.Error("expected b2: bob")
	}
	if values["c1"] != "cathy" {
		t.Error("expected c1: cathy")
	}
}

func TestSearch(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	nameIndex := createIndexer()

	ids, err := nameIndex.Search(ctx, "bob")
	if err != nil {
		t.Fatal(err)
	}
	if len(ids) != 2 {
		t.Fatal("wrong number of ids - expected 2 got", ids)
	}
	for _, id := range ids {
		if id != "b1" && id != "b2" {
			t.Fatal("wrong value in id set")
		}
	}
	if ids[0] == ids[1] {
		t.Fatal("duplicate id")
	}

	ids, err = nameIndex.Search(ctx, "cathy")
	if err != nil {
		t.Fatal(err)
	}
	if len(ids) != 1 || ids[0] != "c1" {
		t.Fatal("wrong ids")
	}

	ids, err = nameIndex.Search(ctx, "amit")
	if err != nil {
		t.Fatal(err)
	}
	if len(ids) != 0 {
		t.Fatal("unexpected ids returned")
	}
}

func TestDelete(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	nameIndex := createIndexer()

	err := nameIndex.Delete(ctx, "bob", "b3")
	if err != nil {
		t.Fatal(err)
	}

	err = nameIndex.Delete(ctx, "alice", "a1")
	if err != nil {
		t.Fatal(err)
	}

	ok, err := nameIndex.HasValue(ctx, "alice", "a1")
	if err != nil {
		t.Fatal(err)
	}
	if ok {
		t.Fatal("index key should have been deleted")
	}

	count, err := nameIndex.DeleteKey(ctx, "bob")
	if err != nil {
		t.Fatal(err)
	}
	if count != 2 {
		t.Fatal("wrong deleted count")
	}
	ok, _ = nameIndex.HasValue(ctx, "bob", "b1")
	if ok {
		t.Fatal("index not deleted")
	}
}

func TestSyncIndex(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	nameIndex := createIndexer()

	dstore := ds.NewMapDatastore()
	refIndex := New(dstore, ds.NewKey("/ref"))
	refIndex.Add(ctx, "alice", "a1")
	refIndex.Add(ctx, "cathy", "zz")
	refIndex.Add(ctx, "dennis", "d1")

	changed, err := SyncIndex(ctx, refIndex, nameIndex)
	if err != nil {
		t.Fatal(err)
	}
	if !changed {
		t.Error("change was not indicated")
	}

	// Create map of id->index in sync target
	syncs := map[string]string{}
	err = nameIndex.ForEach(ctx, "", func(key, value string) bool {
		syncs[value] = key
		return true
	})
	if err != nil {
		t.Fatal(err)
	}

	// Iterate items in sync source and make sure they appear in target
	var itemCount int
	err = refIndex.ForEach(ctx, "", func(key, value string) bool {
		itemCount++
		syncKey, ok := syncs[value]
		if !ok || key != syncKey {
			t.Fatal("key", key, "-->", value, "was not synced")
		}
		return true
	})
	if err != nil {
		t.Fatal(err)
	}

	if itemCount != len(syncs) {
		t.Fatal("different number of items in sync source and target")
	}
}