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

package plog

import (
	"testing"

	jsoniter "github.com/json-iterator/go"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	otlplogs "go.opentelemetry.io/collector/pdata/internal/data/protogen/logs/v1"
	"go.opentelemetry.io/collector/pdata/pcommon"
)

var (
	_ Marshaler   = (*JSONMarshaler)(nil)
	_ Unmarshaler = (*JSONUnmarshaler)(nil)
)

var logsOTLP = func() Logs {
	ld := NewLogs()
	rl := ld.ResourceLogs().AppendEmpty()
	rl.Resource().Attributes().PutStr("host.name", "testHost")
	rl.Resource().SetDroppedAttributesCount(1)
	rl.SetSchemaUrl("resource_schema")
	il := rl.ScopeLogs().AppendEmpty()
	il.Scope().SetName("name")
	il.Scope().SetVersion("version")
	il.Scope().SetDroppedAttributesCount(1)
	il.SetSchemaUrl("scope_schema")
	lg := il.LogRecords().AppendEmpty()
	lg.SetSeverityNumber(SeverityNumber(otlplogs.SeverityNumber_SEVERITY_NUMBER_ERROR))
	lg.SetSeverityText("Error")
	lg.SetDroppedAttributesCount(1)
	lg.SetFlags(LogRecordFlags(otlplogs.LogRecordFlags_LOG_RECORD_FLAGS_DO_NOT_USE))
	traceID := pcommon.TraceID([16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10})
	spanID := pcommon.SpanID([8]byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18})
	lg.SetTraceID(traceID)
	lg.SetSpanID(spanID)
	lg.Body().SetStr("hello world")
	lg.SetTimestamp(pcommon.Timestamp(1684617382541971000))
	lg.SetObservedTimestamp(pcommon.Timestamp(1684623646539558000))
	lg.Attributes().PutStr("sdkVersion", "1.0.1")
	lg.SetFlags(DefaultLogRecordFlags.WithIsSampled(true))
	return ld
}()

func TestLogsJSON(t *testing.T) {
	encoder := &JSONMarshaler{}
	jsonBuf, err := encoder.MarshalLogs(logsOTLP)
	require.NoError(t, err)
	decoder := &JSONUnmarshaler{}
	got, err := decoder.UnmarshalLogs(jsonBuf)
	require.NoError(t, err)
	assert.Equal(t, logsOTLP, got)
}

var logsJSON = `{"resourceLogs":[{"resource":{"attributes":[{"key":"host.name","value":{"stringValue":"testHost"}}],"droppedAttributesCount":1},"scopeLogs":[{"scope":{"name":"name","version":"version","droppedAttributesCount":1},"logRecords":[{"timeUnixNano":"1684617382541971000","observedTimeUnixNano":"1684623646539558000","severityNumber":17,"severityText":"Error","body":{"stringValue":"hello world"},"attributes":[{"key":"sdkVersion","value":{"stringValue":"1.0.1"}}],"droppedAttributesCount":1,"flags":1,"traceId":"0102030405060708090a0b0c0d0e0f10","spanId":"1112131415161718"}],"schemaUrl":"scope_schema"}],"schemaUrl":"resource_schema"}]}`

func TestJSONUnmarshal(t *testing.T) {
	decoder := &JSONUnmarshaler{}
	got, err := decoder.UnmarshalLogs([]byte(logsJSON))
	require.NoError(t, err)
	assert.Equal(t, logsOTLP, got)
}

func TestJSONMarshal(t *testing.T) {
	encoder := &JSONMarshaler{}
	jsonBuf, err := encoder.MarshalLogs(logsOTLP)
	require.NoError(t, err)
	assert.JSONEq(t, logsJSON, string(jsonBuf))
}

func TestJSONUnmarshalInvalid(t *testing.T) {
	jsonStr := `{"extra":"", "resourceLogs": "extra"}`
	decoder := &JSONUnmarshaler{}
	_, err := decoder.UnmarshalLogs([]byte(jsonStr))
	assert.Error(t, err)
}

func TestUnmarshalJsoniterLogsData(t *testing.T) {
	jsonStr := `{"extra":"", "resourceLogs": []}`
	iter := jsoniter.ConfigFastest.BorrowIterator([]byte(jsonStr))
	defer jsoniter.ConfigFastest.ReturnIterator(iter)
	val := NewLogs()
	val.unmarshalJsoniter(iter)
	require.NoError(t, iter.Error)
	assert.Equal(t, NewLogs(), val)
}

func TestUnmarshalJsoniterResourceLogs(t *testing.T) {
	jsonStr := `{"extra":"", "resource": {}, "scopeLogs": []}`
	iter := jsoniter.ConfigFastest.BorrowIterator([]byte(jsonStr))
	defer jsoniter.ConfigFastest.ReturnIterator(iter)
	val := NewResourceLogs()
	val.unmarshalJsoniter(iter)
	require.NoError(t, iter.Error)
	assert.Equal(t, NewResourceLogs(), val)
}

func TestUnmarshalJsoniterScopeLogs(t *testing.T) {
	jsonStr := `{"extra":"", "scope": {}, "logRecords": []}`
	iter := jsoniter.ConfigFastest.BorrowIterator([]byte(jsonStr))
	defer jsoniter.ConfigFastest.ReturnIterator(iter)
	val := NewScopeLogs()
	val.unmarshalJsoniter(iter)
	require.NoError(t, iter.Error)
	assert.Equal(t, NewScopeLogs(), val)
}

func TestUnmarshalJsoniterLogRecord(t *testing.T) {
	jsonStr := `{"extra":"", "body":{}}`
	iter := jsoniter.ConfigFastest.BorrowIterator([]byte(jsonStr))
	defer jsoniter.ConfigFastest.ReturnIterator(iter)
	val := NewLogRecord()
	val.unmarshalJsoniter(iter)
	require.NoError(t, iter.Error)
	assert.Equal(t, NewLogRecord(), val)
}

func TestUnmarshalJsoniterLogWrongTraceID(t *testing.T) {
	jsonStr := `{"body":{}, "traceId":"--"}`
	iter := jsoniter.ConfigFastest.BorrowIterator([]byte(jsonStr))
	defer jsoniter.ConfigFastest.ReturnIterator(iter)
	NewLogRecord().unmarshalJsoniter(iter)
	require.ErrorContains(t, iter.Error, "parse trace_id")
}

func TestUnmarshalJsoniterLogWrongSpanID(t *testing.T) {
	jsonStr := `{"body":{}, "spanId":"--"}`
	iter := jsoniter.ConfigFastest.BorrowIterator([]byte(jsonStr))
	defer jsoniter.ConfigFastest.ReturnIterator(iter)
	NewLogRecord().unmarshalJsoniter(iter)
	require.ErrorContains(t, iter.Error, "parse span_id")
}

func BenchmarkJSONUnmarshal(b *testing.B) {
	b.ReportAllocs()

	encoder := &JSONMarshaler{}
	jsonBuf, err := encoder.MarshalLogs(logsOTLP)
	require.NoError(b, err)
	decoder := &JSONUnmarshaler{}

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			_, err := decoder.UnmarshalLogs(jsonBuf)
			assert.NoError(b, err)
		}
	})
}
