// Copyright (c) 2017 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"testing"
	"time"

	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/uber/jaeger-lib/metrics"
	"github.com/uber/jaeger-lib/metrics/testutils"

	"github.com/uber/jaeger-client-go"
	"github.com/uber/jaeger-client-go/log"
)

func TestNewSamplerConst(t *testing.T) {
	constTests := []struct {
		param    float64
		decision bool
	}{{1, true}, {0, false}}
	for _, tst := range constTests {
		cfg := &SamplerConfig{Type: jaeger.SamplerTypeConst, Param: tst.param}
		s, err := cfg.NewSampler("x", nil)
		require.NoError(t, err)
		s1, ok := s.(*jaeger.ConstSampler)
		require.True(t, ok, "converted to constSampler")
		require.Equal(t, tst.decision, s1.Decision, "decision")
	}
}

func TestNewSamplerProbabilistic(t *testing.T) {
	constTests := []struct {
		param float64
		error bool
	}{{1.5, true}, {0.5, false}}
	for _, tst := range constTests {
		cfg := &SamplerConfig{Type: jaeger.SamplerTypeProbabilistic, Param: tst.param}
		s, err := cfg.NewSampler("x", nil)
		if tst.error {
			require.Error(t, err)
		} else {
			require.NoError(t, err)
			_, ok := s.(*jaeger.ProbabilisticSampler)
			require.True(t, ok, "converted to ProbabilisticSampler")
		}
	}
}

func TestDefaultSampler(t *testing.T) {
	cfg := Configuration{
		Sampler: &SamplerConfig{Type: "InvalidType"},
	}
	_, _, err := cfg.New("testService")
	require.Error(t, err)
}

func TestInvalidSamplerType(t *testing.T) {
	cfg := &SamplerConfig{MaxOperations: 10}
	s, err := cfg.NewSampler("x", jaeger.NewNullMetrics())
	require.NoError(t, err)
	rcs, ok := s.(*jaeger.RemotelyControlledSampler)
	require.True(t, ok, "converted to RemotelyControlledSampler")
	rcs.Close()
}

func TestDefaultConfig(t *testing.T) {
	cfg := Configuration{}
	_, _, err := cfg.New("", Metrics(metrics.NullFactory), Logger(log.NullLogger))
	require.EqualError(t, err, "no service name provided")

	_, closer, err := cfg.New("testService")
	defer closer.Close()
	require.NoError(t, err)
}

func TestDisabledFlag(t *testing.T) {
	cfg := Configuration{Disabled: true}
	_, closer, err := cfg.New("testService")
	defer closer.Close()
	require.NoError(t, err)
}

func TestNewReporterError(t *testing.T) {
	cfg := Configuration{
		Reporter: &ReporterConfig{LocalAgentHostPort: "bad_local_agent"},
	}
	_, _, err := cfg.New("testService")
	require.Error(t, err)
}

func TestInitGlobalTracer(t *testing.T) {
	// Save the existing GlobalTracer and replace after finishing function
	prevTracer := opentracing.GlobalTracer()
	defer opentracing.SetGlobalTracer(prevTracer)
	noopTracer := opentracing.NoopTracer{}

	tests := []struct {
		cfg           Configuration
		shouldErr     bool
		tracerChanged bool
	}{
		{
			cfg:           Configuration{Disabled: true},
			shouldErr:     false,
			tracerChanged: false,
		},
		{
			cfg:           Configuration{Sampler: &SamplerConfig{Type: "InvalidType"}},
			shouldErr:     true,
			tracerChanged: false,
		},
		{
			cfg: Configuration{
				Sampler: &SamplerConfig{
					Type: "remote",
					SamplingRefreshInterval: 1,
				},
			},
			shouldErr:     false,
			tracerChanged: true,
		},
		{
			cfg:           Configuration{},
			shouldErr:     false,
			tracerChanged: true,
		},
	}
	for _, test := range tests {
		opentracing.SetGlobalTracer(noopTracer)
		_, err := test.cfg.InitGlobalTracer("testService")
		if test.shouldErr {
			require.Error(t, err)
		} else {
			require.NoError(t, err)
		}
		if test.tracerChanged {
			require.NotEqual(t, noopTracer, opentracing.GlobalTracer())
		} else {
			require.Equal(t, noopTracer, opentracing.GlobalTracer())
		}
	}
}

func TestConfigWithReporter(t *testing.T) {
	c := Configuration{
		Sampler: &SamplerConfig{
			Type:  "const",
			Param: 1,
		},
	}
	r := jaeger.NewInMemoryReporter()
	tracer, closer, err := c.New("test", Reporter(r))
	require.NoError(t, err)
	defer closer.Close()

	tracer.StartSpan("test").Finish()
	assert.Len(t, r.GetSpans(), 1)
}

func TestConfigWithRPCMetrics(t *testing.T) {
	metrics := metrics.NewLocalFactory(0)
	c := Configuration{
		Sampler: &SamplerConfig{
			Type:  "const",
			Param: 1,
		},
		RPCMetrics: true,
	}
	r := jaeger.NewInMemoryReporter()
	tracer, closer, err := c.New(
		"test",
		Reporter(r),
		Metrics(metrics),
		ContribObserver(fakeContribObserver{}),
	)
	require.NoError(t, err)
	defer closer.Close()

	tracer.StartSpan("test", ext.SpanKindRPCServer).Finish()

	testutils.AssertCounterMetrics(t, metrics,
		testutils.ExpectedMetric{
			Name:  "jaeger-rpc.requests",
			Tags:  map[string]string{"component": "jaeger", "endpoint": "test", "error": "false"},
			Value: 1,
		},
	)
}

func TestBaggageRestrictionsConfig(t *testing.T) {
	m := metrics.NewLocalFactory(0)
	c := Configuration{
		BaggageRestrictions: &BaggageRestrictionsConfig{
			HostPort:        "not:1929213",
			RefreshInterval: time.Minute,
		},
	}
	_, closer, err := c.New(
		"test",
		Metrics(m),
	)
	require.NoError(t, err)
	defer closer.Close()

	metricName := "jaeger.baggage_restrictions_updates"
	metricTags := map[string]string{"result": "err"}
	key := metrics.GetKey(metricName, metricTags, "|", "=")
	for i := 0; i < 100; i++ {
		// wait until the async initialization call is complete
		counters, _ := m.Snapshot()
		if _, ok := counters[key]; ok {
			break
		}
		time.Sleep(time.Millisecond)
	}

	testutils.AssertCounterMetrics(t, m,
		testutils.ExpectedMetric{
			Name:  metricName,
			Tags:  metricTags,
			Value: 1,
		},
	)
}

func TestConfigWithGen128Bit(t *testing.T) {
	c := Configuration{
		Sampler: &SamplerConfig{
			Type:  "const",
			Param: 1,
		},
		RPCMetrics: true,
	}
	tracer, closer, err := c.New("test", Gen128Bit(true))
	require.NoError(t, err)
	defer closer.Close()

	span := tracer.StartSpan("test")
	defer span.Finish()
	traceID := span.Context().(jaeger.SpanContext).TraceID()
	require.True(t, traceID.High != 0)
	require.True(t, traceID.Low != 0)
}
