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

package io.opentelemetry.javaagent.bootstrap;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import javax.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;

class PatchLoggerTest {
  @Test
  void testImplementsAllMethods() {
    Set<MethodSignature> patchLoggerMethods = new HashSet<>();
    for (Method method : PatchLogger.class.getMethods()) {
      MethodSignature methodSignature = new MethodSignature();
      methodSignature.name = method.getName();
      for (Class<?> clazz : method.getParameterTypes()) {
        String parameterType = clazz.getName();
        methodSignature.parameterTypes.add(
            parameterType.replaceFirst(
                "io.opentelemetry.javaagent.bootstrap.PatchLogger", "java.util.logging.Logger"));
      }
      methodSignature.returnType =
          method
              .getReturnType()
              .getName()
              .replace(
                  "io.opentelemetry.javaagent.bootstrap.PatchLogger", "java.util.logging.Logger");
      patchLoggerMethods.add(methodSignature);
    }
    Set<MethodSignature> julLoggerMethods = new HashSet<>();
    for (Method method : java.util.logging.Logger.class.getMethods()) {
      String methodName = method.getName();
      if (methodName.contains("Handler") || methodName.contains("Filter")) {
        continue;
      }
      MethodSignature builder = new MethodSignature();
      builder.name = methodName;
      List<String> parameterTypes = new ArrayList<>();
      for (Class<?> clazz : method.getParameterTypes()) {
        parameterTypes.add(clazz.getName());
      }
      builder.parameterTypes.addAll(parameterTypes);
      builder.returnType = method.getReturnType().getName();
      julLoggerMethods.add(builder);
    }
    assertThat(patchLoggerMethods).containsAll(julLoggerMethods);
  }

