package log

import (
	"testing"

	"github.com/sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

var h = &Hook{}

type levelsTest struct {
	name           string
	acceptedLevels []logrus.Level
	expected       []logrus.Level
	description    string
}

func TestLevels(t *testing.T) {
	tests := []levelsTest{
		{
			name:           "undefinedAcceptedLevels",
			acceptedLevels: []logrus.Level{},
			expected:       logrus.AllLevels,
			description:    "All logrus levels expected, but did not receive them",
		},
		{
			name:           "definedAcceptedLevels",
			acceptedLevels: []logrus.Level{logrus.InfoLevel},
			expected:       []logrus.Level{logrus.InfoLevel},
			description:    "Logrus Info level expected, but did not receive that.",
		},
	}

	for _, test := range tests {
		fn := func(t *testing.T) {
			h.AcceptedLevels = test.acceptedLevels
			levels := h.Levels()
			assert.Equal(t, test.expected, levels, test.description)
		}

		t.Run(test.name, fn)
	}
}

type levelThresholdTest struct {
	name        string
	level       logrus.Level
	expected    []logrus.Level
	description string
}

// levelThreshold returns a []logrus.Level including all levels
// above and including the level given. If the provided level does not exit,
// an empty slice is returned.
func levelThreshold(l logrus.Level) []logrus.Level {
	//nolint
	if l < 0 || int(l) > len(logrus.AllLevels) {
		return []logrus.Level{}
	}
	return logrus.AllLevels[:l+1]
}

func TestLevelThreshold(t *testing.T) {
	tests := []levelThresholdTest{
		{
			name:        "unknownLogLevel",
			level:       logrus.Level(100),
			expected:    []logrus.Level{},
			description: "An empty Level slice was expected but was not returned",
		},
		{
			name:        "errorLogLevel",
			level:       logrus.ErrorLevel,
			expected:    []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel},
			description: "The panic, fatal, and error levels were expected but were not returned",
		},
	}

	for _, test := range tests {
		fn := func(t *testing.T) {
			levels := levelThreshold(test.level)
			assert.Equal(t, test.expected, levels, test.description)
		}

		t.Run(test.name, fn)
	}
}

func TestInvalidRegex(t *testing.T) {
	e := &logrus.Entry{}
	h = &Hook{RedactionList: []string{"\\"}}
	err := h.Fire(e)

	assert.NotNil(t, err)
}

type EntryDataValuesTest struct {
	name          string
	redactionList []string
	logFields     logrus.Fields
	expected      logrus.Fields
	description   string //nolint
}

// Test that any occurrence of a redaction pattern
// in the values of the entry's data fields is redacted.
func TestEntryDataValues(t *testing.T) {
	tests := []EntryDataValuesTest{
		{
			name:          "match on key",
			redactionList: []string{"Password"},
			logFields:     logrus.Fields{"Password": "password123!"},
			expected:      logrus.Fields{"Password": "[REDACTED]"},
			description:   "Password value should have been redacted, but was not.",
		},
		{
			name:          "string value",
			redactionList: []string{"William"},
			logFields:     logrus.Fields{"Description": "His name is William"},
			expected:      logrus.Fields{"Description": "His name is [REDACTED]"},
			description:   "William should have been redacted, but was not.",
		},
		{
			name:          "map value",
			redactionList: []string{"William"},
			logFields:     logrus.Fields{"Description": map[string]string{"name": "His name is William"}},
			expected:      logrus.Fields{"Description": "map[name:His name is [REDACTED]]"},
			description:   "William should have been redacted, but was not.",
		},
	}

	for _, test := range tests {
		fn := func(t *testing.T) {
			logEntry := &logrus.Entry{
				Data: test.logFields,
			}
			h = &Hook{RedactionList: test.redactionList}
			err := h.Fire(logEntry)

			assert.Nil(t, err)
			assert.Equal(t, test.expected, logEntry.Data)
		}
		t.Run(test.name, fn)
	}
}

// Test that any occurrence of a redaction pattern
// in the entry's Message field is redacted.
func TestEntryMessage(t *testing.T) {
	logEntry := &logrus.Entry{
		Message: "Secret Password: password123!",
	}
	h = &Hook{RedactionList: []string{`(Password: ).*`}}
	err := h.Fire(logEntry)

	assert.Nil(t, err)
	assert.Equal(t, "Secret Password: [REDACTED]", logEntry.Message)
}
