package utils

import (
	"testing"

	"github.com/grafana/grafana-plugin-sdk-go/experimental"
	"github.com/grafana/grafana/pkg/tsdb/jaeger/types"
	"github.com/stretchr/testify/assert"
)

func TestTransformGrpcSearchResponse(t *testing.T) {
	t.Run("empty_response", func(t *testing.T) {
		frame := TransformGrpcSearchResponse(types.GrpcTracesResult{}, "test-uid", "test-name", 0)
		experimental.CheckGoldenJSONFrame(t, "../testdata", "search_empty_response.golden", frame, false)
	})

	t.Run("single_trace", func(t *testing.T) {
		response := types.GrpcTracesResult{
			ResourceSpans: []types.GrpcResourceSpans{
				{
					Resource: types.GrpcResource{
						Attributes: []types.GrpcKeyValue{
							{
								Key: "service.name",
								Value: types.GrpcAnyValue{
									StringValue: "test-service",
								},
							},
						},
					},
					ScopeSpans: []types.GrpcScopeSpans{
						{
							Spans: []types.GrpcSpan{
								{
									TraceID:           "test-trace-id",
									Name:              "test-operation",
									StartTimeUnixNano: "1605873894680409000",
									EndTimeUnixNano:   "1605873894681409000",
								},
							},
						},
					},
					SchemaURL: "someschemaurl.com",
				},
			},
		}

		frame := TransformGrpcSearchResponse(response, "test-uid", "test-name", 0)
		experimental.CheckGoldenJSONFrame(t, "../testdata", "search_single_response.golden", frame, false)
	})

	t.Run("multiple_traces", func(t *testing.T) {
		response := types.GrpcTracesResult{
			ResourceSpans: []types.GrpcResourceSpans{
				{
					Resource: types.GrpcResource{
						Attributes: []types.GrpcKeyValue{
							{
								Key: "service.name",
								Value: types.GrpcAnyValue{
									StringValue: "service-1",
								},
							},
						},
					},
					ScopeSpans: []types.GrpcScopeSpans{
						{
							Spans: []types.GrpcSpan{
								{
									TraceID:           "trace-1",
									Name:              "op1",
									StartTimeUnixNano: "1605873894680409000",
									EndTimeUnixNano:   "1605873894681409000",
								},
							},
						},
					},
					SchemaURL: "someschemaurl.com",
				},
				{
					Resource: types.GrpcResource{
						Attributes: []types.GrpcKeyValue{
							{
								Key: "service.name",
								Value: types.GrpcAnyValue{
									StringValue: "service-2",
								},
							},
						},
					},
					ScopeSpans: []types.GrpcScopeSpans{
						{
							Spans: []types.GrpcSpan{
								{
									TraceID:           "trace-2",
									Name:              "op2",
									StartTimeUnixNano: "1605873894680409000",
									EndTimeUnixNano:   "1605873894682409000",
								},
							},
						},
					},
					SchemaURL: "someschemaurl.com",
				},
			},
		}
		frame := TransformGrpcSearchResponse(response, "test-uid", "test-name", 0)
		experimental.CheckGoldenJSONFrame(t, "../testdata", "search_multiple_response.golden", frame, false)
	})
}
func TestGetAttributes(t *testing.T) {
	testAttributes := []types.GrpcKeyValue{
		{
			Key: "some-key1",
			Value: types.GrpcAnyValue{
				StringValue: "some-stringValue1",
			},
		},
		{
			Key: "some-key2",
			Value: types.GrpcAnyValue{
				BoolValue: "true",
			},
		},
		{
			Key: "some-key3",
			Value: types.GrpcAnyValue{
				IntValue: "0",
			},
		},
		{
			Key: "some-key4",
			Value: types.GrpcAnyValue{
				DoubleValue: "0",
			},
		},
		{
			Key: "some-key5",
			Value: types.GrpcAnyValue{
				ArrayValue: types.GrpcArrayValue{
					Values: []types.GrpcAnyValue{},
				},
			},
		},
		{
			Key: "some-key6",
			Value: types.GrpcAnyValue{
				KvListValue: types.KeyValueList{
					Values: []types.GrpcKeyValue{},
				},
			},
		},
		{
			Key: "some-key7",
			Value: types.GrpcAnyValue{
				BytesValue: "somebytesvalue",
			},
		},
	}
	t.Run("handles StringValue", func(t *testing.T) {
		actual := getAttribute(testAttributes, "some-key1")
		assert.Equal(t, types.GrpcAnyValue{
			StringValue: "some-stringValue1",
		}, actual)
	})
	t.Run("handles BoolValue", func(t *testing.T) {
		actual := getAttribute(testAttributes, "some-key2")
		assert.Equal(t, types.GrpcAnyValue{
			BoolValue: "true",
		}, actual)
	})
	t.Run("handles IntValue", func(t *testing.T) {
		actual := getAttribute(testAttributes, "some-key3")
		assert.Equal(t, types.GrpcAnyValue{
			IntValue: "0",
		}, actual)
	})
	t.Run("handles DoubleValue", func(t *testing.T) {
		actual := getAttribute(testAttributes, "some-key4")
		assert.Equal(t, types.GrpcAnyValue{
			DoubleValue: "0",
		}, actual)
	})
	t.Run("handles ArrayValue", func(t *testing.T) {
		actual := getAttribute(testAttributes, "some-key5")
		assert.Equal(t, types.GrpcAnyValue{
			ArrayValue: types.GrpcArrayValue{
				Values: []types.GrpcAnyValue{},
			},
		}, actual)
	})
	t.Run("handles KvListValue", func(t *testing.T) {
		actual := getAttribute(testAttributes, "some-key6")
		assert.Equal(t, types.GrpcAnyValue{
			KvListValue: types.KeyValueList{
				Values: []types.GrpcKeyValue{},
			},
		}, actual)
	})
	t.Run("handles BytesValue", func(t *testing.T) {
		actual := getAttribute(testAttributes, "some-key7")
		assert.Equal(t, types.GrpcAnyValue{
			BytesValue: "somebytesvalue",
		}, actual)
	})
	t.Run("handles non-existent value", func(t *testing.T) {
		actual := getAttribute(testAttributes, "some-key8")
		assert.Equal(t, types.GrpcAnyValue{}, actual)
	})
}

