/*
 * EliasDB
 *
 * Copyright 2016 Matthias Ladkau. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package graph

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"testing"

	"devt.de/krotik/eliasdb/graph/data"
	"devt.de/krotik/eliasdb/graph/graphstorage"
	"devt.de/krotik/eliasdb/graph/util"
	"devt.de/krotik/eliasdb/hash"
	"devt.de/krotik/eliasdb/storage"
)

func TestSimpleNodeStorage(t *testing.T) {
	if !RunDiskStorageTests {
		return
	}

	dgs, err := graphstorage.NewDiskGraphStorage(GraphManagerTestDBDir2, false)
	if err != nil {
		t.Error(err)
		return
	}

	gm := newGraphManagerNoRules(dgs)

	node1 := data.NewGraphNode()
	node1.SetAttr("key", "123")
	node1.SetAttr("kind", "mykind")
	node1.SetAttr("Name", "Some name")
	node1.SetAttr("To Delete", "Some data")

	if cnt := gm.NodeCount("mykind"); cnt != 0 {
		t.Error("Invalid node count:", cnt)
		return
	}

	if err := gm.StoreNode("main", node1); err != nil {
		t.Error(err)
		return
	}

	if cnt := gm.NodeCount("mykind"); cnt != 1 {
		t.Error("Invalid node count:", cnt)
		return
	}

	if gm.IsValidAttr("123") {
		t.Error("123 should not be a valid attribute")
	}
	if !gm.IsValidAttr("Name") {
		t.Error("Name should be a valid attribute")
	}
	if !gm.IsValidAttr(data.NodeKey) {
		t.Error("key should be a valid attribute")
	}
	if !gm.IsValidAttr(data.NodeKind) {
		t.Error("kind should be a valid attribute")
	}
	if !gm.IsValidAttr(data.EdgeEnd1Cascading) {
		t.Error("end1cascading should be a valid attribute")
	}

	node2 := data.NewGraphNode()
	node2.SetAttr("key", "456")
	node2.SetAttr("kind", "mykind")
	node2.SetAttr("Name", "Node2")
	node2.SetAttr("Data", "word1, word2, word3!")

	node1 = data.NewGraphNode()
	node1.SetAttr("key", "123")
	node1.SetAttr("kind", "mykind")
	node1.SetAttr("Name", "Some new name")
	node1.SetAttr("Data", "word4, word5, word6!")

	if err := gm.StoreNode("main", node1); err != nil {
		t.Error(err)
		return
	}

	if err := gm.StoreNode("main", node2); err != nil {
		t.Error(err)
		return
	}

	if cnt := gm.NodeCount("mykind"); cnt != 2 {
		t.Error("Invalid node count:", cnt)
		return
	}

	// Get only part of a node

	fnode1, err := gm.FetchNodePart("main", "123", "mykind", []string{"Data"})
	if err != nil {
		t.Error(err)
		return
	}

	// Check we got only the attributes we asked for

	if res := len(fnode1.Data()); res != 3 {
		t.Error("Unexpected number of attributes:", res)
		return
	}

	if fnode1.Key() != "123" || fnode1.Kind() != "mykind" {
		t.Error("Unexpected result:", fnode1)
		return
	}

	if fnode1.Attr("Data") != "word4, word5, word6!" {
		t.Error("Unexpected result:", fnode1)
		return
	}

	// Get the full node

	fnode2, err := gm.FetchNode("main", "123", "mykind")
	if err != nil {
		t.Error(err)
		return
	}

	// Check we got everything back

	if res := len(fnode2.Data()); res != 4 {
		t.Error("Unexpected number of attributes:", res)
		return
	}

	if fnode2.Key() != "123" || fnode2.Kind() != "mykind" {
		t.Error("Unexpected result:", fnode1)
		return
	}

	if fnode2.Attr("Name") != "Some new name" {
		t.Error("Unexpected result:", fnode1)
		return
	}

	if fnode2.Attr("Data") != "word4, word5, word6!" {
		t.Error("Unexpected result:", fnode1)
		return
	}

	dgs.Close()

	// Check that we can do the lookup with a new graph storage

	dgs2, err := graphstorage.NewDiskGraphStorage(GraphManagerTestDBDir2, false)
	if err != nil {
		t.Error(err)
		return
	}

	gm2 := newGraphManagerNoRules(dgs2)

	// Do an index lookup

	iq, err := gm2.NodeIndexQuery("main", "mykind")
	if err != nil {
		t.Error(err)
		return
	}

	res, err := iq.LookupWord("Data", "word5")
	if err != nil {
		t.Error(err)
		return
	}

	if fmt.Sprint(res) != "map[123:[2]]" {
		t.Error("Unexpected result:", res)
		return
	}

	fnode3, err := gm2.FetchNode("main", "123", "mykind")
	if err != nil {
		t.Error(err)
		return
	}

	// Check we got everything back

	if res := len(fnode3.Data()); res != 4 {
		t.Error("Unexpected number of attributes:", res)
		return
	}

	res2, err := iq.LookupPhrase("Data", "-....word5 ...word6")
	if err != nil {
		t.Error(err)
		return
	}

	if fmt.Sprint(res2) != "[123]" {
		t.Error("Unexpected result:", res)
		return
	}

	// Delete the nodes

	fnode4, err := gm2.RemoveNode("main", "123", "mykind")
	if err != nil {
		t.Error(err)
		return
	}

	if res := len(fnode4.Data()); res != 4 {
		t.Error("Unexpected number of attributes:", res)
		return
	}

	// Check that the node no longer exists

	fnode4, err = gm2.FetchNode("main", "123", "mykind")
	if err != nil || fnode4 != nil {
		t.Error("Unexpected lookup result:", fnode4, err)
		return
	}

	if cnt := gm2.NodeCount("mykind"); cnt != 1 {
		t.Error("Invalid node count:", cnt)
		return
	}

	_, err = gm2.RemoveNode("main", "456", "mykind")
	if err != nil {
		t.Error(err)
		return
	}

	if cnt := gm2.NodeCount("mykind"); cnt != 0 {
		t.Error("Invalid node count:", cnt)
		return
	}

	// Check that all datastructures are empty

	tree, _, _ := gm2.getNodeStorageHTree("main", "mykind", false)
	it := hash.NewHTreeIterator(tree)

	if it.HasNext() {
		t.Error("Node storage tree should be empty at this point")
		return
	}

	tree, _ = gm2.getNodeIndexHTree("main", "mykind", false)
	it = hash.NewHTreeIterator(tree)

	if it.HasNext() {
		t.Error("Node storage tree should be empty at this point")
		return
	}

	dgs2.Close()
}

func TestSimpleNodeUpdate(t *testing.T) {
	if !RunDiskStorageTests {
		return
	}

	dgs, err := graphstorage.NewDiskGraphStorage(GraphManagerTestDBDir2, false)
	if err != nil {
		t.Error(err)
		return
	}

	gm := newGraphManagerNoRules(dgs)

	node1 := data.NewGraphNode()
	node1.SetAttr("key", "nodeToUpdate")
	node1.SetAttr("kind", "nodeupdatekind")

	// Check that an update can do an actual insert

	err = gm.UpdateNode("main", node1)
	if err != nil {
		t.Error(err)
		return
	}

	// Check that we can lookup the node just by asking for the kind attribute

	n, err := gm.FetchNodePart("main", node1.Key(), node1.Kind(), []string{"kind"})
	if err != nil {
		t.Error(err)
	}

	if !data.NodeCompare(node1, n, nil) {
		t.Error("Nodes should match")
		return
	}

	node1.SetAttr("Name", "Some name")
	node1.SetAttr("Name2", "Some name2")
	node1.SetAttr("Name3", "Some name3")
	node1.SetAttr("Name4", "Some name4")

	err = gm.UpdateNode("main", node1)
	if err != nil {
		t.Error(err)
		return
	}

	fetchedNode, _ := gm.FetchNode("main", "nodeToUpdate", "nodeupdatekind")
	if !data.NodeCompare(node1, fetchedNode, nil) {
		t.Error("Node should have been stored completely")
		return
	}

	node2 := data.NewGraphNode()
	node2.SetAttr("key", "nodeToUpdate")
	node2.SetAttr("kind", "nodeupdatekind")
	node2.SetAttr("Name", "Some new name")
	node2.SetAttr("NewField", "Some new field value")

	err = gm.UpdateNode("main", node2)
	if err != nil {
		t.Error(err)
		return
	}

	fetchedNode, _ = gm.FetchNode("main", "nodeToUpdate", "nodeupdatekind")

	if len(fetchedNode.Data()) != len(node1.Data())+1 {
		t.Error("Unexpected number of attributes")
		return
	}

	if !data.NodeCompare(data.NodeMerge(node1, node2), fetchedNode, nil) {
		t.Error("Node should have been stored completely")
		return
	}

	dgs.Close()
}

func TestSimpleNodeStorageErrorCases(t *testing.T) {
	mgs := graphstorage.NewMemoryGraphStorage("mystorage")

	gm := newGraphManagerNoRules(mgs)

	if _, err := gm.FetchNodePart("in valid", "testkey", "testkind", nil); err.Error() !=
		"GraphError: Invalid data (Partition name in valid is not alphanumeric - can only contain [a-zA-Z0-9_])" {

		t.Error("Unexpected error:", err)
		return
	}

	if res, err := gm.FetchNodePart("testpart", "testkey", "testkind", nil); res != nil || err != nil {
		t.Error("Unexpected result:", res, err)
		return
	}

	if _, err := gm.NodeIndexQuery("in valid", "testkind"); err.Error() !=
		"GraphError: Invalid data (Partition name in valid is not alphanumeric - can only contain [a-zA-Z0-9_])" {

		t.Error("Unexpected error:", err)
		return
	}

	if _, err := gm.NodeIndexQuery("testpart", "testkind-"); err.Error() !=
		"GraphError: Invalid data (Node kind testkind- is not alphanumeric - can only contain [a-zA-Z0-9_])" {

		t.Error("Unexpected error:", err)
		return
	}

	if _, err := gm.RemoveNode("in valid", "testkey", "testkind"); err.Error() !=
		"GraphError: Invalid data (Partition name in valid is not alphanumeric - can only contain [a-zA-Z0-9_])" {

		t.Error("Unexpected error:", err)
		return
	}

	if res, err := gm.RemoveNode("testpart", "testkey", "testkind"); res != nil || err != nil {
		t.Error("Unexpected result:", res, err)
		return
	}

	if res, err := gm.NodeIndexQuery("testpart", "testkind"); res != nil || err != nil {
		t.Error("Unexpected result:", res, err)
		return
	}

	if res, err := gm.NodeIndexQuery("testpart", "testkind"); res != nil || err != nil {
		t.Error("Unexpected result:", res, err)
		return
	}

	attTree, valTree, _ := gm.getNodeStorageHTree("testpart", "testkind", true)

	if res, err := gm.readNode("123", "testkind", nil, attTree, valTree); res != nil || err != nil {
		t.Error("Unexpected result:", res, err)
		return
	}

	node1 := data.NewGraphNode()
	node1.SetAttr("key", "123")
	node1.SetAttr("kind", "testkind")
	node1.SetAttr("Name", "Some name")

	if err := gm.StoreNode("testpart", node1); err != nil {
		t.Error(err)
		return
	}

	msm := mgs.StorageManager("testpart"+"testkind"+StorageSuffixNodes,
		true).(*storage.MemoryStorageManager)

	msm.AccessMap[4] = storage.AccessCacheAndFetchError

	if res, err := gm.readNode("123", "testkind", nil, attTree, valTree); res != nil ||
		err.Error() != "GraphError: Could not read graph information "+
			"(Slot not found (mystorage/testparttestkind.nodes - Location:4))" {

		t.Error("Unexpected result:", res, err)
		return
	}

	if res, err := gm.writeNode(node1, true, attTree, valTree, nodeAttributeFilter); res != nil ||
		err.Error() != "GraphError: Could not read graph information "+
			"(Slot not found (mystorage/testparttestkind.nodes - Location:4))" {

		t.Error("Unexpected result:", res, err)
		return
	}

	if res, err := gm.RemoveNode("testpart", "123", "testkind"); res != nil ||
		err.Error() != "GraphError: Could not write graph information "+
			"(Slot not found (mystorage/testparttestkind.nodes - Location:4))" {

		t.Error("Unexpected result:", res, err)
		return
	}

	delete(msm.AccessMap, 4)

	if res, err := gm.RemoveNode("testpart", "1234", "testkind"); res != nil || err != nil {
		t.Error("Unexpected result:", res, err)
		return
	}

	msm.AccessMap[3] = storage.AccessCacheAndFetchError

	if res, err := gm.readNode("123", "testkind", nil, attTree, valTree); res != nil ||
		err.Error() != "GraphError: Could not read graph information "+
			"(Slot not found (mystorage/testparttestkind.nodes - Location:3))" {

		t.Error("Unexpected result:", res, err)
		return
	}

	if res, err := gm.readNode("123", "testkind", []string{"Name"}, attTree, valTree); res != nil ||
		err.Error() != "GraphError: Could not read graph information "+
			"(Slot not found (mystorage/testparttestkind.nodes - Location:3))" {

		t.Error("Unexpected result:", res, err)
		return
	}

	delete(msm.AccessMap, 3)

	node2 := data.NewGraphNode()
	node2.SetAttr("key", "")
	node2.SetAttr("kind", "testkind")
	node2.SetAttr("Name", "Some name2")

	if err := gm.StoreNode("testpart", node2); err.Error() !=
		"GraphError: Invalid data (Node is missing a key value)" {

		t.Error(err)
		return
	}

	node2.SetAttr("key", "456")

	// Test edge to test common error cases with nodes

	edge := data.NewGraphEdge()
	edge.SetAttr("key", "abc")
	edge.SetAttr("kind", "myedge")
	edge.SetAttr(data.EdgeEnd1Key, node1.Key())
	edge.SetAttr(data.EdgeEnd1Kind, node1.Kind())
	edge.SetAttr(data.EdgeEnd1Role, "node1")
	edge.SetAttr(data.EdgeEnd1Cascading, false)

	edge.SetAttr(data.EdgeEnd2Key, node2.Key())
	edge.SetAttr(data.EdgeEnd2Kind, node2.Kind())
	edge.SetAttr(data.EdgeEnd2Role, "node2")
	edge.SetAttr(data.EdgeEnd2Cascading, false)

	if err := gm.StoreNode("testpart ", node2); err.Error() !=
		"GraphError: Invalid data (Partition name testpart  is not alphanumeric - can only contain [a-zA-Z0-9_])" {

		t.Error(err)
		return
	}

	delete(mgs.MainDB(), MainDBNodeCount+"testkind")

	sm := mgs.StorageManager("testpart"+node2.Kind()+StorageSuffixNodes, false).(*storage.MemoryStorageManager)
	sm.AccessMap[1] = storage.AccessCacheAndFetchError

	if err := gm.StoreNode("testpart", node2); err.Error() !=
		"GraphError: Failed to access graph storage component (Slot not found (mystorage/testparttestkind.nodes - Location:1))" {

		t.Error(err)
		return
	}

	delete(sm.AccessMap, 1)

	sm = mgs.StorageManager("testpart"+edge.Kind()+StorageSuffixEdges, true).(*storage.MemoryStorageManager)
	sm.AccessMap[1] = storage.AccessCacheAndFetchError

	sm.SetRoot(RootIDNodeHTree, 1)

	if err := gm.StoreEdge("testpart", edge); err.Error() !=
		"GraphError: Failed to access graph storage component (Slot not found (mystorage/testpartmyedge.edges - Location:1))" {
		t.Error(err)
		return
	}

	delete(sm.AccessMap, 1)

	msm.AccessMap[5] = storage.AccessInsertError

	if err := gm.StoreNode("testpart", node2); err.Error() !=
		"GraphError: Could not write graph information (Record is already in-use (<memory> - ))" {

		t.Error(err)
		return
	}

	delete(msm.AccessMap, 5)

	msm.AccessMap[5] = storage.AccessInsertError

	if err := gm.StoreNode("testpart", node2); err.Error() !=
		"GraphError: Could not write graph information (Record is already in-use (<memory> - ))" {

		t.Error(err)
		return
	}

	delete(msm.AccessMap, 5)

	node2.SetAttr("key", "123")
	node2.SetAttr("Name", nil)

	msm.AccessMap[3] = storage.AccessFreeError

	if err := gm.StoreNode("testpart", node2); err.Error() !=
		"GraphError: Could not write graph information (Slot not found (mystorage/testparttestkind.nodes - Location:3))" {

		t.Error(err)
		return
	}

	delete(msm.AccessMap, 3)

	node2.SetAttr("key", "456")
	node2.SetAttr("Name", "A new name")

	graphstorage.MgsRetFlushMain = &util.GraphError{Type: util.ErrFlushing, Detail: errors.New("Test").Error()}

	if err := gm.StoreNode("testpart", node2); err.Error() !=
		"GraphError: Failed to flush changes (Test)" {

		t.Error(err)
		return
	}

	graphstorage.MgsRetFlushMain = nil

	is := gm.gs.StorageManager("testpart"+"testkind"+StorageSuffixNodesIndex,
		false).(*storage.MemoryStorageManager)

	for i := 0; i < 10; i++ {
		is.AccessMap[uint64(i)] = storage.AccessInsertError
	}

	node2.SetAttr("key", "789")

	if err := gm.StoreNode("testpart", node2); err.Error() !=
		"GraphError: Index error (Record is already in-use (<memory> - ))" {

		t.Error(err)
		return
	}

	node2.SetAttr("key", "123")

	if err := gm.StoreNode("testpart", node2); err.Error() !=
		"GraphError: Index error (Record is already in-use (<memory> - ))" {

		t.Error(err)
		return
	}

	for i := 0; i < 10; i++ {
		is.AccessMap[uint64(i)] = storage.AccessUpdateError
	}

	if res, err := gm.RemoveNode("testpart", "789", "testkind"); !strings.Contains(err.Error(),
		"GraphError: Index error (Slot not found (mystorage/testparttestkind.nodeidx") {

		t.Error("Unexpected result:", res, err)
		return
	}

	for i := 0; i < 10; i++ {
		delete(is.AccessMap, uint64(i))
	}

	msm.AccessMap[9] = storage.AccessCacheAndFetchError

	// This call does delete the node by blowing
	// away the attribute list - the node is removed though its attribute
	// values remain in the datastore

	if res, err := gm.deleteNode("123", "testkind", attTree, valTree); err.Error() !=
		"GraphError: Could not write graph information "+
			"(Slot not found (mystorage/testparttestkind.nodes - Location:9))" {

		t.Error("Unexpected result:", res, err)
		return
	}
	delete(msm.AccessMap, 9)

	if res, err := gm.FetchNodePart("testpart", "123", "testkind", nil); res != nil || err != nil {
		t.Error("Unexpected result:", res, err)
		return
	}

	gm.StoreNode("testpart", node2)

	graphstorage.MgsRetFlushMain = &util.GraphError{Type: util.ErrFlushing, Detail: errors.New("Test").Error()}

	if _, err := gm.RemoveNode("testpart", node2.Key(), node2.Kind()); err.Error() !=
		"GraphError: Failed to flush changes (Test)" {

		t.Error(err)
		return
	}

	graphstorage.MgsRetFlushMain = nil
}

func TestGraphManagerDiskStorage(t *testing.T) {
	if !RunDiskStorageTests {
		return
	}

	dgs, err := graphstorage.NewDiskGraphStorage(GraphManagerTestDBDir1, false)
	if err != nil {
		t.Error(err)
		return
	}

	gm := newGraphManagerNoRules(dgs)

	if gm.Name() != "Graph "+GraphManagerTestDBDir1 {
		t.Error("Unexpected name:", gm.Name())
		return
	}

	sm := dgs.StorageManager("my1", true)

	htree, err := gm.getHTree(sm, RootIDNodeHTree)
	if err != nil {
		t.Error(err)
		return
	}

	htree.Put([]byte("test1"), "testvalue1")

	dgs.Close()

	dgs2, err := graphstorage.NewDiskGraphStorage(GraphManagerTestDBDir1, false)
	if err != nil {
		t.Error(err)
		return
	}

	dgs2.MainDB()[MainDBVersion] = strconv.Itoa(VERSION + 1)
	dgs2.FlushMain()

	testVersionPanic(t, dgs2)

	dgs2.MainDB()[MainDBVersion] = strconv.Itoa(VERSION)
	dgs2.FlushMain()

	// This should now succeed

	newGraphManagerNoRules(dgs2)

	dgs2.MainDB()[MainDBVersion] = strconv.Itoa(VERSION - 1)
	dgs2.FlushMain()

	gm = newGraphManagerNoRules(dgs2)

	if dgs2.MainDB()[MainDBVersion] != strconv.Itoa(VERSION) {
		t.Error("Version should have been corrected")
		return
	}

	sm2 := dgs2.StorageManager("my1", true)

	htree2, err := gm.getHTree(sm2, RootIDNodeHTree)
	if err != nil {
		t.Error(err)
		return
	}

	if val, err := htree2.Get([]byte("test1")); val != "testvalue1" || err != nil {
		t.Error("Unexpected result:", val, err)
		return
	}

	dgs2.Close()

	// Test error cases

	msm := storage.NewMemoryStorageManager("mytest")

	msm.AccessMap[1] = storage.AccessInsertError

	_, err = gm.getHTree(msm, RootIDNodeHTree)

	if err.(*util.GraphError).Type != util.ErrAccessComponent {
		t.Error(err)
		return
	}

	delete(msm.AccessMap, 1)

	msm.SetRoot(RootIDNodeHTree, 2)

	msm.AccessMap[2] = storage.AccessInsertError

	_, err = gm.getHTree(msm, RootIDNodeHTree)

	if err.(*util.GraphError).Type != util.ErrAccessComponent {
		t.Error(err)
		return
	}

	delete(msm.AccessMap, 2)
}

func testVersionPanic(t *testing.T, gs graphstorage.Storage) {
	defer func() {
		if r := recover(); r == nil {
			t.Error("Opening a graph with a newer version did not cause a panic.")
		}
	}()

	newGraphManagerNoRules(gs)
}
