package zapx

import (
	"bytes"
	"testing"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var (
	eventTime = time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)
	caller    = func(zapcore.EntryCaller) string { return "" }
)

func TestEncodeNestedObject(t *testing.T) {
	entry := zapcore.Entry{
		Level:      zapcore.InfoLevel,
		Time:       eventTime,
		LoggerName: "",
		Message:    "msg",
		Caller:     zapcore.EntryCaller{},
		Stack:      "",
	}
	fields := []zapcore.Field{
		zap.Object("a", zapcore.ObjectMarshalerFunc(func(oe zapcore.ObjectEncoder) error {
			oe.AddString("a1", "v1")
			oe.AddArray("b", zapcore.ArrayMarshalerFunc(func(ae zapcore.ArrayEncoder) error {
				ae.AppendString("b1")
				ae.AppendObject(zapcore.ObjectMarshalerFunc(func(oe zapcore.ObjectEncoder) error {
					oe.AddString("c1", "v2")
					return nil
				}))
				return nil
			}))
			return nil
		})),
	}

	encoder := NewEncoder("category", caller)
	buf, err := encoder.EncodeEntry(entry, fields)
	if err != nil {
		t.Fatal(err)
	}
	s := buf.String()
	want := `{"eventTime":"2006-01-02T15:04:05Z","category":"category","level":"info","caller":"","message":"msg","stringEntries":[{"key":"a.a1","value":"v1"},{"key":"a.b[0]","value":"b1"},{"key":"a.b[1].c1","value":"v2"}],"floatEntries":[],"intEntries":[],"boolEntries":[]}`
	if s != want {
		t.Errorf("log mismatch\n got %s\nwant %s", s, want)
	}
}

func TestEncodeInt(t *testing.T) {
	entry := zapcore.Entry{
		Level:      zapcore.InfoLevel,
		Time:       eventTime,
		LoggerName: "",
		Message:    "msg",
		Caller:     zapcore.EntryCaller{},
		Stack:      "",
	}
	fields := []zapcore.Field{
		zap.Int64("a1", 1),
		zap.Int64("b1", 2147483648),       // 1 << 33 = max int32 + 1
		zap.Int64("c1", 9007199254740993), // 1<<53+1
		zap.Uint64("a2", 1),
		zap.Uint64("b2", 2147483648),       // 1 << 33 = max int32 + 1
		zap.Uint64("c2", 9007199254740993), // 1<<53+1
	}

	encoder := NewEncoder("category", caller)
	buf, err := encoder.EncodeEntry(entry, fields)
	if err != nil {
		t.Fatal(err)
	}
	s := buf.String()
	want := `{"eventTime":"2006-01-02T15:04:05Z","category":"category","level":"info","caller":"","message":"msg","stringEntries":[{"key":"c1","value":"9007199254740993"},{"key":"c2","value":"9007199254740993"}],"floatEntries":[{"key":"b1","value":2147483648},{"key":"b2","value":2147483648}],"intEntries":[{"key":"a1","value":1},{"key":"a2","value":1}],"boolEntries":[]}`
	if s != want {
		t.Errorf("log mismatch\n got %s\nwant %s", s, want)
	}
}

func TestWith(t *testing.T) {
	var buf bytes.Buffer
	var clock mockClock
	encoder := NewEncoder("category", caller)

	core := zapcore.NewCore(encoder, zapcore.AddSync(&buf), zapcore.InfoLevel)

	logger := zap.New(core, zap.WithClock(clock))
	logger = logger.With(zap.String("a", "b"))
	logger.Info("hello", zap.String("name", "world"))

	want := `{"eventTime":"2006-01-02T15:04:05Z","category":"category","level":"info","caller":"","message":"hello","stringEntries":[{"key":"a","value":"b"},{"key":"name","value":"world"}],"floatEntries":[],"intEntries":[],"boolEntries":[]}`
	s := buf.String()
	if s != want {
		t.Errorf("log mismatch\n got %s\nwant %s", s, want)
	}
}

func TestNameSpace(t *testing.T) {
	var buf bytes.Buffer
	var clock mockClock
	encoder := NewEncoder("category", caller)

	core := zapcore.NewCore(encoder, zapcore.AddSync(&buf), zapcore.InfoLevel)

	logger := zap.New(core, zap.WithClock(clock))
	logger = logger.With(zap.Namespace("a"), zap.String("b", "c"))
	logger.Info("msg", zap.String("d", "e"))

	want := `{"eventTime":"2006-01-02T15:04:05Z","category":"category","level":"info","caller":"","message":"msg","stringEntries":[{"key":"a.b","value":"c"},{"key":"a.d","value":"e"}],"floatEntries":[],"intEntries":[],"boolEntries":[]}`
	s := buf.String()
	if s != want {
		t.Errorf("log mismatch\n got %s\nwant %s", s, want)
	}
}

type mockClock struct{}

func (c mockClock) Now() time.Time                       { return eventTime }
func (c mockClock) NewTicker(time.Duration) *time.Ticker { panic("not implemented") }
