/*
 * ECAL
 *
 * Copyright 2020 Matthias Ladkau. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the MIT
 * License, If a copy of the MIT License was not distributed with this
 * file, You can obtain one at https://opensource.org/licenses/MIT.
 */

package engine

import (
	"fmt"
	"regexp"
	"sort"
	"testing"
)

func TestRuleIndexSimple(t *testing.T) {
	ruleindexidcounter = 0
	defer func() {
		ruleindexidcounter = 0
	}()

	// Store a simple rule

	rule := &Rule{
		"TestRule", // Name
		"",         // Description
		[]string{"core.main.tester", "core.tmp.*"}, // Kind match
		[]string{"data.read", "data.test"},         // Match on event cascade scope
		nil,
		0,                      // Priority of the rule
		[]string{"TestRule66"}, // List of suppressed rules by this rule
		func(p Processor, m Monitor, e *Event) error { // Action of the rule
			return nil
		},
	}

	index := NewRuleIndex()

	index.AddRule(rule)

	// Check error cases

	err := index.AddRule(&Rule{
		"TestRuleError",              // Name
		"",                           // Description
		[]string{"core.main.tester"}, // Kind match
		nil,
		nil,
		0,                      // Priority of the rule
		[]string{"TestRule66"}, // List of suppressed rules by this rule
		func(p Processor, m Monitor, e *Event) error { // Action of the rule
			return nil
		},
	})
	if err.Error() != "Cannot add rule without a scope match: TestRuleError" {
		t.Error("Unexpected result:", err)
		return
	}

	err = index.AddRule(&Rule{
		"TestRuleError2",                   // Name
		"",                                 // Description
		nil,                                // Kind match
		[]string{"data.read", "data.test"}, // Match on event cascade scope
		nil,
		0,                      // Priority of the rule
		[]string{"TestRule66"}, // List of suppressed rules by this rule
		func(p Processor, m Monitor, e *Event) error { // Action of the rule
			return nil
		},
	})
	if err.Error() != "Cannot add rule without a kind match: TestRuleError2" {
		t.Error("Unexpected result:", err)
		return
	}

	// Check index layout

	if res := index.String(); res != `
core - RuleIndexKind (0)
  main - RuleIndexKind (1)
    tester - RuleIndexKind (2)
      RuleIndexAll (3)
        Rule:TestRule [] (Priority:0 Kind:[core.main.tester core.tmp.*] Scope:[data.read data.test] StateMatch:null Suppress:[TestRule66])
  tmp - RuleIndexKind (1)
    * - RuleIndexKind (4)
      RuleIndexAll (5)
        Rule:TestRule [] (Priority:0 Kind:[core.main.tester core.tmp.*] Scope:[data.read data.test] StateMatch:null Suppress:[TestRule66])
`[1:] {
		t.Error("Unexpected index layout:", res)
		return
	}

	// Check trigger queries

	if !index.IsTriggering(&Event{
		"bla",
		[]string{"core", "tmp", "bla"},
		nil,
	}) {
		t.Error("Unexpected result")
		return
	}

	if index.IsTriggering(&Event{
		"bla",
		[]string{"core", "tmp"},
		nil,
	}) {
		t.Error("Unexpected result")
		return
	}

	if index.IsTriggering(&Event{
		"bla",
		[]string{"core", "tmpp", "bla"},
		nil,
	}) {
		t.Error("Unexpected result")
		return
	}

	if !index.IsTriggering(&Event{
		"bla",
		[]string{"core", "main", "tester"},
		nil,
	}) {
		t.Error("Unexpected result")
		return
	}

	if index.IsTriggering(&Event{
		"bla",
		[]string{"core", "main", "tester", "bla"},
		nil,
	}) {
		t.Error("Unexpected result")
		return
	}

	if index.IsTriggering(&Event{
		"bla",
		[]string{"core", "main", "teste"},
		nil,
	}) {
		t.Error("Unexpected result")
		return
	}

	if index.IsTriggering(&Event{
		"bla",
		[]string{"core", "main"},
		nil,
	}) {
		t.Error("Unexpected result")
		return
	}

	// Event matching

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "main", "tester"},
		nil,
	}); printRules(res) != "[TestRule]" {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "tmp", "x"},
		nil,
	}); printRules(res) != "[TestRule]" {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "tmp"},
		nil,
	}); printRules(res) != "[]" {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "tmp", "x", "y"},
		nil,
	}); printRules(res) != "[]" {
		t.Error("Unexpected result:", res)
		return
	}
}

