package log_test

import (
	"encoding"
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/go-kit/kit/log"
)

func TestValueBinding(t *testing.T) {
	t.Parallel()
	var output []interface{}

	logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
		output = keyvals
		return nil
	}))

	start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC)
	now := start
	mocktime := func() time.Time {
		now = now.Add(time.Second)
		return now
	}

	lc := log.With(logger, "ts", log.Timestamp(mocktime), "caller", log.DefaultCaller)

	lc.Log("foo", "bar")
	timestamp, ok := output[1].(time.Time)
	if !ok {
		t.Fatalf("want time.Time, have %T", output[1])
	}
	if want, have := start.Add(time.Second), timestamp; want != have {
		t.Errorf("output[1]: want %v, have %v", want, have)
	}
	if want, have := "value_test.go:31", fmt.Sprint(output[3]); want != have {
		t.Errorf("output[3]: want %s, have %s", want, have)
	}

	// A second attempt to confirm the bindings are truly dynamic.
	lc.Log("foo", "bar")
	timestamp, ok = output[1].(time.Time)
	if !ok {
		t.Fatalf("want time.Time, have %T", output[1])
	}
	if want, have := start.Add(2*time.Second), timestamp; want != have {
		t.Errorf("output[1]: want %v, have %v", want, have)
	}
	if want, have := "value_test.go:44", fmt.Sprint(output[3]); want != have {
		t.Errorf("output[3]: want %s, have %s", want, have)
	}
}

func TestValueBinding_loggingZeroKeyvals(t *testing.T) {
	t.Parallel()
	var output []interface{}

	logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
		output = keyvals
		return nil
	}))

	start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC)
	now := start
	mocktime := func() time.Time {
		now = now.Add(time.Second)
		return now
	}

	logger = log.With(logger, "ts", log.Timestamp(mocktime))

	logger.Log()
	timestamp, ok := output[1].(time.Time)
	if !ok {
		t.Fatalf("want time.Time, have %T", output[1])
	}
	if want, have := start.Add(time.Second), timestamp; want != have {
		t.Errorf("output[1]: want %v, have %v", want, have)
	}

	// A second attempt to confirm the bindings are truly dynamic.
	logger.Log()
	timestamp, ok = output[1].(time.Time)
	if !ok {
		t.Fatalf("want time.Time, have %T", output[1])
	}
	if want, have := start.Add(2*time.Second), timestamp; want != have {
		t.Errorf("output[1]: want %v, have %v", want, have)
	}
}

func TestTimestampFormat(t *testing.T) {
	t.Parallel()

	start := time.Date(2015, time.April, 25, 0, 0, 0, 0, time.UTC)
	now := start
	mocktime := func() time.Time {
		now = now.Add(time.Second)
		return now
	}

	tv := log.TimestampFormat(mocktime, time.RFC822)

	if want, have := now.Add(time.Second).Format(time.RFC822), fmt.Sprint(tv()); want != have {
		t.Errorf("wrong time format: want %v, have %v", want, have)
	}

	if want, have := now.Add(2*time.Second).Format(time.RFC822), fmt.Sprint(tv()); want != have {
		t.Errorf("wrong time format: want %v, have %v", want, have)
	}

	mustMarshal := func(v interface{}) []byte {
		b, err := v.(encoding.TextMarshaler).MarshalText()
		if err != nil {
			t.Fatal("error marshaling text:", err)
		}
		return b
	}

	if want, have := now.Add(3*time.Second).AppendFormat(nil, time.RFC822), mustMarshal(tv()); !reflect.DeepEqual(want, have) {
		t.Errorf("wrong time format: want %s, have %s", want, have)
	}

	if want, have := now.Add(4*time.Second).AppendFormat(nil, time.RFC822), mustMarshal(tv()); !reflect.DeepEqual(want, have) {
		t.Errorf("wrong time format: want %s, have %s", want, have)
	}
}

func BenchmarkValueBindingTimestamp(b *testing.B) {
	logger := log.NewNopLogger()
	lc := log.With(logger, "ts", log.DefaultTimestamp)
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		lc.Log("k", "v")
	}
}

func BenchmarkValueBindingCaller(b *testing.B) {
	logger := log.NewNopLogger()
	lc := log.With(logger, "caller", log.DefaultCaller)
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		lc.Log("k", "v")
	}
}
