// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package logtest

import (
	"context"
	"errors"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/log"
)

func TestRecorderLogger(t *testing.T) {
	for _, tt := range []struct {
		name    string
		options []Option

		loggerName    string
		loggerOptions []log.LoggerOption

		want Recording
	}{
		{
			name: "default scope",
			want: Recording{
				Scope{}: nil,
			},
		},
		{
			name:       "configured scope",
			loggerName: "test",
			loggerOptions: []log.LoggerOption{
				log.WithInstrumentationVersion("logtest v42"),
				log.WithSchemaURL("https://example.com"),
				log.WithInstrumentationAttributes(attribute.String("foo", "bar")),
			},
			want: Recording{
				Scope{
					Name:       "test",
					Version:    "logtest v42",
					SchemaURL:  "https://example.com",
					Attributes: attribute.NewSet(attribute.String("foo", "bar")),
				}: nil,
			},
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			rec := NewRecorder(tt.options...)
			rec.Logger(tt.loggerName, tt.loggerOptions...)
			got := rec.Result()
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestRecorderLoggerCreatesNewStruct(t *testing.T) {
	r := &Recorder{}
	assert.NotEqual(t, r, r.Logger("test"))
}

func TestLoggerEnabled(t *testing.T) {
	for _, tt := range []struct {
		name          string
		options       []Option
		ctx           context.Context
		enabledParams log.EnabledParameters
		want          bool
	}{
		{
			name: "the default option enables every log entry",
			ctx:  t.Context(),
			want: true,
		},
		{
			name: "with everything disabled",
			options: []Option{
				WithEnabledFunc(func(context.Context, log.EnabledParameters) bool {
					return false
				}),
			},
			ctx:  t.Context(),
			want: false,
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			e := NewRecorder(tt.options...).Logger("test").Enabled(tt.ctx, tt.enabledParams)
			assert.Equal(t, tt.want, e)
		})
	}
}

func TestLoggerEnabledFnUnset(t *testing.T) {
	r := &logger{}
	assert.True(t, r.Enabled(t.Context(), log.EnabledParameters{}))
}

func TestRecorderLoggerEmitAndReset(t *testing.T) {
	rec := NewRecorder()
	ts := time.Now()
	errBoom := errors.New("boom")

	l := rec.Logger(t.Name())
	ctx := t.Context()
	r := log.Record{}
	r.SetTimestamp(ts)
	r.SetSeverity(log.SeverityInfo)
	r.SetBody(log.StringValue("Hello there"))
	r.SetErr(errBoom)
	r.AddAttributes(log.Int("n", 1))
	r.AddAttributes(log.String("foo", "bar"))
	l.Emit(ctx, r)

	l2 := rec.Logger(t.Name())
	r2 := log.Record{}
	r2.SetBody(log.StringValue("Logger with the same scope"))
	l2.Emit(ctx, r2)

	want := Recording{
		Scope{Name: t.Name()}: []Record{
			{
				Context:   ctx,
				Timestamp: ts,
				Severity:  log.SeverityInfo,
				Body:      log.StringValue("Hello there"),
				Error:     errBoom,
				Attributes: []log.KeyValue{
					log.Int("n", 1),
					log.String("foo", "bar"),
				},
			},
			{
				Context:    ctx,
				Body:       log.StringValue("Logger with the same scope"),
				Attributes: []log.KeyValue{},
			},
		},
	}
	got := rec.Result()
	assert.Equal(t, want, got)

	rec.Reset()

	want = Recording{
		Scope{Name: t.Name()}: nil,
	}
	got = rec.Result()
	assert.Equal(t, want, got)
}

func TestRecorderConcurrentSafe(t *testing.T) {
	const goRoutineN = 10

	var wg sync.WaitGroup
	wg.Add(goRoutineN)

	r := &Recorder{}

	for range goRoutineN {
		go func() {
			defer wg.Done()

			nr := r.Logger("test")
			nr.Enabled(t.Context(), log.EnabledParameters{})
			nr.Emit(t.Context(), log.Record{})

			r.Result()
			r.Reset()
		}()
	}

	wg.Wait()
}
