package datastore

import (
	"encoding/json"
	"reflect"
	"testing"

	"github.com/docker/docker/libnetwork/options"
	"gotest.tools/v3/assert"
)

var dummyKey = "dummy"

// NewCustomDataStore can be used by other Tests in order to use custom datastore
func NewTestDataStore() DataStore {
	return &datastore{scope: LocalScope, store: NewMockStore()}
}

func TestKey(t *testing.T) {
	eKey := []string{"hello", "world"}
	sKey := Key(eKey...)
	if sKey != "docker/network/v1.0/hello/world/" {
		t.Fatalf("unexpected key : %s", sKey)
	}
}

func TestParseKey(t *testing.T) {
	keySlice, err := ParseKey("/docker/network/v1.0/hello/world/")
	if err != nil {
		t.Fatal(err)
	}
	eKey := []string{"hello", "world"}
	if len(keySlice) < 2 || !reflect.DeepEqual(eKey, keySlice) {
		t.Fatalf("unexpected unkey : %s", keySlice)
	}
}

func TestInvalidDataStore(t *testing.T) {
	config := ScopeCfg{
		Client: ScopeClientCfg{
			Provider: "invalid",
			Address:  "localhost:8500",
		},
	}
	_, err := NewDataStore(config)
	if err == nil {
		t.Fatal("Invalid Datastore connection configuration must result in a failure")
	}
}

func TestKVObjectFlatKey(t *testing.T) {
	store := NewTestDataStore()
	expected := dummyKVObject("1000", true)
	err := store.PutObject(expected)
	if err != nil {
		t.Fatal(err)
	}
	keychain := []string{dummyKey, "1000"}
	data, err := store.KVStore().Get(Key(keychain...))
	if err != nil {
		t.Fatal(err)
	}
	var n dummyObject
	json.Unmarshal(data.Value, &n)
	if n.Name != expected.Name {
		t.Fatal("Dummy object doesn't match the expected object")
	}
}

func TestAtomicKVObjectFlatKey(t *testing.T) {
	store := NewTestDataStore()
	expected := dummyKVObject("1111", true)
	assert.Check(t, !expected.Exists())
	err := store.PutObjectAtomic(expected)
	if err != nil {
		t.Fatal(err)
	}
	assert.Check(t, expected.Exists())

	// PutObjectAtomic automatically sets the Index again. Hence the following must pass.

	err = store.PutObjectAtomic(expected)
	if err != nil {
		t.Fatal("Atomic update should succeed.")
	}

	// Get the latest index and try PutObjectAtomic again for the same Key
	// This must succeed as well
	data, err := store.KVStore().Get(Key(expected.Key()...))
	if err != nil {
		t.Fatal(err)
	}
	n := dummyObject{}
	json.Unmarshal(data.Value, &n)
	n.ID = "1111"
	n.SetIndex(data.LastIndex)
	n.ReturnValue = true
	err = store.PutObjectAtomic(&n)
	if err != nil {
		t.Fatal(err)
	}

	// Get the Object using GetObject, then set again.
	newObj := dummyObject{}
	err = store.GetObject(Key(expected.Key()...), &newObj)
	if err != nil {
		t.Fatal(err)
	}
	assert.Check(t, newObj.Exists())
	err = store.PutObjectAtomic(&n)
	if err != nil {
		t.Fatal(err)
	}
}

// dummy data used to test the datastore
type dummyObject struct {
	Name        string                `kv:"leaf"`
	NetworkType string                `kv:"leaf"`
	EnableIPv6  bool                  `kv:"leaf"`
	Rec         *recStruct            `kv:"recursive"`
	Dict        map[string]*recStruct `kv:"iterative"`
	Generic     options.Generic       `kv:"iterative"`
	ID          string
	DBIndex     uint64
	DBExists    bool
	SkipSave    bool
	ReturnValue bool
}

func (n *dummyObject) Key() []string {
	return []string{dummyKey, n.ID}
}

func (n *dummyObject) KeyPrefix() []string {
	return []string{dummyKey}
}

func (n *dummyObject) Value() []byte {
	if !n.ReturnValue {
		return nil
	}

	b, err := json.Marshal(n)
	if err != nil {
		return nil
	}
	return b
}

func (n *dummyObject) SetValue(value []byte) error {
	return json.Unmarshal(value, n)
}

func (n *dummyObject) Index() uint64 {
	return n.DBIndex
}

func (n *dummyObject) SetIndex(index uint64) {
	n.DBIndex = index
	n.DBExists = true
}

func (n *dummyObject) Exists() bool {
	return n.DBExists
}

func (n *dummyObject) Skip() bool {
	return n.SkipSave
}

func (n *dummyObject) DataScope() string {
	return LocalScope
}

func (n *dummyObject) MarshalJSON() ([]byte, error) {
	netMap := make(map[string]interface{})
	netMap["name"] = n.Name
	netMap["networkType"] = n.NetworkType
	netMap["enableIPv6"] = n.EnableIPv6
	netMap["generic"] = n.Generic
	return json.Marshal(netMap)
}

func (n *dummyObject) UnmarshalJSON(b []byte) (err error) {
	var netMap map[string]interface{}
	if err := json.Unmarshal(b, &netMap); err != nil {
		return err
	}
	n.Name = netMap["name"].(string)
	n.NetworkType = netMap["networkType"].(string)
	n.EnableIPv6 = netMap["enableIPv6"].(bool)
	n.Generic = netMap["generic"].(map[string]interface{})
	return nil
}

// dummy structure to test "recursive" cases
type recStruct struct {
	Name     string            `kv:"leaf"`
	Field1   int               `kv:"leaf"`
	Dict     map[string]string `kv:"iterative"`
	DBIndex  uint64
	DBExists bool
	SkipSave bool
}

func (r *recStruct) Key() []string {
	return []string{"recStruct"}
}
func (r *recStruct) Value() []byte {
	b, err := json.Marshal(r)
	if err != nil {
		return nil
	}
	return b
}

func (r *recStruct) SetValue(value []byte) error {
	return json.Unmarshal(value, r)
}

func (r *recStruct) Index() uint64 {
	return r.DBIndex
}

func (r *recStruct) SetIndex(index uint64) {
	r.DBIndex = index
	r.DBExists = true
}

func (r *recStruct) Exists() bool {
	return r.DBExists
}

func (r *recStruct) Skip() bool {
	return r.SkipSave
}

func dummyKVObject(id string, retValue bool) *dummyObject {
	cDict := make(map[string]string)
	cDict["foo"] = "bar"
	cDict["hello"] = "world"
	n := dummyObject{
		Name:        "testNw",
		NetworkType: "bridge",
		EnableIPv6:  true,
		Rec:         &recStruct{"gen", 5, cDict, 0, false, false},
		ID:          id,
		DBIndex:     0,
		ReturnValue: retValue,
		DBExists:    false,
		SkipSave:    false}
	generic := make(map[string]interface{})
	generic["label1"] = &recStruct{"value1", 1, cDict, 0, false, false}
	generic["label2"] = "subnet=10.1.1.0/16"
	n.Generic = generic
	return &n
}
