// Copyright (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"io/ioutil"
	"regexp"
	"strings"
	"testing"
	"time"

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

type testData struct {
	network     string
	raddr       string
	expectError bool
}

func init() {
	// Ensure all log levels are logged
	ccLog.Logger.Level = logrus.DebugLevel

	// Discard log output
	ccLog.Logger.Out = ioutil.Discard
}

func TestHandleSystemLog(t *testing.T) {
	assert := assert.New(t)

	data := []testData{
		{"invalid-net-type", "999.999.999.999", true},
		{"invalid net-type", "a a ", true},
		{"invalid-net-type", ".", true},
		{"moo", "999.999.999.999", true},
		{"moo", "999.999.999.999:99999999999999999", true},
		{"qwerty", "uiop:ftw!", true},
		{"", "", false},
	}

	for _, d := range data {
		err := handleSystemLog(d.network, d.raddr)
		if d.expectError {
			assert.Error(err, fmt.Sprintf("%+v", d))
		} else {
			assert.NoError(err, fmt.Sprintf("%+v", d))
		}
	}
}

func TestNewSystemLogHook(t *testing.T) {
	assert := assert.New(t)

	hook, err := newSystemLogHook("", "")
	assert.NoError(err)

	msg := "wibble"
	level := logrus.DebugLevel

	logger := logrus.New()

	// ensure all output is displayed
	logger.Level = logrus.DebugLevel

	// throw away all stdout so that the Format() call
	// below returns the data in structured form.
	logger.Out = ioutil.Discard

	entry := &logrus.Entry{
		Logger: logger,

		// UTC for time.Parse()
		Time: time.Now().UTC(),

		Message: msg,
		Level:   level,
	}

	// call the formatting function directly and see if the output
	// matches what we expect.
	bytes, err := hook.formatter.Format(entry)
	assert.NoError(err)

	output := string(bytes)
	output = strings.TrimSpace(output)
	output = strings.Replace(output, `"`, "", -1)

	fields := strings.Fields(output)

	msgFound := ""
	timeFound := ""
	levelFound := ""

	// look for the expected fields
	for _, field := range fields {

		// split each structured field into name and value fields
		f := strings.Split(field, "=")
		assert.True(len(f) >= 2)

		switch f[0] {
		case "level":
			levelFound = f[1]
		case "msg":
			msgFound = f[1]
		case "time":
			timeFound = f[1]
		}
	}

	assert.Equal(levelFound, level.String())
	assert.Equal(msgFound, msg)
	assert.NotEqual(timeFound, "")

	// Tell time.Parse() how to handle the timestamps by putting it into
	// the standard golang time format equivalent to:
	//
	//     "Mon Jan 2 15:04:05 -0700 MST 2006".
	//
	expectedTimeFormat := "2006-01-02T15:04:05.999999999Z"

	// Note that time.Parse() assumes a UTC time.
	_, err = time.Parse(expectedTimeFormat, timeFound)
	assert.NoError(err)

	// time.Parse() is "clever" but also doesn't check anything more
	// granular than a second, so let's be completely paranoid and check
	// via regular expression too.
	expectedPattern :=
		// YYYY-MM-DD
		`\d{4}-\d{2}-\d{2}` +
			// time separator
			`T` +
			// HH:MM:SS
			`\d{2}:\d{2}:\d{2}` +
			// high-precision separator
			`.` +
			// nano-seconds. Note that the quantifier range is
			// required because the time.RFC3339Nano format
			// trunctates trailing zeros.
			`\d{1,9}` +
			// UTC timezone specifier
			`Z`

	expectedRE := regexp.MustCompile(expectedPattern)
	matched := expectedRE.FindAllStringSubmatch(timeFound, -1)
	assert.NotNil(matched, "expected time in format %q, got %q", expectedPattern, timeFound)
}
