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

package io.opentelemetry.instrumentation.runtimemetrics.java8;

import static io.opentelemetry.instrumentation.runtimemetrics.java8.ScopeUtil.EXPECTED_SCOPE;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension;
import io.opentelemetry.semconv.JvmAttributes;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;

@ExtendWith(MockitoExtension.class)
class ThreadsStableSemconvTest {

  @RegisterExtension
  static final InstrumentationExtension testing = LibraryInstrumentationExtension.create();

  @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create();

  @Mock private ThreadMXBean threadBean;

  @Test
  @EnabledOnJre(JRE.JAVA_8)
  void registerObservers_Java8() {
    when(threadBean.getThreadCount()).thenReturn(7);
    when(threadBean.getDaemonThreadCount()).thenReturn(2);

    Threads.INSTANCE
        .registerObservers(testing.getOpenTelemetry(), threadBean)
        .forEach(cleanup::deferCleanup);

    testing.waitAndAssertMetrics(
        "io.opentelemetry.runtime-telemetry-java8",
        "jvm.thread.count",
        metrics ->
            metrics.anySatisfy(
                metricData ->
                    assertThat(metricData)
                        .hasInstrumentationScope(EXPECTED_SCOPE)
                        .hasDescription("Number of executing platform threads.")
                        .hasUnit("{thread}")
                        .hasLongSumSatisfying(
                            sum ->
                                sum.isNotMonotonic()
                                    .hasPointsSatisfying(
                                        point ->
                                            point
                                                .hasValue(2)
                                                .hasAttributesSatisfying(
                                                    equalTo(JvmAttributes.JVM_THREAD_DAEMON, true)),
                                        point ->
                                            point
                                                .hasValue(5)
                                                .hasAttributesSatisfying(
                                                    equalTo(
                                                        JvmAttributes.JVM_THREAD_DAEMON,
                                                        false))))));
  }

  @Test
  @EnabledForJreRange(min = JRE.JAVA_9)
  void registerObservers_Java9AndNewer() {
    ThreadInfo threadInfo1 =
        mock(ThreadInfo.class, new ThreadInfoAnswer(false, Thread.State.RUNNABLE));
    ThreadInfo threadInfo2 =
        mock(ThreadInfo.class, new ThreadInfoAnswer(true, Thread.State.WAITING));

    long[] threadIds = {12, 32, 42};
    when(threadBean.getAllThreadIds()).thenReturn(threadIds);
    when(threadBean.getThreadInfo(threadIds))
        .thenReturn(new ThreadInfo[] {threadInfo1, null, threadInfo2});

    Threads.INSTANCE
        .registerObservers(testing.getOpenTelemetry(), threadBean)
        .forEach(cleanup::deferCleanup);

    testing.waitAndAssertMetrics(
        "io.opentelemetry.runtime-telemetry-java8",
        "jvm.thread.count",
        metrics ->
            metrics.anySatisfy(
                metricData ->
                    assertThat(metricData)
                        .hasInstrumentationScope(EXPECTED_SCOPE)
                        .hasDescription("Number of executing platform threads.")
                        .hasUnit("{thread}")
                        .hasLongSumSatisfying(
                            sum ->
                                sum.isNotMonotonic()
                                    .hasPointsSatisfying(
                                        point ->
                                            point
                                                .hasValue(1)
                                                .hasAttributesSatisfying(
                                                    equalTo(JvmAttributes.JVM_THREAD_DAEMON, false),
                                                    equalTo(
                                                        JvmAttributes.JVM_THREAD_STATE,
                                                        "runnable")),
                                        point ->
                                            point
                                                .hasValue(1)
                                                .hasAttributesSatisfying(
                                                    equalTo(JvmAttributes.JVM_THREAD_DAEMON, true),
                                                    equalTo(
                                                        JvmAttributes.JVM_THREAD_STATE,
                                                        "waiting"))))));
  }

  static final class ThreadInfoAnswer implements Answer<Object> {

    private final boolean isDaemon;
    private final Thread.State state;

    ThreadInfoAnswer(boolean isDaemon, Thread.State state) {
      this.isDaemon = isDaemon;
      this.state = state;
    }

    @Override
    public Object answer(InvocationOnMock invocation) {
      String methodName = invocation.getMethod().getName();
      if (methodName.equals("isDaemon")) {
        return isDaemon;
      } else if (methodName.equals("getThreadState")) {
        return state;
      }
      return null;
    }
  }
}
