// 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 zipkin

import (
	"testing"
	"time"

	zipkinmodel "github.com/openzipkin/zipkin-go/model"
	"github.com/stretchr/testify/assert"

	"go.opentelemetry.io/collector/consumer/pdata"
	"go.opentelemetry.io/collector/translator/conventions"
)

func TestZipkinSpansToInternalTraces(t *testing.T) {
	tests := []struct {
		name string
		zs   []*zipkinmodel.SpanModel
		td   pdata.Traces
		err  error
	}{
		{
			name: "empty",
			zs:   make([]*zipkinmodel.SpanModel, 0),
			td:   pdata.NewTraces(),
			err:  nil,
		},
		{
			name: "minimalSpan",
			zs:   generateSpanNoEndpoints(),
			td:   generateTraceSingleSpanNoResourceOrInstrLibrary(),
			err:  nil,
		},
		{
			name: "onlyLocalEndpointSpan",
			zs:   generateSpanNoTags(),
			td:   generateTraceSingleSpanMinmalResource(),
			err:  nil,
		},
		{
			name: "errorTag",
			zs:   generateSpanErrorTags(),
			td:   generateTraceSingleSpanErrorStatus(),
			err:  nil,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			td, err := V2SpansToInternalTraces(test.zs, false)
			assert.EqualValues(t, test.err, err)
			if test.name != "nilSpan" {
				assert.Equal(t, len(test.zs), td.SpanCount())
			}
			assert.EqualValues(t, test.td, td)
		})
	}
}

func generateSpanNoEndpoints() []*zipkinmodel.SpanModel {
	spans := make([]*zipkinmodel.SpanModel, 1)
	spans[0] = &zipkinmodel.SpanModel{
		SpanContext: zipkinmodel.SpanContext{
			TraceID: convertTraceID(
				pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})),
			ID: convertSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})),
		},
		Name:           "MinimalData",
		Kind:           zipkinmodel.Client,
		Timestamp:      time.Unix(1596911098, 294000000),
		Duration:       1000000,
		Shared:         false,
		LocalEndpoint:  nil,
		RemoteEndpoint: nil,
		Annotations:    nil,
		Tags:           nil,
	}
	return spans
}

func generateSpanNoTags() []*zipkinmodel.SpanModel {
	spans := generateSpanNoEndpoints()
	spans[0].LocalEndpoint = &zipkinmodel.Endpoint{ServiceName: "SoleAttr"}
	return spans
}

func generateSpanErrorTags() []*zipkinmodel.SpanModel {
	errorTags := make(map[string]string)
	errorTags["error"] = "true"

	spans := generateSpanNoEndpoints()
	spans[0].Tags = errorTags
	return spans
}

func generateTraceSingleSpanNoResourceOrInstrLibrary() pdata.Traces {
	td := pdata.NewTraces()
	span := td.ResourceSpans().AppendEmpty().InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty()
	span.SetTraceID(
		pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}))
	span.SetSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8}))
	span.SetName("MinimalData")
	span.SetKind(pdata.SpanKindClient)
	span.SetStartTimestamp(1596911098294000000)
	span.SetEndTimestamp(1596911098295000000)
	return td
}

func generateTraceSingleSpanMinmalResource() pdata.Traces {
	td := generateTraceSingleSpanNoResourceOrInstrLibrary()
	rs := td.ResourceSpans().At(0)
	rsc := rs.Resource()
	rsc.Attributes().UpsertString(conventions.AttributeServiceName, "SoleAttr")
	return td
}

func generateTraceSingleSpanErrorStatus() pdata.Traces {
	td := pdata.NewTraces()
	span := td.ResourceSpans().AppendEmpty().InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty()
	span.SetTraceID(
		pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80}))
	span.SetSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8}))
	span.SetName("MinimalData")
	span.SetKind(pdata.SpanKindClient)
	span.SetStartTimestamp(1596911098294000000)
	span.SetEndTimestamp(1596911098295000000)
	span.Status().SetCode(pdata.StatusCodeError)
	return td
}

func TestV2SpanWithoutTimestampGetsTag(t *testing.T) {
	duration := int64(2948533333)
	spans := make([]*zipkinmodel.SpanModel, 1)
	spans[0] = &zipkinmodel.SpanModel{
		SpanContext: zipkinmodel.SpanContext{
			TraceID: convertTraceID(
				pdata.NewTraceID([16]byte{0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x80})),
			ID: convertSpanID(pdata.NewSpanID([8]byte{0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8})),
		},
		Name:           "NoTimestamps",
		Kind:           zipkinmodel.Client,
		Duration:       time.Duration(duration),
		Shared:         false,
		LocalEndpoint:  nil,
		RemoteEndpoint: nil,
		Annotations:    nil,
		Tags:           nil,
	}

	gb, err := V2SpansToInternalTraces(spans, false)
	if err != nil {
		t.Errorf("Unexpected error: %v", err)
		return
	}

	gs := gb.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0)
	assert.NotNil(t, gs.StartTimestamp)
	assert.NotNil(t, gs.EndTimestamp)

	// expect starttime to be set to zero (unix time)
	unixTime := gs.StartTimestamp().AsTime().Unix()
	assert.Equal(t, int64(0), unixTime)

	// expect end time to be zero (unix time) plus the duration
	assert.Equal(t, duration, gs.EndTimestamp().AsTime().UnixNano())

	wasAbsent, mapContainedKey := gs.Attributes().Get(startTimeAbsent)
	assert.True(t, mapContainedKey)
	assert.True(t, wasAbsent.BoolVal())
}