func TestTransformGrpcTraceResponse(t *testing.T) {
	t.Run("simple_trace", func(t *testing.T) {
		trace := []types.GrpcResourceSpans{
			{
				Resource: types.GrpcResource{
					Attributes: []types.GrpcKeyValue{
						{
							Key: "service.name",
							Value: types.GrpcAnyValue{
								StringValue: "tempo-querier",
							},
						},
						{
							Key: "cluster",
							Value: types.GrpcAnyValue{
								StringValue: "ops-tools1",
							},
						},
						{
							Key: "container",
							Value: types.GrpcAnyValue{
								StringValue: "tempo-query",
							},
						},
					},
				},
				ScopeSpans: []types.GrpcScopeSpans{
					{
						Scope: types.GrpcInstrumentationScope{
							Name:    "some_scope1",
							Version: "0.0.39",
						},
						Spans: []types.GrpcSpan{
							{
								TraceID:      "3fa414edcef6ad90",
								SpanID:       "3fa414edcef6ad90",
								ParentSpanID: "",
								Name:         "HTTP GET - api_traces_traceid",
								Attributes: []types.GrpcKeyValue{
									{
										Key: "sampler.type",
										Value: types.GrpcAnyValue{
											StringValue: "probabilistic",
										},
									},
									{
										Key: "sampler.param",
										Value: types.GrpcAnyValue{
											DoubleValue: "100.00",
										},
									},
								},
								StartTimeUnixNano: "1605873894680409000",
								EndTimeUnixNano:   "1605873895729550000",
							},
							{
								TraceID:      "3fa414edcef6ad90",
								SpanID:       "0f5c1808567e4403",
								ParentSpanID: "3fa414edcef6ad90",
								Name:         "HTTP GET - api_traces_traceid",
								Attributes: []types.GrpcKeyValue{
									{
										Key: "component",
										Value: types.GrpcAnyValue{
											StringValue: "gRPC",
										},
									},
									{
										Key: "span.kind",
										Value: types.GrpcAnyValue{
											DoubleValue: "client",
										},
									},
								},
								StartTimeUnixNano: "1605873894680587000",
								EndTimeUnixNano:   "1605873894682434000",
							},
						},
					},
				},
			},
		}
		frame := TransformGrpcTraceResponse(trace, "test")
		experimental.CheckGoldenJSONFrame(t, "../testdata", "simple_trace_grpc.golden", frame, false)
	})

	t.Run("complex_trace", func(t *testing.T) {
		trace := []types.GrpcResourceSpans{
			{
				Resource: types.GrpcResource{
					Attributes: []types.GrpcKeyValue{
						{
							Key: "service.name",
							Value: types.GrpcAnyValue{
								StringValue: "tempo-querier",
							},
						},
						{
							Key: "cluster",
							Value: types.GrpcAnyValue{
								StringValue: "ops-tools1",
							},
						},
						{
							Key: "container",
							Value: types.GrpcAnyValue{
								StringValue: "tempo-storage",
							},
						},
						{
							Key: "version",
							Value: types.GrpcAnyValue{
								StringValue: "2.0.1",
							},
						},
					},
				},
				ScopeSpans: []types.GrpcScopeSpans{
					{
						Spans: []types.GrpcSpan{
							{
								TraceID:           "3fa414edcef6ad90",
								SpanID:            "3fa414edcef6ad90",
								Name:              "HTTP GET - api_traces_traceid",
								Links:             []types.GrpcSpanLink{},
								StartTimeUnixNano: "1605873894680409000",
								EndTimeUnixNano:   "1605873895729550000",
								Attributes: []types.GrpcKeyValue{
									{
										Key: "sampler.type",
										Value: types.GrpcAnyValue{
											StringValue: "probabilistic",
										},
									},
									{
										Key: "sampler.param",
										Value: types.GrpcAnyValue{
											DoubleValue: "1",
										},
									},
									{
										Key: "error",
										Value: types.GrpcAnyValue{
											BoolValue: "true",
										},
									},
									{
										Key: "http.status_code",
										Value: types.GrpcAnyValue{
											IntValue: "500",
										},
									},
								},
								Events: []types.GrpcSpanEvent{
									{
										TimeUnixNano: "1605873894681000000",
										Attributes: []types.GrpcKeyValue{
											{
												Key: "event",
												Value: types.GrpcAnyValue{
													StringValue: "error",
												},
											},
											{
												Key: "message",
												Value: types.GrpcAnyValue{
													StringValue: "Internal server error",
												},
											},
										},
									},
								},
							},
							{
								TraceID: "3fa414edcef6ad90",
								SpanID:  "0f5c1808567e4403",
								Name:    "/tempopb.Querier/FindTraceByID",
								Links: []types.GrpcSpanLink{
									{
										TraceID: "3fa414edcef6ad90",
										SpanID:  "3fa414edcef6ad90",
									},
								},
								StartTimeUnixNano: "1605873894680587000",
								EndTimeUnixNano:   "1605873894682434000",
								Attributes: []types.GrpcKeyValue{
									{
										Key: "component",
										Value: types.GrpcAnyValue{
											StringValue: "gRPC",
										},
									},
									{
										Key: "span.kind",
										Value: types.GrpcAnyValue{
											StringValue: "client",
										},
									},
									{
										Key: "error",
										Value: types.GrpcAnyValue{
											BoolValue: "true",
										},
									},
									{
										Key: "grpc.status_code",
										Value: types.GrpcAnyValue{
											IntValue: "13",
										},
									},
								},
								Events: []types.GrpcSpanEvent{
									{
										TimeUnixNano: "1605873894680700000",
										Attributes: []types.GrpcKeyValue{
											{
												Key: "event",
												Value: types.GrpcAnyValue{
													StringValue: "error",
												},
											},
											{
												Key: "message",
												Value: types.GrpcAnyValue{
													StringValue: "gRPC error: INTERNAL",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
			{
				Resource: types.GrpcResource{
					Attributes: []types.GrpcKeyValue{
						{
							Key: "service.name",
							Value: types.GrpcAnyValue{
								StringValue: "tempo-storage",
							},
						},
						{
							Key: "cluster",
							Value: types.GrpcAnyValue{
								StringValue: "ops-tools1",
							},
						},
						{
							Key: "container",
							Value: types.GrpcAnyValue{
								StringValue: "tempo-storage",
							},
						},
						{
							Key: "version",
							Value: types.GrpcAnyValue{
								StringValue: "2.0.1",
							},
						},
					},
				},
				ScopeSpans: []types.GrpcScopeSpans{
					{
						Spans: []types.GrpcSpan{
							{
								TraceID: "3fa414edcef6ad90",
								SpanID:  "1a2b3c4d5e6f7g8h",
								Name:    "db.query",
								Links: []types.GrpcSpanLink{
									{
										TraceID: "3fa414edcef6ad90",
										SpanID:  "0f5c1808567e4403",
									},
								},
								StartTimeUnixNano: "1605873894680800000",
								EndTimeUnixNano:   "1605873894681300000",
								Attributes: []types.GrpcKeyValue{
									{
										Key: "db.type",
										Value: types.GrpcAnyValue{
											StringValue: "postgresql",
										},
									},
									{
										Key: "db.statement",
										Value: types.GrpcAnyValue{
											StringValue: "SELECT * FROM traces WHERE id = $1",
										},
									},
									{
										Key: "error",
										Value: types.GrpcAnyValue{
											BoolValue: "true",
										},
									},
								},
								Events: []types.GrpcSpanEvent{
									{
										TimeUnixNano: "1605873894681000000",
										Attributes: []types.GrpcKeyValue{
											{
												Key: "event",
												Value: types.GrpcAnyValue{
													StringValue: "error",
												},
											},
											{
												Key: "message",
												Value: types.GrpcAnyValue{
													StringValue: "Database connection timeout",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
		}

		frame := TransformGrpcTraceResponse(trace, "test")
		experimental.CheckGoldenJSONFrame(t, "../testdata", "complex_trace_grpc.golden", frame, false)
	})
}

func TestProcessSpanKind(t *testing.T) {
	t.Run("converts unspecified span kind", func(t *testing.T) {
		actual := processSpanKind(0)
		assert.Equal(t, "unspecified", actual)
	})

	t.Run("converts internal span kind", func(t *testing.T) {
		actual := processSpanKind(1)
		assert.Equal(t, "internal", actual)
	})

	t.Run("converts server span kind", func(t *testing.T) {
		actual := processSpanKind(2)
		assert.Equal(t, "server", actual)
	})

	t.Run("converts client span kind", func(t *testing.T) {
		actual := processSpanKind(3)
		assert.Equal(t, "client", actual)
	})

	t.Run("converts producer span kind", func(t *testing.T) {
		actual := processSpanKind(4)
		assert.Equal(t, "producer", actual)
	})

	t.Run("converts consumer span kind", func(t *testing.T) {
		actual := processSpanKind(5)
		assert.Equal(t, "consumer", actual)
	})

	t.Run("converts unsupported span kind", func(t *testing.T) {
		actual := processSpanKind(10)
		assert.Equal(t, "unspecified", actual)
	})
}

func TestProcessAttributes(t *testing.T) {
	t.Run("processes empty attributes", func(t *testing.T) {
		actual := processAttributes([]types.GrpcKeyValue{})
		assert.Equal(t, []types.KeyValueType{}, actual)
	})
	t.Run("processes string attribute types", func(t *testing.T) {
		attributes := []types.GrpcKeyValue{
			{
				Key: "key1",
				Value: types.GrpcAnyValue{
					StringValue: "value1",
				},
			},
		}
		expected := []types.KeyValueType{
			{
				Key:   "key1",
				Value: "value1",
				Type:  "string",
			},
		}
		actual := processAttributes(attributes)
		assert.Equal(t, expected, actual)
	})

	t.Run("processes bool attribute types", func(t *testing.T) {
		attributes := []types.GrpcKeyValue{
			{
				Key: "key1",
				Value: types.GrpcAnyValue{
					BoolValue: "true",
				},
			},
		}
		expected := []types.KeyValueType{
			{
				Key:   "key1",
				Value: true,
				Type:  "boolean",
			},
		}
		actual := processAttributes(attributes)
		assert.Equal(t, expected, actual)
	})

	t.Run("processes int attribute types", func(t *testing.T) {
		attributes := []types.GrpcKeyValue{
			{
				Key: "key1",
				Value: types.GrpcAnyValue{
					IntValue: "10",
				},
			},
		}
		expected := []types.KeyValueType{
			{
				Key:   "key1",
				Value: int64(10),
				Type:  "int64",
			},
		}
		actual := processAttributes(attributes)
		assert.Equal(t, expected, actual)
	})

	t.Run("processes double attribute types", func(t *testing.T) {
		attributes := []types.GrpcKeyValue{
			{
				Key: "key1",
				Value: types.GrpcAnyValue{
					DoubleValue: "100.50",
				},
			},
		}
		expected := []types.KeyValueType{
			{
				Key:   "key1",
				Value: float64(100.50),
				Type:  "float64",
			},
		}
		actual := processAttributes(attributes)
		assert.Equal(t, expected, actual)
	})

	t.Run("processes arrayvalue attribute types", func(t *testing.T) {
		attributes := []types.GrpcKeyValue{
			{
				Key: "key1",
				Value: types.GrpcAnyValue{
					ArrayValue: types.GrpcArrayValue{
						Values: []types.GrpcAnyValue{
							{
								StringValue: "value1",
							},
						},
					},
				},
			},
		}
		expected := []types.KeyValueType{
			{
				Key: "key1",
				Value: []types.GrpcAnyValue{
					{
						StringValue: "value1",
					},
				},
			},
		}
		actual := processAttributes(attributes)
		assert.Equal(t, expected, actual)
	})

	t.Run("processes kvlistvalue attribute types", func(t *testing.T) {
		attributes := []types.GrpcKeyValue{
			{
				Key: "key1",
				Value: types.GrpcAnyValue{
					KvListValue: types.KeyValueList{
						Values: []types.GrpcKeyValue{
							{
								Key: "key2",
								Value: types.GrpcAnyValue{
									StringValue: "value2",
								},
							},
						},
					},
				},
			},
		}
		expected := []types.KeyValueType{
			{
				Key: "key1",
				Value: []types.GrpcKeyValue{
					{
						Key: "key2",
						Value: types.GrpcAnyValue{
							StringValue: "value2",
						},
					},
				},
			},
		}
		actual := processAttributes(attributes)
		assert.Equal(t, expected, actual)
	})

	t.Run("processes bytes attribute types", func(t *testing.T) {
		attributes := []types.GrpcKeyValue{
			{
				Key: "key1",
				Value: types.GrpcAnyValue{
					BytesValue: "bytesvalue1",
				},
			},
		}
		expected := []types.KeyValueType{
			{
				Key:   "key1",
				Value: "bytesvalue1",
				Type:  "bytes",
			},
		}
		actual := processAttributes(attributes)
		assert.Equal(t, expected, actual)
	})
}

func TestConvertGrpcEventsToLogs(t *testing.T) {
	t.Run("converts events with timestamp and attributes", func(t *testing.T) {
		events := []types.GrpcSpanEvent{
			{
				TimeUnixNano: "2000",
				Name:         "error",
				Attributes: []types.GrpcKeyValue{
					{
						Key: "event",
						Value: types.GrpcAnyValue{
							StringValue: "error",
						},
					},
				},
			},
		}

		logs := convertGrpcEventsToLogs(events)

		expected := []types.TraceLog{
			{
				Name:      "error",
				Timestamp: int64(2),
				Fields: []types.KeyValueType{
					{
						Key:   "event",
						Value: "error",
						Type:  "string",
					},
				},
			},
		}

		assert.Equal(t, expected, logs)
	})

	t.Run("returns zero timestamp when parsing fails", func(t *testing.T) {
		events := []types.GrpcSpanEvent{
			{
				TimeUnixNano: "invalid",
				Name:         "log-without-timestamp",
			},
		}

		logs := convertGrpcEventsToLogs(events)
		assert.Len(t, logs, 1)
		assert.Equal(t, int64(0), logs[0].Timestamp)
		assert.Equal(t, "log-without-timestamp", logs[0].Name)
		assert.Empty(t, logs[0].Fields)
	})
}

func TestConvertGrpcLinkToReference(t *testing.T) {
	t.Run("converts links to references", func(t *testing.T) {
		links := []types.GrpcSpanLink{
			{
				TraceID: "trace-id",
				SpanID:  "span-id",
			},
		}

		references := convertGrpcLinkToReference(links)

		expected := []types.TraceSpanReference{
			{
				TraceID: "trace-id",
				SpanID:  "span-id",
			},
		}

		assert.Equal(t, expected, references)
	})

	t.Run("returns empty slice for no links", func(t *testing.T) {
		references := convertGrpcLinkToReference(nil)
		assert.Empty(t, references)
	})
}
