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

package io.opentelemetry.instrumentation.api.incubator.semconv.messaging;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.semconv.SemanticAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.assertj.core.data.MapEntry;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class MessagingAttributesExtractorTest {

  @ParameterizedTest
  @MethodSource("destinations")
  void shouldExtractAllAvailableAttributes(
      boolean temporary,
      boolean anonymous,
      String destination,
      MessageOperation operation,
      String expectedDestination) {
    // given
    Map<String, String> request = new HashMap<>();
    request.put("system", "myQueue");
    request.put("destinationKind", "topic");
    request.put("destination", destination);
    request.put("destinationTemplate", destination);
    if (temporary) {
      request.put("temporaryDestination", "y");
    }
    if (anonymous) {
      request.put("anonymousDestination", "y");
    }
    request.put("url", "http://broker/topic");
    request.put("conversationId", "42");
    request.put("bodySize", "100");
    request.put("envelopeSize", "120");
    request.put("clientId", "43");
    request.put("batchMessageCount", "2");

    AttributesExtractor<Map<String, String>, String> underTest =
        MessagingAttributesExtractor.create(TestGetter.INSTANCE, operation);

    Context context = Context.root();

    // when
    AttributesBuilder startAttributes = Attributes.builder();
    underTest.onStart(startAttributes, context, request);

    AttributesBuilder endAttributes = Attributes.builder();
    underTest.onEnd(endAttributes, context, request, "42", null);

    // then
    List<MapEntry<AttributeKey<?>, Object>> expectedEntries = new ArrayList<>();
    expectedEntries.add(entry(SemanticAttributes.MESSAGING_SYSTEM, "myQueue"));
    expectedEntries.add(entry(SemanticAttributes.MESSAGING_DESTINATION_NAME, expectedDestination));
    if (temporary) {
      expectedEntries.add(entry(SemanticAttributes.MESSAGING_DESTINATION_TEMPORARY, true));
    } else {
      expectedEntries.add(
          entry(SemanticAttributes.MESSAGING_DESTINATION_TEMPLATE, expectedDestination));
    }
    if (anonymous) {
      expectedEntries.add(entry(SemanticAttributes.MESSAGING_DESTINATION_ANONYMOUS, true));
    }
    expectedEntries.add(entry(SemanticAttributes.MESSAGING_MESSAGE_CONVERSATION_ID, "42"));
    expectedEntries.add(entry(SemanticAttributes.MESSAGING_MESSAGE_BODY_SIZE, 100L));
    expectedEntries.add(entry(SemanticAttributes.MESSAGING_MESSAGE_ENVELOPE_SIZE, 120L));
    expectedEntries.add(entry(SemanticAttributes.MESSAGING_CLIENT_ID, "43"));
    expectedEntries.add(entry(SemanticAttributes.MESSAGING_OPERATION, operation.operationName()));

    @SuppressWarnings({"unchecked", "rawtypes"})
    MapEntry<? extends AttributeKey<?>, ?>[] expectedEntriesArr =
        expectedEntries.toArray(new MapEntry[0]);
    assertThat(startAttributes.build()).containsOnly(expectedEntriesArr);

    assertThat(endAttributes.build())
        .containsOnly(
            entry(SemanticAttributes.MESSAGING_MESSAGE_ID, "42"),
            entry(SemanticAttributes.MESSAGING_BATCH_MESSAGE_COUNT, 2L));
  }

  static Stream<Arguments> destinations() {
    return Stream.of(
        Arguments.of(false, false, "destination", MessageOperation.RECEIVE, "destination"),
        Arguments.of(true, true, null, MessageOperation.PROCESS, "(temporary)"));
  }

  @Test
  void shouldExtractNoAttributesIfNoneAreAvailable() {
    // given
    AttributesExtractor<Map<String, String>, String> underTest =
        MessagingAttributesExtractor.create(TestGetter.INSTANCE, null);

    Context context = Context.root();

    // when
    AttributesBuilder startAttributes = Attributes.builder();
    underTest.onStart(startAttributes, context, Collections.emptyMap());

    AttributesBuilder endAttributes = Attributes.builder();
    underTest.onEnd(endAttributes, context, Collections.emptyMap(), null, null);

    // then
    assertThat(startAttributes.build().isEmpty()).isTrue();

    assertThat(endAttributes.build().isEmpty()).isTrue();
  }

  enum TestGetter implements MessagingAttributesGetter<Map<String, String>, String> {
    INSTANCE;

    @Override
    public String getSystem(Map<String, String> request) {
      return request.get("system");
    }

    @Override
    public String getDestination(Map<String, String> request) {
      return request.get("destination");
    }

    @Nullable
    @Override
    public String getDestinationTemplate(Map<String, String> request) {
      return request.get("destinationTemplate");
    }

    @Override
    public boolean isTemporaryDestination(Map<String, String> request) {
      return request.containsKey("temporaryDestination");
    }

    @Override
    public boolean isAnonymousDestination(Map<String, String> request) {
      return request.containsKey("anonymousDestination");
    }

    @Override
    public String getConversationId(Map<String, String> request) {
      return request.get("conversationId");
    }

    @Nullable
    @Override
    public Long getMessageBodySize(Map<String, String> request) {
      String payloadSize = request.get("bodySize");
      return payloadSize == null ? null : Long.valueOf(payloadSize);
    }

    @Nullable
    @Override
    public Long getMessageEnvelopeSize(Map<String, String> request) {
      String payloadSize = request.get("envelopeSize");
      return payloadSize == null ? null : Long.valueOf(payloadSize);
    }

    @Override
    public String getMessageId(Map<String, String> request, String response) {
      return response;
    }

    @Nullable
    @Override
    public String getClientId(Map<String, String> request) {
      return request.get("clientId");
    }

    @Nullable
    @Override
    public Long getBatchMessageCount(Map<String, String> request, @Nullable String response) {
      String payloadSize = request.get("batchMessageCount");
      return payloadSize == null ? null : Long.valueOf(payloadSize);
    }
  }
}