func TestRuleIndexStateMatch(t *testing.T) {
	ruleindexidcounter = 0
	defer func() {
		ruleindexidcounter = 0
	}()

	rule1 := &Rule{
		"TestRule1", // Name
		"",          // Description
		[]string{"core.main.tester", "core.tmp.*"}, // Kind match
		[]string{"data.read", "data.test"},         // Match on event cascade scope
		map[string]interface{}{ // Match on event state
			"name": nil,
			"test": "val1",
		},
		0,                      // Priority of the rule
		[]string{"TestRule66"}, // List of suppressed rules by this rule
		func(p Processor, m Monitor, e *Event) error { // Action of the rule
			return nil
		},
	}

	rule2 := &Rule{
		"TestRule2",                  // Name
		"",                           // Description
		[]string{"core.main.tester"}, // Kind match
		[]string{"data.read"},        // Match on event cascade scope
		map[string]interface{}{ // Match on event state
			"name":  nil,
			"test":  "val2",
			"test2": 42,
		},
		0,                      // Priority of the rule
		[]string{"TestRule66"}, // List of suppressed rules by this rule
		func(p Processor, m Monitor, e *Event) error { // Action of the rule
			return nil
		},
	}

	rule3 := &Rule{
		"TestRule3",                  // Name
		"",                           // Description
		[]string{"core.main.tester"}, // Kind match
		[]string{"data.read"},        // Match on event cascade scope
		map[string]interface{}{ // Match on event state
			"name":  nil,
			"test":  "val2",
			"test2": 42,
			"test3": 15,
		},
		0,                      // Priority of the rule
		[]string{"TestRule66"}, // List of suppressed rules by this rule
		func(p Processor, m Monitor, e *Event) error { // Action of the rule
			return nil
		},
	}

	index := NewRuleIndex()

	index.AddRule(rule1)
	index.AddRule(rule2)
	index.AddRule(rule3)

	if err := index.AddRule(rule3); err.Error() != "Cannot add rule TestRule3 twice" {
		t.Error("Unexpected result:", err)
		return
	}

	if len(index.Rules()) != 3 {
		t.Error("Unexpected number of rules:", len(index.Rules()))
	}

	// Check index layout

	if res := index.String(); res != `
core - RuleIndexKind (0)
  main - RuleIndexKind (1)
    tester - RuleIndexKind (2)
      RuleIndexState (3) [TestRule1 TestRule2 TestRule3 ]
        name - 00000007 *:00000007 [] []
        test - 00000007 *:00000000 [val1:00000001 val2:00000006 ] []
        test2 - 00000006 *:00000000 [42:00000006 ] []
        test3 - 00000004 *:00000000 [15:00000004 ] []
  tmp - RuleIndexKind (1)
    * - RuleIndexKind (4)
      RuleIndexState (5) [TestRule1 ]
        name - 00000001 *:00000001 [] []
        test - 00000001 *:00000000 [val1:00000001 ] []
`[1:] {
		t.Error("Unexpected index layout:", res)
		return
	}

	// Make sure events without state do not match

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "tmp", "x"},
		nil,
	}); printRules(res) != "[]" {
		t.Error("Unexpected result:", res)
		return
	}

	// Single rule match

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "tmp", "x"},
		map[interface{}]interface{}{ // Match on event state
			"name": nil,
			"test": "val1",
		},
	}); printRules(res) != "[TestRule1]" {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "main", "tester"},
		map[interface{}]interface{}{ // Match on event state
			"name": nil,
			"test": "val1",
		},
	}); printRules(res) != "[TestRule1]" {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "main", "tester"},
		map[interface{}]interface{}{ // Match on event state
			"name":  "foobar",
			"test":  "val2",
			"test2": 42,
		},
	}); printRules(res) != "[TestRule2]" {
		t.Error("Unexpected result:", res)
		return
	}

	// Test multiple rule match

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "main", "tester"},
		map[interface{}]interface{}{ // Match on event state
			"name":  nil,
			"test":  "val2",
			"test2": 42,
			"test3": 15,
		},
	}); printRules(res) != "[TestRule2 TestRule3]" {
		t.Error("Unexpected result:", res)
		return
	}
}

