/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package fabenc_test

import (
	"bytes"
	"fmt"
	"runtime"
	"strconv"
	"sync"
	"testing"
	"time"

	"github.com/hyperledger/fabric/common/flogging/fabenc"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func TestParseFormat(t *testing.T) {
	var tests = []struct {
		desc       string
		spec       string
		formatters []fabenc.Formatter
	}{
		{
			desc:       "empty spec",
			spec:       "",
			formatters: []fabenc.Formatter{},
		},
		{
			desc: "simple verb",
			spec: "%{color}",
			formatters: []fabenc.Formatter{
				fabenc.ColorFormatter{},
			},
		},
		{
			desc: "with prefix",
			spec: "prefix %{color}",
			formatters: []fabenc.Formatter{
				fabenc.StringFormatter{Value: "prefix "},
				fabenc.ColorFormatter{},
			},
		},
		{
			desc: "with suffix",
			spec: "%{color} suffix",
			formatters: []fabenc.Formatter{
				fabenc.ColorFormatter{},
				fabenc.StringFormatter{Value: " suffix"}},
		},
		{
			desc: "with prefix and suffix",
			spec: "prefix %{color} suffix",
			formatters: []fabenc.Formatter{
				fabenc.StringFormatter{Value: "prefix "},
				fabenc.ColorFormatter{},
				fabenc.StringFormatter{Value: " suffix"},
			},
		},
		{
			desc: "with format",
			spec: "%{level:.4s} suffix",
			formatters: []fabenc.Formatter{
				fabenc.LevelFormatter{FormatVerb: "%.4s"},
				fabenc.StringFormatter{Value: " suffix"},
			},
		},
	}

	for _, tc := range tests {
		t.Run(fmt.Sprintf(tc.desc), func(t *testing.T) {
			formatters, err := fabenc.ParseFormat(tc.spec)
			require.NoError(t, err)
			require.Equal(t, tc.formatters, formatters)
		})
	}
}

func TestParseFormatError(t *testing.T) {
	_, err := fabenc.ParseFormat("%{color:bad}")
	require.EqualError(t, err, "invalid color option: bad")
}

func TestNewFormatter(t *testing.T) {
	var tests = []struct {
		verb      string
		format    string
		formatter fabenc.Formatter
		errorMsg  string
	}{
		{verb: "color", format: "", formatter: fabenc.ColorFormatter{}},
		{verb: "color", format: "bold", formatter: fabenc.ColorFormatter{Bold: true}},
		{verb: "color", format: "reset", formatter: fabenc.ColorFormatter{Reset: true}},
		{verb: "color", format: "unknown", errorMsg: "invalid color option: unknown"},
		{verb: "id", format: "", formatter: fabenc.SequenceFormatter{FormatVerb: "%d"}},
		{verb: "id", format: "04x", formatter: fabenc.SequenceFormatter{FormatVerb: "%04x"}},
		{verb: "level", format: "", formatter: fabenc.LevelFormatter{FormatVerb: "%s"}},
		{verb: "level", format: ".4s", formatter: fabenc.LevelFormatter{FormatVerb: "%.4s"}},
		{verb: "message", format: "", formatter: fabenc.MessageFormatter{FormatVerb: "%s"}},
		{verb: "message", format: "#30s", formatter: fabenc.MessageFormatter{FormatVerb: "%#30s"}},
		{verb: "module", format: "", formatter: fabenc.ModuleFormatter{FormatVerb: "%s"}},
		{verb: "module", format: "ok", formatter: fabenc.ModuleFormatter{FormatVerb: "%ok"}},
		{verb: "shortfunc", format: "", formatter: fabenc.ShortFuncFormatter{FormatVerb: "%s"}},
		{verb: "shortfunc", format: "U", formatter: fabenc.ShortFuncFormatter{FormatVerb: "%U"}},
		{verb: "time", format: "", formatter: fabenc.TimeFormatter{Layout: "2006-01-02T15:04:05.999Z07:00"}},
		{verb: "time", format: "04:05.999999Z05:00", formatter: fabenc.TimeFormatter{Layout: "04:05.999999Z05:00"}},
		{verb: "unknown", format: "", errorMsg: "unknown verb: unknown"},
	}

	for i, tc := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			f, err := fabenc.NewFormatter(tc.verb, tc.format)
			if tc.errorMsg == "" {
				require.NoError(t, err)
				require.Equal(t, tc.formatter, f)
			} else {
				require.EqualError(t, err, tc.errorMsg)
			}
		})
	}
}

