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

package componentattribute_test

import (
	"context"
	"fmt"
	"slices"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.opentelemetry.io/otel/attribute"
	sdkTrace "go.opentelemetry.io/otel/sdk/trace"
	"go.opentelemetry.io/otel/sdk/trace/tracetest"
	"go.opentelemetry.io/otel/trace"
	"go.opentelemetry.io/otel/trace/embedded"

	"go.opentelemetry.io/collector/internal/telemetry/componentattribute"
)

// Emulate a TracerProvider from a non-official SDK
type customTracerProvider struct {
	embedded.TracerProvider
	tp *sdkTrace.TracerProvider
}

func (ctp customTracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
	return ctp.tp.Tracer(name, options...)
}

var _ trace.TracerProvider = customTracerProvider{}

func TestTPWA(t *testing.T) {
	exporter := tracetest.NewInMemoryExporter()
	tp1 := sdkTrace.NewTracerProvider(sdkTrace.WithSpanProcessor(sdkTrace.NewSimpleSpanProcessor(exporter)))

	// Add attribute set
	extraAttrs2 := attribute.NewSet(
		attribute.String("extrakey2", "extraval2"),
	)
	tp2 := componentattribute.TracerProviderWithAttributes(tp1, extraAttrs2)

	// Replace attribute set
	extraAttrs3 := attribute.NewSet(
		attribute.String("extrakey3", "extraval3"),
	)
	tp3 := componentattribute.TracerProviderWithAttributes(tp2, extraAttrs3)

	// The same thing, but with a non-official SDK Provider
	tp4 := &customTracerProvider{tp: tp1}
	tp5 := componentattribute.TracerProviderWithAttributes(tp4, extraAttrs2)
	tp6 := componentattribute.TracerProviderWithAttributes(tp5, extraAttrs3)

	noAttrs := attribute.NewSet()
	// Add a standard attribute on top of the extra attributes
	attrs4 := attribute.NewSet(
		attribute.String("stdkey", "stdval"),
	)
	expAttrs4 := attribute.NewSet(append(extraAttrs3.ToSlice(), attrs4.ToSlice()...)...)
	// Overwrite the extra attribute
	attrs5 := attribute.NewSet(
		attribute.String("extrakey3", "customval"),
	)

	tests := []struct {
		tp       trace.TracerProvider
		attrs    attribute.Set
		expAttrs attribute.Set
		name     string
	}{
		{tp: tp1, attrs: noAttrs, expAttrs: noAttrs, name: "no extra attributes"},
		{tp: tp2, attrs: noAttrs, expAttrs: extraAttrs2, name: "set extra attributes"},
		{tp: tp3, attrs: noAttrs, expAttrs: extraAttrs3, name: "reset extra attributes"},
		{tp: tp3, attrs: attrs4, expAttrs: expAttrs4, name: "merge attributes"},
		{tp: tp3, attrs: attrs5, expAttrs: attrs5, name: "overwrite extra attribute"},
		{tp: tp4, attrs: noAttrs, expAttrs: noAttrs, name: "no extra attributes, non-official SDK"},
		{tp: tp5, attrs: noAttrs, expAttrs: extraAttrs2, name: "set extra attributes, non-official SDK"},
		{tp: tp6, attrs: noAttrs, expAttrs: extraAttrs3, name: "reset extra attributes, non-official SDK"},
	}

	for i, test := range tests {
		tracerName := fmt.Sprintf("testtracer%d", i+1)
		_, span := test.tp.Tracer(
			tracerName,
			trace.WithInstrumentationAttributes(test.attrs.ToSlice()...),
		).Start(context.Background(), "testspan")
		span.End()
	}

	spans := exporter.GetSpans()
	require.LessOrEqual(t, len(spans), len(tests))

	for i, test := range tests {
		t.Run(test.name+"/check", func(t *testing.T) {
			tracerName := fmt.Sprintf("testtracer%d", i+1)
			i := slices.IndexFunc(spans, func(s tracetest.SpanStub) bool {
				return s.InstrumentationScope.Name == tracerName
			})
			assert.NotEqual(t, i, -1)
			span := spans[i]
			assert.Equal(t, "testspan", span.Name)
			assert.Equal(t, test.expAttrs, span.InstrumentationScope.Attributes)
		})
	}
}