func TestRuleIndexStateRegexMatch(t *testing.T) {
	ruleindexidcounter = 0
	defer func() {
		ruleindexidcounter = 0
	}()

	rule1 := &Rule{
		"TestRule1", // Name
		"",          // Description
		[]string{"core.main.tester", "core.tmp.*"}, // Kind match
		[]string{"data.read", "data.test"},         // Match on event cascade scope
		map[string]interface{}{ // Match on event state
			"name": nil,
			"test": regexp.MustCompile("val.*"),
		},
		0,                      // Priority of the rule
		[]string{"TestRule66"}, // List of suppressed rules by this rule
		func(p Processor, m Monitor, e *Event) error { // Action of the rule
			return nil
		},
	}

	rule2 := &Rule{
		"TestRule2",                  // Name
		"",                           // Description
		[]string{"core.main.tester"}, // Kind match
		[]string{"data.read"},        // Match on event cascade scope
		map[string]interface{}{ // Match on event state
			"name": nil,
			"test": regexp.MustCompile("va..*"),
		},
		0,                      // Priority of the rule
		[]string{"TestRule66"}, // List of suppressed rules by this rule
		func(p Processor, m Monitor, e *Event) error { // Action of the rule
			return nil
		},
	}

	index := NewRuleIndex()

	index.AddRule(rule1)
	index.AddRule(rule2)

	// Check index layout

	if res := index.String(); res != `
core - RuleIndexKind (0)
  main - RuleIndexKind (1)
    tester - RuleIndexKind (2)
      RuleIndexState (3) [TestRule1 TestRule2 ]
        name - 00000003 *:00000003 [] []
        test - 00000003 *:00000003 [] [00000001:val.* 00000002:va..* ]
  tmp - RuleIndexKind (1)
    * - RuleIndexKind (4)
      RuleIndexState (5) [TestRule1 ]
        name - 00000001 *:00000001 [] []
        test - 00000001 *:00000001 [] [00000001:val.* ]
`[1:] {
		t.Error("Unexpected index layout:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "tmp", "x"},
		map[interface{}]interface{}{ // Match on event state
			"name": "boo",
			"test": "val1",
		},
	}); printRules(res) != "[TestRule1]" {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "tmp", "x"},
		map[interface{}]interface{}{ // Match on event state
			"name": "boo",
			"test": "val",
		},
	}); printRules(res) != "[TestRule1]" {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "main", "tester"},
		map[interface{}]interface{}{ // Match on event state
			"name": "boo",
			"test": "var",
		},
	}); printRules(res) != "[TestRule2]" {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "main", "tester"},
		map[interface{}]interface{}{ // Match on event state
			"name": "boo",
			"test": "val",
		},
	}); printRules(res) != "[TestRule1 TestRule2]" {
		t.Error("Unexpected result:", res)
		return
	}

	// Test error cases

	if res := index.IsTriggering(&Event{
		"bla",
		[]string{"core", "main", "tester", "a"},
		map[interface{}]interface{}{ // Match on event state
			"name": "boo",
			"test": "val",
		},
	}); res {
		t.Error("Unexpected result:", res)
		return
	}

	if res := index.Match(&Event{
		"bla",
		[]string{"core", "main", "tester", "a"},
		map[interface{}]interface{}{ // Match on event state
			"name": "boo",
			"test": "val",
		},
	}); printRules(res) != "[]" {
		t.Error("Unexpected result:", res)
		return
	}
}

func printRules(rules []*Rule) string {
	var ret []string

	for _, r := range rules {
		ret = append(ret, r.Name)
	}

	sort.Strings(ret)

	return fmt.Sprint(ret)
}