func TestColorFormatter(t *testing.T) {
	var tests = []struct {
		f         fabenc.ColorFormatter
		level     zapcore.Level
		formatted string
	}{
		{f: fabenc.ColorFormatter{Reset: true}, level: zapcore.DebugLevel, formatted: fabenc.ResetColor()},
		{f: fabenc.ColorFormatter{}, level: zapcore.DebugLevel, formatted: fabenc.ColorCyan.Normal()},
		{f: fabenc.ColorFormatter{Bold: true}, level: zapcore.DebugLevel, formatted: fabenc.ColorCyan.Bold()},
		{f: fabenc.ColorFormatter{}, level: zapcore.InfoLevel, formatted: fabenc.ColorBlue.Normal()},
		{f: fabenc.ColorFormatter{Bold: true}, level: zapcore.InfoLevel, formatted: fabenc.ColorBlue.Bold()},
		{f: fabenc.ColorFormatter{}, level: zapcore.WarnLevel, formatted: fabenc.ColorYellow.Normal()},
		{f: fabenc.ColorFormatter{Bold: true}, level: zapcore.WarnLevel, formatted: fabenc.ColorYellow.Bold()},
		{f: fabenc.ColorFormatter{}, level: zapcore.ErrorLevel, formatted: fabenc.ColorRed.Normal()},
		{f: fabenc.ColorFormatter{Bold: true}, level: zapcore.ErrorLevel, formatted: fabenc.ColorRed.Bold()},
		{f: fabenc.ColorFormatter{}, level: zapcore.DPanicLevel, formatted: fabenc.ColorMagenta.Normal()},
		{f: fabenc.ColorFormatter{Bold: true}, level: zapcore.DPanicLevel, formatted: fabenc.ColorMagenta.Bold()},
		{f: fabenc.ColorFormatter{}, level: zapcore.PanicLevel, formatted: fabenc.ColorMagenta.Normal()},
		{f: fabenc.ColorFormatter{Bold: true}, level: zapcore.PanicLevel, formatted: fabenc.ColorMagenta.Bold()},
		{f: fabenc.ColorFormatter{}, level: zapcore.FatalLevel, formatted: fabenc.ColorMagenta.Normal()},
		{f: fabenc.ColorFormatter{Bold: true}, level: zapcore.FatalLevel, formatted: fabenc.ColorMagenta.Bold()},
		{f: fabenc.ColorFormatter{}, level: zapcore.Level(99), formatted: fabenc.ColorNone.Normal()},
		{f: fabenc.ColorFormatter{Bold: true}, level: zapcore.Level(99), formatted: fabenc.ColorNone.Normal()},
	}

	for i, tc := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			buf := &bytes.Buffer{}
			entry := zapcore.Entry{Level: tc.level}
			tc.f.Format(buf, entry, nil)
			require.Equal(t, tc.formatted, buf.String())
		})
	}
}

func TestLevelFormatter(t *testing.T) {
	var tests = []struct {
		level     zapcore.Level
		formatted string
	}{
		{level: zapcore.DebugLevel, formatted: "DEBUG"},
		{level: zapcore.InfoLevel, formatted: "INFO"},
		{level: zapcore.WarnLevel, formatted: "WARN"},
		{level: zapcore.ErrorLevel, formatted: "ERROR"},
		{level: zapcore.DPanicLevel, formatted: "DPANIC"},
		{level: zapcore.PanicLevel, formatted: "PANIC"},
		{level: zapcore.FatalLevel, formatted: "FATAL"},
		{level: zapcore.Level(99), formatted: "LEVEL(99)"},
	}

	for i, tc := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			buf := &bytes.Buffer{}
			entry := zapcore.Entry{Level: tc.level}
			fabenc.LevelFormatter{FormatVerb: "%s"}.Format(buf, entry, nil)
			require.Equal(t, tc.formatted, buf.String())
		})
	}
}

