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

package builders

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/component/componenttest"
	"go.opentelemetry.io/collector/consumer"
	"go.opentelemetry.io/collector/consumer/consumertest"
	"go.opentelemetry.io/collector/receiver"
	"go.opentelemetry.io/collector/receiver/receivertest"
)

func TestReceiverBuilder(t *testing.T) {
	defaultCfg := struct{}{}
	factories, err := receiver.MakeFactoryMap([]receiver.Factory{
		receiver.NewFactory(component.MustNewType("err"), nil),
		receiver.NewFactory(
			component.MustNewType("all"),
			func() component.Config { return &defaultCfg },
			receiver.WithTraces(createReceiverTraces, component.StabilityLevelDevelopment),
			receiver.WithMetrics(createReceiverMetrics, component.StabilityLevelAlpha),
			receiver.WithLogs(createReceiverLogs, component.StabilityLevelDeprecated),
		),
	}...)
	require.NoError(t, err)

	testCases := []struct {
		name        string
		id          component.ID
		err         string
		nextTraces  consumer.Traces
		nextLogs    consumer.Logs
		nextMetrics consumer.Metrics
	}{
		{
			name:        "unknown",
			id:          component.MustNewID("unknown"),
			err:         "receiver factory not available for: \"unknown\"",
			nextTraces:  consumertest.NewNop(),
			nextLogs:    consumertest.NewNop(),
			nextMetrics: consumertest.NewNop(),
		},
		{
			name:        "err",
			id:          component.MustNewID("err"),
			err:         "telemetry type is not supported",
			nextTraces:  consumertest.NewNop(),
			nextLogs:    consumertest.NewNop(),
			nextMetrics: consumertest.NewNop(),
		},
		{
			name:        "all",
			id:          component.MustNewID("all"),
			nextTraces:  consumertest.NewNop(),
			nextLogs:    consumertest.NewNop(),
			nextMetrics: consumertest.NewNop(),
		},
		{
			name:        "all/named",
			id:          component.MustNewIDWithName("all", "named"),
			nextTraces:  consumertest.NewNop(),
			nextLogs:    consumertest.NewNop(),
			nextMetrics: consumertest.NewNop(),
		},
		{
			name:        "no next consumer",
			id:          component.MustNewID("unknown"),
			err:         "nil next Consumer",
			nextTraces:  nil,
			nextLogs:    nil,
			nextMetrics: nil,
		},
	}

	for _, tt := range testCases {
		t.Run(tt.name, func(t *testing.T) {
			cfgs := map[component.ID]component.Config{tt.id: defaultCfg}
			b := NewReceiver(cfgs, factories)

			te, err := b.CreateTraces(context.Background(), settings(tt.id), tt.nextTraces)
			if tt.err != "" {
				assert.EqualError(t, err, tt.err)
				assert.Nil(t, te)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, nopReceiverInstance, te)
			}

			me, err := b.CreateMetrics(context.Background(), settings(tt.id), tt.nextMetrics)
			if tt.err != "" {
				assert.EqualError(t, err, tt.err)
				assert.Nil(t, me)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, nopReceiverInstance, me)
			}

			le, err := b.CreateLogs(context.Background(), settings(tt.id), tt.nextLogs)
			if tt.err != "" {
				assert.EqualError(t, err, tt.err)
				assert.Nil(t, le)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, nopReceiverInstance, le)
			}
		})
	}
}

func TestReceiverBuilderMissingConfig(t *testing.T) {
	defaultCfg := struct{}{}
	factories, err := receiver.MakeFactoryMap([]receiver.Factory{
		receiver.NewFactory(
			component.MustNewType("all"),
			func() component.Config { return &defaultCfg },
			receiver.WithTraces(createReceiverTraces, component.StabilityLevelDevelopment),
			receiver.WithMetrics(createReceiverMetrics, component.StabilityLevelAlpha),
			receiver.WithLogs(createReceiverLogs, component.StabilityLevelDeprecated),
		),
	}...)

	require.NoError(t, err)

	bErr := NewReceiver(map[component.ID]component.Config{}, factories)
	missingID := component.MustNewIDWithName("all", "missing")

	te, err := bErr.CreateTraces(context.Background(), settings(missingID), consumertest.NewNop())
	assert.EqualError(t, err, "receiver \"all/missing\" is not configured")
	assert.Nil(t, te)

	me, err := bErr.CreateMetrics(context.Background(), settings(missingID), consumertest.NewNop())
	assert.EqualError(t, err, "receiver \"all/missing\" is not configured")
	assert.Nil(t, me)

	le, err := bErr.CreateLogs(context.Background(), settings(missingID), consumertest.NewNop())
	assert.EqualError(t, err, "receiver \"all/missing\" is not configured")
	assert.Nil(t, le)
}

func TestReceiverBuilderFactory(t *testing.T) {
	factories, err := receiver.MakeFactoryMap([]receiver.Factory{receiver.NewFactory(component.MustNewType("foo"), nil)}...)
	require.NoError(t, err)

	cfgs := map[component.ID]component.Config{component.MustNewID("foo"): struct{}{}}
	b := NewReceiver(cfgs, factories)

	assert.NotNil(t, b.Factory(component.MustNewID("foo").Type()))
	assert.Nil(t, b.Factory(component.MustNewID("bar").Type()))
}

func TestNewNopReceiverConfigsAndFactories(t *testing.T) {
	configs, factories := NewNopReceiverConfigsAndFactories()
	builder := NewReceiver(configs, factories)
	require.NotNil(t, builder)

	factory := receivertest.NewNopFactory()
	cfg := factory.CreateDefaultConfig()
	set := receivertest.NewNopSettings()
	set.ID = component.NewID(nopType)

	traces, err := factory.CreateTracesReceiver(context.Background(), set, cfg, consumertest.NewNop())
	require.NoError(t, err)
	bTraces, err := builder.CreateTraces(context.Background(), set, consumertest.NewNop())
	require.NoError(t, err)
	assert.IsType(t, traces, bTraces)

	metrics, err := factory.CreateMetricsReceiver(context.Background(), set, cfg, consumertest.NewNop())
	require.NoError(t, err)
	bMetrics, err := builder.CreateMetrics(context.Background(), set, consumertest.NewNop())
	require.NoError(t, err)
	assert.IsType(t, metrics, bMetrics)

	logs, err := factory.CreateLogsReceiver(context.Background(), set, cfg, consumertest.NewNop())
	require.NoError(t, err)
	bLogs, err := builder.CreateLogs(context.Background(), set, consumertest.NewNop())
	require.NoError(t, err)
	assert.IsType(t, logs, bLogs)
}

func settings(id component.ID) receiver.Settings {
	return receiver.Settings{
		ID:                id,
		TelemetrySettings: componenttest.NewNopTelemetrySettings(),
		BuildInfo:         component.NewDefaultBuildInfo(),
	}
}

var nopReceiverInstance = &nopReceiver{
	Consumer: consumertest.NewNop(),
}

// nopReceiver stores consumed traces and metrics for testing purposes.
type nopReceiver struct {
	component.StartFunc
	component.ShutdownFunc
	consumertest.Consumer
}

func createReceiverTraces(context.Context, receiver.Settings, component.Config, consumer.Traces) (receiver.Traces, error) {
	return nopReceiverInstance, nil
}

func createReceiverMetrics(context.Context, receiver.Settings, component.Config, consumer.Metrics) (receiver.Metrics, error) {
	return nopReceiverInstance, nil
}

func createReceiverLogs(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) {
	return nopReceiverInstance, nil
}
