/*
 * 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 (
	"fmt"
	"testing"

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

type TestRule struct {
	handledError    bool
	processingError bool
	commitError     bool
	handles         []int
}

func (r *TestRule) Name() string {
	return "testrule"
}

func (r *TestRule) Handles() []int {
	return r.handles
}

func (r *TestRule) Handle(gm *Manager, trans Trans, event int, ed ...interface{}) error {
	if r.handledError {
		return ErrEventHandled
	}
	if r.processingError {
		return &util.GraphError{Type: util.ErrAccessComponent, Detail: "Test error"}
	}

	if r.commitError {
		node := data.NewGraphNode()
		node.SetAttr("key", "123")
		node.SetAttr("kind", "bla")

		edge := data.NewGraphEdge()
		edge.SetAttr("key", "123")
		edge.SetAttr("kind", "myedge")

		edge.SetAttr(data.EdgeEnd1Key, node.Key())
		edge.SetAttr(data.EdgeEnd1Kind, node.Kind())
		edge.SetAttr(data.EdgeEnd1Role, "node1")
		edge.SetAttr(data.EdgeEnd1Cascading, false)

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

		trans.StoreEdge("test", edge)
	}

	return nil
}

func TestCascadingLastRules(t *testing.T) {

	mgs := graphstorage.NewMemoryGraphStorage("mystorage")
	gm := NewGraphManager(mgs)

	constructEdge := func(key string, node1 data.Node, node2 data.Node) data.Edge {
		edge := data.NewGraphEdge()

		edge.SetAttr("key", key)
		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, true)
		edge.SetAttr(data.EdgeEnd1CascadingLast, true)

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

		edge.SetAttr(data.NodeName, "Edge1"+key)

		return edge
	}

	node1 := data.NewGraphNode()
	node1.SetAttr("key", "123")
	node1.SetAttr("kind", "Artist")
	node1.SetAttr("Name", "Artist1")
	gm.StoreNode("main", node1)

	node2 := data.NewGraphNode()
	node2.SetAttr("key", "456")
	node2.SetAttr("kind", "Song")
	node2.SetAttr("Name", "Song1")
	gm.StoreNode("main", node2)

	node3 := data.NewGraphNode()
	node3.SetAttr("key", "789")
	node3.SetAttr("kind", "Song")
	node3.SetAttr("Name", "Song2")
	gm.StoreNode("main", node3)

	gm.StoreEdge("main", constructEdge("abc1", node2, node1))
	gm.StoreEdge("main", constructEdge("abc2", node3, node1))

	gm.RemoveNode("main", "456", "Song")

	n, _ := gm.FetchNode("main", "123", "Artist")
	if n == nil {
		t.Error("Artist node should have not been deleted")
		return
	}

	gm.RemoveNode("main", "789", "Song")

	n, _ = gm.FetchNode("main", "123", "Artist")
	if n != nil {
		t.Error("Last removed Song node should have deleted the Artist node")
		return
	}

	// Now again but with multiple relationships

	mgs = graphstorage.NewMemoryGraphStorage("mystorage")
	gm = NewGraphManager(mgs)

	node1 = data.NewGraphNode()
	node1.SetAttr("key", "123")
	node1.SetAttr("kind", "Artist")
	node1.SetAttr("Name", "Artist1")
	gm.StoreNode("main", node1)

	node2 = data.NewGraphNode()
	node2.SetAttr("key", "456")
	node2.SetAttr("kind", "Song")
	node2.SetAttr("Name", "Song1")
	gm.StoreNode("main", node2)

	gm.StoreEdge("main", constructEdge("abc1", node2, node1))
	gm.StoreEdge("main", constructEdge("abc2", node2, node1))

	gm.RemoveNode("main", "456", "Song")

	n, _ = gm.FetchNode("main", "123", "Artist")
	if n != nil {
		t.Error("Artist node should have been deleted")
		return
	}
}

func TestRules(t *testing.T) {

	mgs := graphstorage.NewMemoryGraphStorage("mystorage")
	gm := NewGraphManager(mgs)

	constructEdge := func(key string, node1 data.Node, node2 data.Node) data.Edge {
		edge := data.NewGraphEdge()

		edge.SetAttr("key", key)
		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, true)

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

		edge.SetAttr(data.NodeName, "Edge1"+key)

		return edge
	}

	constructGraph := func() {

		mgs = graphstorage.NewMemoryGraphStorage("mystorage")
		gm = NewGraphManager(mgs)

		node1 := data.NewGraphNode()
		node1.SetAttr("key", "123")
		node1.SetAttr("kind", "mynode")
		node1.SetAttr("Name", "Node1")
		gm.StoreNode("main", node1)

		node2 := data.NewGraphNode()
		node2.SetAttr("key", "456")
		node2.SetAttr("kind", "mynewnode")
		node2.SetAttr("Name", "Node2")
		gm.StoreNode("main", node2)

		node3 := data.NewGraphNode()
		node3.SetAttr("key", "789")
		node3.SetAttr("kind", "mynewnode")
		node3.SetAttr("Name", "Node3")
		gm.StoreNode("main", node3)

		node4 := data.NewGraphNode()
		node4.SetAttr("key", "xxx")
		node4.SetAttr("kind", "mynewnode")
		node4.SetAttr("Name", "Node4")
		gm.StoreNode("main", node4)

		gm.StoreEdge("main", constructEdge("abc2", node1, node2))
		gm.StoreEdge("main", constructEdge("abc3", node1, node2))
		gm.StoreEdge("main", constructEdge("abc1", node2, node3))
		gm.StoreEdge("main", constructEdge("abc0", node3, node4))
		gm.StoreEdge("main", constructEdge("abc99", node4, node1))
	}

	// Check rules on empty graph

	if len(gm.NodeKinds()) != 0 {
		t.Error("Unexpected node kinds result")
		return
	}
	if len(gm.Partitions()) != 0 {
		t.Error("Unexpected node kinds result")
		return
	}
	if len(gm.EdgeKinds()) != 0 {
		t.Error("Unexpected node kinds result")
		return
	}

	if len(gm.NodeAttrs("test")) != 0 {
		t.Error("Unexpected node attributes result")
		return
	}

	if _, err := gm.RemoveNode("test", "test", "test"); err != nil {
		t.Error(err)
		return
	}

	constructGraph()

	if res := fmt.Sprint(gm.NodeKinds()); res != "[mynewnode mynode]" {
		t.Error("Unexpected node kinds result:", res)
		return
	}

	if res := fmt.Sprint(gm.EdgeKinds()); res != "[myedge]" {
		t.Error("Unexpected edge kinds result:", res)
		return
	}

	if res := fmt.Sprint(gm.Partitions()); res != "[main]" {
		t.Error("Unexpected partitions result:", res)
		return
	}

	if res := fmt.Sprint(gm.NodeAttrs("mynode")); res != "[Name key kind]" {
		t.Error("Unexpected node attributes result:", res)
		return
	}

	// Test updating a node

	node1 := data.NewGraphNode()
	node1.SetAttr("key", "123")
	node1.SetAttr("kind", "mynode")
	node1.SetAttr("Name2", "Node1Name2")

	gm.StoreNode("main", node1)

	if res := fmt.Sprint(gm.NodeAttrs("mynode")); res != "[Name Name2 key kind]" {
		t.Error("Unexpected node attributes result:", res)
		return
	}

	// Test edge specs

	if res := fmt.Sprint(gm.NodeEdges("mynode")); res != "[node1:myedge:node2:mynewnode node2:myedge:node1:mynewnode]" {
		t.Error("Unexpected node attributes result:", res)
		return
	}

	if res := fmt.Sprint(gm.EdgeAttrs("myedge")); res != "[end1cascading end1key end1kind end1role end2cascading end2key end2kind end2role key kind name]" {
		t.Error("Unexpected node attributes result:", res)
		return
	}

	if c := gm.NodeCount("mynode"); c != 1 {
		t.Error("Unexpected node count:", c)
		return
	} else if c := gm.NodeCount("mynewnode"); c != 3 {
		t.Error("Unexpected node count:", c)
		return
	} else if c := gm.EdgeCount("myedge"); c != 5 {
		t.Error("Unexpected edge count:", c)
		return
	}

	gm.RemoveNode("main", "123", "mynode")

	if c := gm.NodeCount("mynode"); c != 0 {
		t.Error("Unexpected node count:", c)
		return
	} else if c := gm.NodeCount("mynewnode"); c != 0 {
		t.Error("Unexpected node count:", c)
		return
	} else if c := gm.EdgeCount("myedge"); c != 0 {
		t.Error("Unexpected edge count:", c)
		return
	}

	// Finally test a rather esotheric error case

	trans := NewConcurrentGraphTrans(gm)

	err := gm.gr.graphEvent(trans, EventNodeDeleted, "test 1", node1)

	if err.Error() !=
		"GraphError: Graph rule error (GraphError: Invalid data (Partition name "+
			"test 1 is not alphanumeric - can only contain [a-zA-Z0-9_]))" {

		t.Error(err)
		return
	}
}

func TestRulesTrans(t *testing.T) {

	mgs := graphstorage.NewMemoryGraphStorage("mystorage")
	gm := NewGraphManager(mgs)

	constructEdge := func(key string, node1 data.Node, node2 data.Node) data.Edge {
		edge := data.NewGraphEdge()

		edge.SetAttr("key", key)
		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, true)

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

		edge.SetAttr(data.NodeName, "Edge1"+key)

		return edge
	}

	constructGraph := func() {

		mgs = graphstorage.NewMemoryGraphStorage("mystorage")
		gm = NewGraphManager(mgs)

		node1 := data.NewGraphNode()
		node1.SetAttr("key", "123")
		node1.SetAttr("kind", "mynode")
		node1.SetAttr("Name", "Node1")
		gm.StoreNode("main", node1)

		node2 := data.NewGraphNode()
		node2.SetAttr("key", "456")
		node2.SetAttr("kind", "mynewnode")
		node2.SetAttr("Name", "Node2")
		gm.StoreNode("main", node2)

		node3 := data.NewGraphNode()
		node3.SetAttr("key", "789")
		node3.SetAttr("kind", "mynewnode")
		node3.SetAttr("Name", "Node3")
		gm.StoreNode("main", node3)

		node4 := data.NewGraphNode()
		node4.SetAttr("key", "xxx")
		node4.SetAttr("kind", "mynewnode")
		node4.SetAttr("Name", "Node4")
		gm.StoreNode("main", node4)

		gm.StoreEdge("main", constructEdge("abc2", node1, node2))
		gm.StoreEdge("main", constructEdge("abc3", node1, node2))
		gm.StoreEdge("main", constructEdge("abc1", node2, node3))
		gm.StoreEdge("main", constructEdge("abc0", node3, node4))
		gm.StoreEdge("main", constructEdge("abc99", node4, node1))
	}

	// Check rules on empty graph

	if _, err := gm.RemoveNode("test", "test", "test"); err != nil {
		t.Error(err)
		return
	}

	constructGraph()

	if c := gm.NodeCount("mynode"); c != 1 {
		t.Error("Unexpected node count:", c)
		return
	} else if c := gm.NodeCount("mynewnode"); c != 3 {
		t.Error("Unexpected node count:", c)
		return
	} else if c := gm.EdgeCount("myedge"); c != 5 {
		t.Error("Unexpected edge count:", c)
		return
	}

	trans := NewConcurrentGraphTrans(gm)
	trans.RemoveNode("main", "123", "mynode")

	if err := trans.Commit(); err != nil {
		t.Error(err)
		return
	}

	if c := gm.NodeCount("mynode"); c != 0 {
		t.Error("Unexpected node count:", c)
		return
	} else if c := gm.NodeCount("mynewnode"); c != 0 {
		t.Error("Unexpected node count:", c)
		return
	} else if c := gm.EdgeCount("myedge"); c != 0 {
		t.Error("Unexpected edge count:", c)
		return
	}
}

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

	tr := &TestRule{false, false, false, []int{EventNodeCreated, EventNodeUpdated, EventNodeDeleted,
		EventEdgeCreated, EventEdgeUpdated, EventEdgeDeleted}}

	gm.SetGraphRule(tr)

	// Check that the test rule was added

	if rules := fmt.Sprint(gm.GraphRules()); rules !=
		"[system.deletenodeedges system.updatenodestats testrule]" {
		t.Error("unexpected graph rule list:", rules)
		return
	}

	node1 := data.NewGraphNode()
	node1.SetAttr("key", "456")
	node1.SetAttr("kind", "mynode")
	node1.SetAttr("Name", "Node1")

	tr.processingError = true
	tr.commitError = false

	if err := gm.StoreNode("main", node1); err.Error() !=
		"GraphError: Graph rule error (GraphError: Failed to access graph storage component (Test error))" {
		t.Error(err)
		return
	}

	// Make sure the node was still added

	if res, err := gm.FetchNode("main", "456", "mynode"); res == nil || err != nil {
		t.Error("Unexpeccted result:", res, err)
		return
	}

	tr.processingError = false
	tr.commitError = true

	if err := gm.StoreNode("main", node1); err.Error() !=
		"GraphError: Invalid data (Can't store edge to non-existing node kind: bla)" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = true
	tr.commitError = false

	if _, err := gm.RemoveNode("main", node1.Key(), node1.Kind()); err.Error() !=
		"GraphError: Graph rule error (GraphError: Failed to access graph storage component (Test error))" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = false
	tr.commitError = true

	if _, err := gm.RemoveNode("main", node1.Key(), node1.Kind()); err.Error() !=
		"GraphError: Invalid data (Can't store edge to non-existing node kind: bla)" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = true
	tr.commitError = false

	edge := data.NewGraphEdge()
	edge.SetAttr("key", "123")
	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, node1.Key())
	edge.SetAttr(data.EdgeEnd2Kind, node1.Kind())
	edge.SetAttr(data.EdgeEnd2Role, "node2")
	edge.SetAttr(data.EdgeEnd2Cascading, false)

	if err := gm.StoreEdge("main", edge); err.Error() !=
		"GraphError: Graph rule error (GraphError: Failed to access graph storage component (Test error))" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = true

	if err := gm.StoreEdge("main", edge); err.Error() !=
		"GraphError: Invalid data (Can't store edge to non-existing node kind: bla)" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = false
	tr.commitError = true

	if _, err := gm.RemoveEdge("main", edge.Key(), edge.Kind()); err.Error() !=
		"GraphError: Invalid data (Can't store edge to non-existing node kind: bla)" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = true
	tr.commitError = false

	if _, err := gm.RemoveEdge("main", edge.Key(), edge.Kind()); err.Error() !=
		"GraphError: Graph rule error (GraphError: Failed to access graph storage component (Test error))" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	// Test transaction errors

	trans := NewConcurrentGraphTrans(gm)

	tr.processingError = true
	tr.commitError = false

	trans.StoreNode("main", node1)

	if err := trans.Commit(); err.Error() !=
		"GraphError: Graph rule error (GraphError: Failed to access graph storage component (Test error))" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = true

	trans.StoreNode("main", node1)

	if err := trans.Commit(); err.Error() !=
		"GraphError: Invalid data (Can't store edge to non-existing node kind: bla)" {
		t.Error(err)
		return
	}

	tr.processingError = true
	tr.commitError = false

	trans.RemoveNode("main", node1.Key(), node1.Kind())

	if err := trans.Commit(); err.Error() !=
		"GraphError: Graph rule error (GraphError: Failed to access graph storage component (Test error))" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = false
	tr.commitError = true

	trans.RemoveNode("main", node1.Key(), node1.Kind())

	if err := trans.Commit(); err.Error() !=
		"GraphError: Invalid data (Can't store edge to non-existing node kind: bla)" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = true
	tr.commitError = false

	trans.StoreEdge("main", edge)

	if err := trans.Commit(); err.Error() !=
		"GraphError: Graph rule error (GraphError: Failed to access graph storage component (Test error))" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = true

	trans.StoreEdge("main", edge)

	if err := trans.Commit(); err.Error() !=
		"GraphError: Invalid data (Can't store edge to non-existing node kind: bla)" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = false
	tr.commitError = true

	trans.RemoveEdge("main", edge.Key(), edge.Kind())

	if err := trans.Commit(); err.Error() !=
		"GraphError: Invalid data (Can't store edge to non-existing node kind: bla)" {
		t.Error(err)
		return
	}

	tr.processingError = false
	tr.commitError = false

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

	tr.processingError = true
	tr.commitError = false

	trans.RemoveEdge("main", edge.Key(), edge.Kind())

	if err := trans.Commit(); err.Error() !=
		"GraphError: Graph rule error (GraphError: Failed to access graph storage component (Test error))" {
		t.Error(err)
		return
	}
}

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

	tr := &TestRule{false, false, false, []int{EventNodeStore, EventNodeUpdate, EventNodeDelete,
		EventEdgeStore, EventEdgeDelete}}

	gm.SetGraphRule(tr)

	// Check that the test rule was added

	if rules := fmt.Sprint(gm.GraphRules()); rules !=
		"[system.deletenodeedges system.updatenodestats testrule]" {
		t.Error("unexpected graph rule list:", rules)
		return
	}

	// Test that we can stop the storage of a node with custom processing

	node1 := data.NewGraphNode()
	node1.SetAttr("key", "456")
	node1.SetAttr("kind", "mynode")
	node1.SetAttr("Name", "Node1")

	tr.handledError = true

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

	// Make sure the node was not added

	if res, err := gm.FetchNode("main", "456", "mynode"); res != nil || err != nil {
		t.Error("Unexpeccted result:", res, err)
		return
	}

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

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

	edge := data.NewGraphEdge()
	edge.SetAttr("key", "123")
	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, node1.Key())
	edge.SetAttr(data.EdgeEnd2Kind, node1.Kind())
	edge.SetAttr(data.EdgeEnd2Role, "node2")
	edge.SetAttr(data.EdgeEnd2Cascading, false)

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

	if _, err := gm.RemoveEdge("main", edge.Key(), edge.Kind()); err != nil {
		t.Error(err)
		return
	}
}
