// Copyright The OpenTelemetry Authors
//
// 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 trace

import (
	"testing"

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

var (
	traceID    = ID{0, 0, 0, 0, 0, 0, 0, 0x7b, 0, 0, 0, 0, 0, 0, 0x1, 0xc8}
	traceIDStr = "000000000000007b00000000000001c8"
	spanID     = SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b}
	spanIDStr  = "000000000000007b"
)

func TestExtractMultiple(t *testing.T) {
	tests := []struct {
		traceID      string
		spanID       string
		parentSpanID string
		sampled      string
		flags        string
		expected     SpanContext
		err          error
	}{
		{
			"", "", "", "0", "",
			SpanContext{},
			nil,
		},
		{
			"", "", "", "", "",
			SpanContext{TraceFlags: FlagsDeferred},
			nil,
		},
		{
			"", "", "", "1", "",
			SpanContext{TraceFlags: FlagsSampled},
			nil,
		},
		{
			"", "", "", "", "1",
			SpanContext{TraceFlags: FlagsDeferred | FlagsDebug},
			nil,
		},
		{
			"", "", "", "0", "1",
			SpanContext{TraceFlags: FlagsDebug},
			nil,
		},
		{
			"", "", "", "1", "1",
			SpanContext{TraceFlags: FlagsSampled | FlagsDebug},
			nil,
		},
		{
			traceIDStr, spanIDStr, "", "", "",
			SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: FlagsDeferred},
			nil,
		},
		{
			traceIDStr, spanIDStr, "", "0", "",
			SpanContext{TraceID: traceID, SpanID: spanID},
			nil,
		},
		// Ensure backwards compatibility.
		{
			traceIDStr, spanIDStr, "", "false", "",
			SpanContext{TraceID: traceID, SpanID: spanID},
			nil,
		},
		{
			traceIDStr, spanIDStr, "", "1", "",
			SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: FlagsSampled},
			nil,
		},
		// Ensure backwards compatibility.
		{
			traceIDStr, spanIDStr, "", "true", "",
			SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: FlagsSampled},
			nil,
		},
		{
			traceIDStr, spanIDStr, "", "a", "",
			empty,
			errInvalidSampledHeader,
		},
		{
			traceIDStr, spanIDStr, "", "1", "1",
			SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: FlagsSampled | FlagsDebug},
			nil,
		},
		// Invalid flags are discarded.
		{
			traceIDStr, spanIDStr, "", "1", "invalid",
			SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: FlagsSampled},
			nil,
		},
		// Support short trace IDs.
		{
			"00000000000001c8", spanIDStr, "", "0", "",
			SpanContext{
				TraceID: ID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0xc8},
				SpanID:  spanID,
			},
			nil,
		},
		{
			"00000000000001c", spanIDStr, "", "0", "",
			empty,
			errInvalidTraceIDHeader,
		},
		{
			"00000000000001c80", spanIDStr, "", "0", "",
			empty,
			errInvalidTraceIDHeader,
		},
		{
			traceIDStr[:len(traceIDStr)-2], spanIDStr, "", "0", "",
			empty,
			errInvalidTraceIDHeader,
		},
		{
			traceIDStr + "0", spanIDStr, "", "0", "",
			empty,
			errInvalidTraceIDHeader,
		},
		{
			traceIDStr, "00000000000001c", "", "0", "",
			empty,
			errInvalidSpanIDHeader,
		},
		{
			traceIDStr, "00000000000001c80", "", "0", "",
			empty,
			errInvalidSpanIDHeader,
		},
		{
			traceIDStr, "", "", "0", "",
			empty,
			errInvalidScope,
		},
		{
			"", spanIDStr, "", "0", "",
			empty,
			errInvalidScope,
		},
		{
			"", "", spanIDStr, "0", "",
			empty,
			errInvalidScopeParent,
		},
		{
			traceIDStr, spanIDStr, "00000000000001c8", "0", "",
			SpanContext{TraceID: traceID, SpanID: spanID},
			nil,
		},
		{
			traceIDStr, spanIDStr, "00000000000001c", "0", "",
			empty,
			errInvalidParentSpanIDHeader,
		},
		{
			traceIDStr, spanIDStr, "00000000000001c80", "0", "",
			empty,
			errInvalidParentSpanIDHeader,
		},
	}

	for _, test := range tests {
		actual, err := extractMultiple(
			test.traceID,
			test.spanID,
			test.parentSpanID,
			test.sampled,
			test.flags,
		)
		info := []interface{}{
			"trace ID: %q, span ID: %q, parent span ID: %q, sampled: %q, flags: %q",
			test.traceID,
			test.spanID,
			test.parentSpanID,
			test.sampled,
			test.flags,
		}
		if !assert.Equal(t, test.err, err, info...) {
			continue
		}
		assert.Equal(t, test.expected, actual, info...)
	}
}