  @Test
  void testGetName() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.name()).thenReturn("xyz");
    // when
    PatchLogger logger = new PatchLogger(internalLogger);
    // then
    assertThat(logger.getName()).isEqualTo("xyz");
  }

  @Test
  void testNormalMethods() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.severe("ereves");
    logger.warning("gninraw");
    logger.info("ofni");
    logger.config("gifnoc");
    logger.fine("enif");
    logger.finer("renif");
    logger.finest("tsenif");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testParameterizedLevelMethodsWithNoParams() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.log(Level.SEVERE, "ereves");
    logger.log(Level.WARNING, "gninraw");
    logger.log(Level.INFO, "ofni");
    logger.log(Level.CONFIG, "gifnoc");
    logger.log(Level.FINE, "enif");
    logger.log(Level.FINER, "renif");
    logger.log(Level.FINEST, "tsenif");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testParameterizedLevelMethodsWithSingleParam() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.log(Level.SEVERE, "ereves: {0}", "a");
    logger.log(Level.WARNING, "gninraw: {0}", "b");
    logger.log(Level.INFO, "ofni: {0}", "c");
    logger.log(Level.CONFIG, "gifnoc: {0}", "d");
    logger.log(Level.FINE, "enif: {0}", "e");
    logger.log(Level.FINER, "renif: {0}", "f");
    logger.log(Level.FINEST, "tsenif: {0}", "g");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.ERROR);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves: a", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.WARN);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw: b", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni: c", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc: d", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.DEBUG);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif: e", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif: f", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif: g", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testParameterizedLevelMethodsWithArrayOfParams() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.log(Level.SEVERE, "ereves: {0},{1}", new Object[] {"a", "b"});
    logger.log(Level.WARNING, "gninraw: {0},{1}", new Object[] {"b", "c"});
    logger.log(Level.INFO, "ofni: {0},{1}", new Object[] {"c", "d"});
    logger.log(Level.CONFIG, "gifnoc: {0},{1}", new Object[] {"d", "e"});
    logger.log(Level.FINE, "enif: {0},{1}", new Object[] {"e", "f"});
    logger.log(Level.FINER, "renif: {0},{1}", new Object[] {"f", "g"});
    logger.log(Level.FINEST, "tsenif: {0},{1}", new Object[] {"g", "h"});

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.ERROR);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves: a,b", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.WARN);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw: b,c", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni: c,d", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc: d,e", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.DEBUG);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif: e,f", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif: f,g", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif: g,h", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testParameterizedLevelMethodsWithThrowable() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);
    Throwable a = new Throwable();
    Throwable b = new Throwable();
    Throwable c = new Throwable();
    Throwable d = new Throwable();
    Throwable e = new Throwable();
    Throwable f = new Throwable();
    Throwable g = new Throwable();

    // when
    logger.log(Level.SEVERE, "ereves", a);
    logger.log(Level.WARNING, "gninraw", b);
    logger.log(Level.INFO, "ofni", c);
    logger.log(Level.CONFIG, "gifnoc", d);
    logger.log(Level.FINE, "enif", e);
    logger.log(Level.FINER, "renif", f);
    logger.log(Level.FINEST, "tsenif", g);

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", a);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", b);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", c);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", d);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", e);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", f);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", g);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testIsLoggableAll() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);

    // when
    PatchLogger logger = new PatchLogger(internalLogger);

    // then
    assertThat(logger.isLoggable(Level.SEVERE)).isTrue();
    assertThat(logger.isLoggable(Level.WARNING)).isTrue();
    assertThat(logger.isLoggable(Level.INFO)).isTrue();
    assertThat(logger.isLoggable(Level.CONFIG)).isTrue();
    assertThat(logger.isLoggable(Level.FINE)).isTrue();
    assertThat(logger.isLoggable(Level.FINER)).isTrue();
    assertThat(logger.isLoggable(Level.FINEST)).isTrue();
  }

  @Test
  void testIsLoggableSome() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(InternalLogger.Level.ERROR)).thenReturn(true);
    when(internalLogger.isLoggable(InternalLogger.Level.WARN)).thenReturn(true);

    // when
    PatchLogger logger = new PatchLogger(internalLogger);

    // then
    assertThat(logger.isLoggable(Level.SEVERE)).isTrue();
    assertThat(logger.isLoggable(Level.WARNING)).isTrue();
    assertThat(logger.isLoggable(Level.INFO)).isFalse();
    assertThat(logger.isLoggable(Level.CONFIG)).isFalse();
    assertThat(logger.isLoggable(Level.FINE)).isFalse();
    assertThat(logger.isLoggable(Level.FINER)).isFalse();
    assertThat(logger.isLoggable(Level.FINEST)).isFalse();
  }

  @Test
  void testIsLoggableNone() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);

    // when
    PatchLogger logger = new PatchLogger(internalLogger);

    // then
    assertThat(logger.isLoggable(Level.SEVERE)).isFalse();
    assertThat(logger.isLoggable(Level.WARNING)).isFalse();
    assertThat(logger.isLoggable(Level.INFO)).isFalse();
    assertThat(logger.isLoggable(Level.CONFIG)).isFalse();
    assertThat(logger.isLoggable(Level.FINE)).isFalse();
    assertThat(logger.isLoggable(Level.FINER)).isFalse();
    assertThat(logger.isLoggable(Level.FINEST)).isFalse();
  }

  @Test
  void testGetLevelSevere() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(InternalLogger.Level.ERROR)).thenReturn(true);
    // when
    PatchLogger logger = new PatchLogger(internalLogger);
    // then
    assertThat(logger.getLevel()).isEqualTo(Level.SEVERE);
  }

  @Test
  void testGetLevelWarning() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(InternalLogger.Level.WARN)).thenReturn(true);
    // when
    PatchLogger logger = new PatchLogger(internalLogger);
    // then
    assertThat(logger.getLevel()).isEqualTo(Level.WARNING);
  }

  @Test
  void testGetLevelConfig() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(InternalLogger.Level.INFO)).thenReturn(true);
    // when
    PatchLogger logger = new PatchLogger(internalLogger);
    // then
    assertThat(logger.getLevel()).isEqualTo(Level.CONFIG);
  }

  @Test
  void testGetLevelFine() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(InternalLogger.Level.DEBUG)).thenReturn(true);
    // when
    PatchLogger logger = new PatchLogger(internalLogger);
    // then
    assertThat(logger.getLevel()).isEqualTo(Level.FINE);
  }

  @Test
  void testGetLevelFinest() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(InternalLogger.Level.TRACE)).thenReturn(true);
    // when
    PatchLogger logger = new PatchLogger(internalLogger);
    // then
    assertThat(logger.getLevel()).isEqualTo(Level.FINEST);
  }

  @Test
  void testGetLevelOff() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    // when
    PatchLogger logger = new PatchLogger(internalLogger);
    // then
    assertThat(logger.getLevel()).isEqualTo(Level.OFF);
  }

  @Test
  void testLogpParameterizedLevelMethodsWithNoParams() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.logp(Level.SEVERE, null, null, "ereves");
    logger.logp(Level.WARNING, null, null, "gninraw");
    logger.logp(Level.INFO, null, null, "ofni");
    logger.logp(Level.CONFIG, null, null, "gifnoc");
    logger.logp(Level.FINE, null, null, "enif");
    logger.logp(Level.FINER, null, null, "renif");
    logger.logp(Level.FINEST, null, null, "tsenif");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogpParameterizedLevelMethodsWithSingleParam() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.logp(Level.SEVERE, null, null, "ereves: {0}", "a");
    logger.logp(Level.WARNING, null, null, "gninraw: {0}", "b");
    logger.logp(Level.INFO, null, null, "ofni: {0}", "c");
    logger.logp(Level.CONFIG, null, null, "gifnoc: {0}", "d");
    logger.logp(Level.FINE, null, null, "enif: {0}", "e");
    logger.logp(Level.FINER, null, null, "renif: {0}", "f");
    logger.logp(Level.FINEST, null, null, "tsenif: {0}", "g");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.ERROR);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves: a", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.WARN);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw: b", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni: c", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc: d", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.DEBUG);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif: e", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif: f", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif: g", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogpParameterizedLevelMethodsWithArrayOfParams() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.logp(Level.SEVERE, null, null, "ereves: {0},{1}", new Object[] {"a", "b"});
    logger.logp(Level.WARNING, null, null, "gninraw: {0},{1}", new Object[] {"b", "c"});
    logger.logp(Level.INFO, null, null, "ofni: {0},{1}", new Object[] {"c", "d"});
    logger.logp(Level.CONFIG, null, null, "gifnoc: {0},{1}", new Object[] {"d", "e"});
    logger.logp(Level.FINE, null, null, "enif: {0},{1}", new Object[] {"e", "f"});
    logger.logp(Level.FINER, null, null, "renif: {0},{1}", new Object[] {"f", "g"});
    logger.logp(Level.FINEST, null, null, "tsenif: {0},{1}", new Object[] {"g", "h"});

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.ERROR);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves: a,b", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.WARN);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw: b,c", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni: c,d", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc: d,e", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.DEBUG);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif: e,f", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif: f,g", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif: g,h", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogpParameterizedLevelMethodsWithThrowable() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);
    Throwable a = new Throwable();
    Throwable b = new Throwable();
    Throwable c = new Throwable();
    Throwable d = new Throwable();
    Throwable e = new Throwable();
    Throwable f = new Throwable();
    Throwable g = new Throwable();

    // when
    logger.logp(Level.SEVERE, null, null, "ereves", a);
    logger.logp(Level.WARNING, null, null, "gninraw", b);
    logger.logp(Level.INFO, null, null, "ofni", c);
    logger.logp(Level.CONFIG, null, null, "gifnoc", d);
    logger.logp(Level.FINE, null, null, "enif", e);
    logger.logp(Level.FINER, null, null, "renif", f);
    logger.logp(Level.FINEST, null, null, "tsenif", g);

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", a);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", b);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", c);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", d);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", e);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", f);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", g);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogrbParameterizedLevelMethodsWithNoParams() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.logrb(Level.SEVERE, null, null, null, "ereves");
    logger.logrb(Level.WARNING, null, null, null, "gninraw");
    logger.logrb(Level.INFO, null, null, null, "ofni");
    logger.logrb(Level.CONFIG, null, null, null, "gifnoc");
    logger.logrb(Level.FINE, null, null, null, "enif");
    logger.logrb(Level.FINER, null, null, null, "renif");
    logger.logrb(Level.FINEST, null, null, null, "tsenif");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", null);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogrbParameterizedLevelMethodsWithSingleParam() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.logrb(Level.SEVERE, null, null, null, "ereves: {0}", "a");
    logger.logrb(Level.WARNING, null, null, null, "gninraw: {0}", "b");
    logger.logrb(Level.INFO, null, null, null, "ofni: {0}", "c");
    logger.logrb(Level.CONFIG, null, null, null, "gifnoc: {0}", "d");
    logger.logrb(Level.FINE, null, null, null, "enif: {0}", "e");
    logger.logrb(Level.FINER, null, null, null, "renif: {0}", "f");
    logger.logrb(Level.FINEST, null, null, null, "tsenif: {0}", "g");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.ERROR);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves: a", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.WARN);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw: b", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni: c", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc: d", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.DEBUG);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif: e", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif: f", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif: g", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogrbParameterizedLevelMethodsWithArrayOfParams() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.logrb(
        Level.SEVERE, null, null, (String) null, "ereves: {0},{1}", new Object[] {"a", "b"});
    logger.logrb(
        Level.WARNING, null, null, (String) null, "gninraw: {0},{1}", new Object[] {"b", "c"});
    logger.logrb(Level.INFO, null, null, (String) null, "ofni: {0},{1}", new Object[] {"c", "d"});
    logger.logrb(
        Level.CONFIG, null, null, (String) null, "gifnoc: {0},{1}", new Object[] {"d", "e"});
    logger.logrb(Level.FINE, null, null, (String) null, "enif: {0},{1}", new Object[] {"e", "f"});
    logger.logrb(Level.FINER, null, null, (String) null, "renif: {0},{1}", new Object[] {"f", "g"});
    logger.logrb(
        Level.FINEST, null, null, (String) null, "tsenif: {0},{1}", new Object[] {"g", "h"});

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.ERROR);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves: a,b", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.WARN);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw: b,c", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni: c,d", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc: d,e", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.DEBUG);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif: e,f", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif: f,g", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif: g,h", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogrbParameterizedLevelMethodsWithVarArgsOfParams() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.logrb(Level.SEVERE, (String) null, null, null, "ereves: {0},{1}", "a", "b");
    logger.logrb(Level.WARNING, (String) null, null, null, "gninraw: {0},{1}", "b", "c");
    logger.logrb(Level.INFO, (String) null, null, null, "ofni: {0},{1}", "c", "d");
    logger.logrb(Level.CONFIG, (String) null, null, null, "gifnoc: {0},{1}", "d", "e");
    logger.logrb(Level.FINE, (String) null, null, null, "enif: {0},{1}", "e", "f");
    logger.logrb(Level.FINER, (String) null, null, null, "renif: {0},{1}", "f", "g");
    logger.logrb(Level.FINEST, (String) null, null, null, "tsenif: {0},{1}", "g", "h");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.ERROR);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves: a,b", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.WARN);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw: b,c", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni: c,d", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc: d,e", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.DEBUG);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif: e,f", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif: f,g", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif: g,h", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogrbParameterizedLevelMethodsWithVarArgsOfParams2() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    when(internalLogger.isLoggable(any())).thenReturn(true);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.logrb(Level.SEVERE, (ResourceBundle) null, "ereves: {0},{1}", "a", "b");
    logger.logrb(Level.WARNING, (ResourceBundle) null, "gninraw: {0},{1}", "b", "c");
    logger.logrb(Level.INFO, (ResourceBundle) null, "ofni: {0},{1}", "c", "d");
    logger.logrb(Level.CONFIG, (ResourceBundle) null, "gifnoc: {0},{1}", "d", "e");
    logger.logrb(Level.FINE, (ResourceBundle) null, "enif: {0},{1}", "e", "f");
    logger.logrb(Level.FINER, (ResourceBundle) null, "renif: {0},{1}", "f", "g");
    logger.logrb(Level.FINEST, (ResourceBundle) null, "tsenif: {0},{1}", "g", "h");

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.ERROR);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves: a,b", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.WARN);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw: b,c", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni: c,d", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.INFO);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc: d,e", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.DEBUG);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif: e,f", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif: f,g", null);
    inOrder.verify(internalLogger).isLoggable(InternalLogger.Level.TRACE);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif: g,h", null);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogrbParameterizedLevelMethodsWithThrowable() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);
    Throwable a = new Throwable();
    Throwable b = new Throwable();
    Throwable c = new Throwable();
    Throwable d = new Throwable();
    Throwable e = new Throwable();
    Throwable f = new Throwable();
    Throwable g = new Throwable();

    // when
    logger.logrb(Level.SEVERE, null, null, (String) null, "ereves", a);
    logger.logrb(Level.WARNING, null, null, (String) null, "gninraw", b);
    logger.logrb(Level.INFO, null, null, (String) null, "ofni", c);
    logger.logrb(Level.CONFIG, null, null, (String) null, "gifnoc", d);
    logger.logrb(Level.FINE, null, null, (String) null, "enif", e);
    logger.logrb(Level.FINER, null, null, (String) null, "renif", f);
    logger.logrb(Level.FINEST, null, null, (String) null, "tsenif", g);

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", a);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", b);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", c);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", d);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", e);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", f);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", g);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogrbParameterizedLevelMethodsWithThrowable2() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);
    Throwable a = new Throwable();
    Throwable b = new Throwable();
    Throwable c = new Throwable();
    Throwable d = new Throwable();
    Throwable e = new Throwable();
    Throwable f = new Throwable();
    Throwable g = new Throwable();

    // when
    logger.logrb(Level.SEVERE, null, null, (ResourceBundle) null, "ereves", a);
    logger.logrb(Level.WARNING, null, null, (ResourceBundle) null, "gninraw", b);
    logger.logrb(Level.INFO, null, null, (ResourceBundle) null, "ofni", c);
    logger.logrb(Level.CONFIG, null, null, (ResourceBundle) null, "gifnoc", d);
    logger.logrb(Level.FINE, null, null, (ResourceBundle) null, "enif", e);
    logger.logrb(Level.FINER, null, null, (ResourceBundle) null, "renif", f);
    logger.logrb(Level.FINEST, null, null, (ResourceBundle) null, "tsenif", g);

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", a);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", b);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", c);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", d);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", e);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", f);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", g);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testLogrbParameterizedLevelMethodsWithResourceBundleObjectAndThrowable() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);
    Throwable a = new Throwable();
    Throwable b = new Throwable();
    Throwable c = new Throwable();
    Throwable d = new Throwable();
    Throwable e = new Throwable();
    Throwable f = new Throwable();
    Throwable g = new Throwable();

    // when
    logger.logrb(Level.SEVERE, null, null, (ResourceBundle) null, "ereves", a);
    logger.logrb(Level.WARNING, null, null, (ResourceBundle) null, "gninraw", b);
    logger.logrb(Level.INFO, null, null, (ResourceBundle) null, "ofni", c);
    logger.logrb(Level.CONFIG, null, null, (ResourceBundle) null, "gifnoc", d);
    logger.logrb(Level.FINE, null, null, (ResourceBundle) null, "enif", e);
    logger.logrb(Level.FINER, null, null, (ResourceBundle) null, "renif", f);
    logger.logrb(Level.FINEST, null, null, (ResourceBundle) null, "tsenif", g);

    // then
    InOrder inOrder = Mockito.inOrder(internalLogger);
    inOrder.verify(internalLogger).log(InternalLogger.Level.ERROR, "ereves", a);
    inOrder.verify(internalLogger).log(InternalLogger.Level.WARN, "gninraw", b);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "ofni", c);
    inOrder.verify(internalLogger).log(InternalLogger.Level.INFO, "gifnoc", d);
    inOrder.verify(internalLogger).log(InternalLogger.Level.DEBUG, "enif", e);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "renif", f);
    inOrder.verify(internalLogger).log(InternalLogger.Level.TRACE, "tsenif", g);
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testEnteringExitingThrowingMethods() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);
    PatchLogger logger = new PatchLogger(internalLogger);

    // when
    logger.entering(null, null);
    logger.entering(null, null, new Object());
    logger.entering(null, null, new Object[0]);
    logger.exiting(null, null);
    logger.exiting(null, null, new Object());
    logger.throwing(null, null, null);

    // then
    verifyNoMoreInteractions(internalLogger);
  }

  @Test
  void testResourceBundle() {
    // given
    InternalLogger internalLogger = mock(InternalLogger.class);

    // when
    PatchLogger logger = new PatchLogger(internalLogger);

    // then
    assertThat(logger.getResourceBundle()).isNull();
    assertThat(logger.getResourceBundleName()).isNull();
    verifyNoMoreInteractions(internalLogger);
  }

  static class MethodSignature {
    String name;
    List<String> parameterTypes = new ArrayList<>();
    String returnType;

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj == this) {
        return true;
      }
      if (!(obj instanceof MethodSignature)) {
        return false;
      }
      MethodSignature other = (MethodSignature) obj;
      return Objects.equals(name, other.name)
          && Objects.equals(parameterTypes, other.parameterTypes)
          && Objects.equals(returnType, other.returnType);
    }

    @Override
    public int hashCode() {
      return Objects.hash(name, parameterTypes, returnType);
    }

    @Override
    public String toString() {
      String params = parameterTypes.stream().reduce((a, b) -> a + ", " + b).orElse("");
      return name + "(" + params + ")" + returnType;
    }
  }
}