func TestMessageFormatter(t *testing.T) {
	buf := &bytes.Buffer{}
	entry := zapcore.Entry{Message: "some message text \n\n"}
	f := fabenc.MessageFormatter{FormatVerb: "%s"}
	f.Format(buf, entry, nil)
	require.Equal(t, "some message text ", buf.String())
}

func TestModuleFormatter(t *testing.T) {
	buf := &bytes.Buffer{}
	entry := zapcore.Entry{LoggerName: "logger/name"}
	f := fabenc.ModuleFormatter{FormatVerb: "%s"}
	f.Format(buf, entry, nil)
	require.Equal(t, "logger/name", buf.String())
}

func TestSequenceFormatter(t *testing.T) {
	mutex := &sync.Mutex{}
	results := map[string]struct{}{}

	ready := &sync.WaitGroup{}
	ready.Add(100)

	finished := &sync.WaitGroup{}
	finished.Add(100)

	fabenc.SetSequence(0)
	for i := 1; i <= 100; i++ {
		go func(i int) {
			buf := &bytes.Buffer{}
			entry := zapcore.Entry{Level: zapcore.DebugLevel}
			f := fabenc.SequenceFormatter{FormatVerb: "%d"}
			ready.Done() // setup complete
			ready.Wait() // wait for all go routines to be ready

			f.Format(buf, entry, nil) // format concurrently

			mutex.Lock()
			results[buf.String()] = struct{}{}
			mutex.Unlock()

			finished.Done()
		}(i)
	}

	finished.Wait()
	for i := 1; i <= 100; i++ {
		require.Contains(t, results, strconv.Itoa(i))
	}
}

func TestShortFuncFormatter(t *testing.T) {
	callerpc, _, _, ok := runtime.Caller(0)
	require.True(t, ok)
	buf := &bytes.Buffer{}
	entry := zapcore.Entry{Caller: zapcore.EntryCaller{PC: callerpc}}
	fabenc.ShortFuncFormatter{FormatVerb: "%s"}.Format(buf, entry, nil)
	require.Equal(t, "TestShortFuncFormatter", buf.String())

	buf = &bytes.Buffer{}
	entry = zapcore.Entry{Caller: zapcore.EntryCaller{PC: 0}}
	fabenc.ShortFuncFormatter{FormatVerb: "%s"}.Format(buf, entry, nil)
	require.Equal(t, "(unknown)", buf.String())
}

func TestTimeFormatter(t *testing.T) {
	buf := &bytes.Buffer{}
	entry := zapcore.Entry{Time: time.Date(1975, time.August, 15, 12, 0, 0, 333, time.UTC)}
	f := fabenc.TimeFormatter{Layout: time.RFC3339Nano}
	f.Format(buf, entry, nil)
	require.Equal(t, "1975-08-15T12:00:00.000000333Z", buf.String())
}

func TestMultiFormatter(t *testing.T) {
	entry := zapcore.Entry{
		Message: "message",
		Level:   zapcore.InfoLevel,
	}
	fields := []zapcore.Field{
		zap.String("name", "value"),
	}

	var tests = []struct {
		desc     string
		initial  []fabenc.Formatter
		update   []fabenc.Formatter
		expected string
	}{
		{
			desc:     "no formatters",
			initial:  nil,
			update:   nil,
			expected: "",
		},
		{
			desc:     "initial formatters",
			initial:  []fabenc.Formatter{fabenc.StringFormatter{Value: "string1"}},
			update:   nil,
			expected: "string1",
		},
		{
			desc:    "set to formatters",
			initial: []fabenc.Formatter{fabenc.StringFormatter{Value: "string1"}},
			update: []fabenc.Formatter{
				fabenc.StringFormatter{Value: "string1"},
				fabenc.StringFormatter{Value: "-"},
				fabenc.StringFormatter{Value: "string2"},
			},
			expected: "string1-string2",
		},
		{
			desc:     "set to empty",
			initial:  []fabenc.Formatter{fabenc.StringFormatter{Value: "string1"}},
			update:   []fabenc.Formatter{},
			expected: "",
		},
	}

	for _, tc := range tests {
		mf := fabenc.NewMultiFormatter(tc.initial...)
		if tc.update != nil {
			mf.SetFormatters(tc.update)
		}

		buf := &bytes.Buffer{}
		mf.Format(buf, entry, fields)
		require.Equal(t, tc.expected, buf.String())
	}
}