func TestExtractSingle(t *testing.T) {
	tests := []struct {
		header   string
		expected SpanContext
		err      error
	}{
		{"0", SpanContext{}, nil},
		{"1", SpanContext{TraceFlags: FlagsSampled}, nil},
		{"d", SpanContext{TraceFlags: FlagsDebug}, nil},
		{"a", empty, errInvalidSampledByte},
		{"3", empty, errInvalidSampledByte},
		{"000000000000007b", empty, errInvalidScope},
		{"000000000000007b00000000000001c8", empty, errInvalidScope},
		// Support short trace IDs.
		{
			"00000000000001c8-000000000000007b",
			SpanContext{
				TraceID:    ID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0xc8},
				SpanID:     spanID,
				TraceFlags: FlagsDeferred,
			},
			nil,
		},
		{
			"000000000000007b00000000000001c8-000000000000007b",
			SpanContext{
				TraceID:    traceID,
				SpanID:     spanID,
				TraceFlags: FlagsDeferred,
			},
			nil,
		},
		{
			"000000000000007b00000000000001c8-000000000000007b-",
			empty,
			errInvalidSampledByte,
		},
		{
			"000000000000007b00000000000001c8-000000000000007b-3",
			empty,
			errInvalidSampledByte,
		},
		{
			"000000000000007b00000000000001c8-000000000000007b-00000000000001c8",
			empty,
			errInvalidScopeParentSingle,
		},
		{
			"000000000000007b00000000000001c8-000000000000007b-1",
			SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: FlagsSampled},
			nil,
		},
		// ParentSpanID is discarded, but should still restult in a parsable
		// header.
		{
			"000000000000007b00000000000001c8-000000000000007b-1-00000000000001c8",
			SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: FlagsSampled},
			nil,
		},
		{
			"000000000000007b00000000000001c8-000000000000007b-1-00000000000001c",
			empty,
			errInvalidParentSpanIDValue,
		},
		{"", empty, errEmptyContext},
	}

	for _, test := range tests {
		actual, err := extractSingle(test.header)
		if !assert.Equal(t, test.err, err, "header: %s", test.header) {
			continue
		}
		assert.Equal(t, test.expected, actual, "header: %s", test.header)
	}
}

func TestB3EncodingOperations(t *testing.T) {
	encodings := []B3Encoding{
		B3MultipleHeader,
		B3SingleHeader,
		B3Unspecified,
	}

	// Test for overflow (or something really unexpected).
	for i, e := range encodings {
		for j := i + 1; j < i+len(encodings); j++ {
			o := encodings[j%len(encodings)]
			assert.False(t, e == o, "%v == %v", e, o)
		}
	}

	// B3Unspecified is a special case, it supports only itself, but is
	// supported by everything.
	assert.True(t, B3Unspecified.supports(B3Unspecified))
	for _, e := range encodings[:len(encodings)-1] {
		assert.False(t, B3Unspecified.supports(e), e)
		assert.True(t, e.supports(B3Unspecified), e)
	}

	// Skip the special case for B3Unspecified.
	for i, e := range encodings[:len(encodings)-1] {
		// Everything should support itself.
		assert.True(t, e.supports(e))
		for j := i + 1; j < i+len(encodings); j++ {
			o := encodings[j%len(encodings)]
			// Any "or" combination should be supportive of an operand.
			assert.True(t, (e | o).supports(e), "(%[0]v|%[1]v).supports(%[0]v)", e, o)
			// Bitmasks should be unique.
			assert.False(t, o.supports(e), "%v.supports(%v)", o, e)
		}
	}

	// B3Encoding.supports should be more inclusive than equality.
	all := ^B3Unspecified
	for _, e := range encodings {
		assert.True(t, all.supports(e))
	}
}