// SPDX-FileCopyrightText: © 2024 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
//
// SPDX-License-Identifier: LGPL-3.0-or-later

package eu.mulk.quarkus.googlecloud.jsonlogging;

import static org.junit.jupiter.api.Assertions.assertLinesMatch;

import jakarta.json.spi.JsonProvider;
import java.util.Collection;
import java.util.List;
import org.jboss.logmanager.ExtLogRecord;
import org.jboss.logmanager.Level;
import org.junit.jupiter.api.Test;

class FormatterTest {

  private static final JsonProvider JSON = JsonProvider.provider();

  @Test
  void simpleRecord() {
    var logRecord = makeSimpleRecord();

    var formatter = new Formatter(List.of(), List.of());
    var formattingResult = formatter.format(logRecord);

    assertLinesMatch(
        List.of(
            "\\{"
                + "\"logging.googleapis.com/sourceLocation\":"
                + "\\{\"file\":\"ReflectionUtils.java\","
                + "\"line\":\"\\d+\","
                + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\""
                + "\\},"
                + "\"message\":\"Hello, world!\","
                + "\"severity\":\"INFO\","
                + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}"
                + "\\}\n"),
        List.of(formattingResult));
  }

  static ExtLogRecord makeSimpleRecord() {
    return new ExtLogRecord(Level.INFO, "Hello, world!", FormatterTest.class.getName());
  }

  @Test
  void structuredRecord() {
    var parameterProvider =
        new StructuredParameterProvider() {
          @Override
          public StructuredParameter getParameter() {
            var b = JSON.createObjectBuilder();
            b.add("traceId", "39f9a49a9567a8bd7087b708f8932550");
            b.add("spanId", "c7431b14630b633d");
            return () -> b;
          }
        };

    var labelProvider =
        new LabelProvider() {
          @Override
          public Collection<Label> getLabels() {
            return List.of(Label.of("requestId", "123"));
          }
        };

    var logRecord = makeStructuredRecord();

    var formatter = new Formatter(List.of(parameterProvider), List.of(labelProvider));
    var formattingResult = formatter.format(logRecord);
    assertLinesMatch(
        List.of(
            "\\{"
                + "\"logging.googleapis.com/insertId\":\"123-456-789\","
                + "\"logging.googleapis.com/labels\":\\{\"a\":\"b\",\"requestId\":\"123\"\\},"
                + "\"traceId\":\"39f9a49a9567a8bd7087b708f8932550\","
                + "\"spanId\":\"c7431b14630b633d\","
                + "\"one\":1,"
                + "\"two\":2.0,"
                + "\"yes\":true,"
                + "\"logging.googleapis.com/sourceLocation\":"
                + "\\{\"file\":\"ReflectionUtils.java\","
                + "\"line\":\"\\d+\","
                + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\""
                + "\\},"
                + "\"message\":\"Hello, world!\","
                + "\"severity\":\"INFO\","
                + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}"
                + "\\}\n"),
        List.of(formattingResult));
  }

  static ExtLogRecord makeStructuredRecord() {
    var logRecord = makeSimpleRecord();
    logRecord.setParameters(
        new Object[] {
          (StructuredParameter)
              () -> JSON.createObjectBuilder().add("one", 1).add("two", 2.0).add("yes", true),
          Label.of("a", "b"),
          InsertId.of("123-456-789"),
        });
    return logRecord;
  }

  @Test
  void massivelyStructuredRecord() {
    var logRecord = makeMassivelyStructuredRecord();

    var formatter = new Formatter(List.of(), List.of());
    var formattingResult = formatter.format(logRecord);
    assertLinesMatch(
        List.of(
            "\\{"
                + "\"int-0\":0,\"int-1\":1,\"int-2\":2,\"int-3\":3,\"int-4\":4,\"int-5\":5,\"int-6\":6,\"int-7\":7,\"int-8\":8,\"int-9\":9,"
                + "\"double-10\":10.0,\"double-11\":11.0,\"double-12\":12.0,\"double-13\":13.0,\"double-14\":14.0,"
                + "\"double-15\":15.0,\"double-16\":16.0,\"double-17\":17.0,\"double-18\":18.0,\"double-19\":19.0,"
                + "\"boolean-20\":true,\"boolean-21\":false,\"boolean-22\":true,\"boolean-23\":false,\"boolean-24\":true,"
                + "\"boolean-25\":false,\"boolean-26\":true,\"boolean-27\":false,\"boolean-28\":true,\"boolean-29\":false,"
                + "\"string-30\":\"30\",\"string-31\":\"31\",\"string-32\":\"32\",\"string-33\":\"33\",\"string-34\":\"34\","
                + "\"string-35\":\"35\",\"string-36\":\"36\",\"string-37\":\"37\",\"string-38\":\"38\",\"string-39\":\"39\","
                + "\"logging.googleapis.com/sourceLocation\":"
                + "\\{\"file\":\"ReflectionUtils.java\","
                + "\"line\":\"\\d+\","
                + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\""
                + "\\},"
                + "\"message\":\"Hello, world!\","
                + "\"severity\":\"INFO\","
                + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}"
                + "\\}\n"),
        List.of(formattingResult));
  }

  static ExtLogRecord makeMassivelyStructuredRecord() {
    var logRecord = FormatterTest.makeSimpleRecord();
    logRecord.setParameters(
        new Object[] {
          (StructuredParameter)
              () -> {
                var b = JSON.createObjectBuilder();
                for (int i = 0; i < 10; i++) {
                  b.add("int-" + i, i);
                }
                for (int i = 10; i < 20; i++) {
                  b.add("double-" + i, (double) i);
                }
                for (int i = 20; i < 30; i++) {
                  b.add("boolean-" + i, i % 2 == 0);
                }
                for (int i = 30; i < 40; i++) {
                  b.add("string-" + i, String.valueOf(i));
                }
                return b;
              }
        });
    return logRecord;
  }

  @Test
  void nestedRecord() {
    var logRecord = makeNestedRecord();

    var formatter = new Formatter(List.of(), List.of());
    var formattingResult = formatter.format(logRecord);
    assertLinesMatch(
        List.of(
            "\\{"
                + "\"anObject\":\\{\"a\":\"b\\\\nc\\\\nd\\\\u0000\\\\u0001\\\\u0002e\"\\},"
                + "\"anArray\":\\[1,2,3\\],"
                + "\"anArrayOfObjects\":\\["
                + "\\{\"a\":1,\"b\":2\\},"
                + "\\{\"b\":2,\"c\":3\\},"
                + "\\{\"c\":3,\"d\":4\\}"
                + "\\],"
                + "\"logging.googleapis.com/sourceLocation\":"
                + "\\{\"file\":\"ReflectionUtils.java\","
                + "\"line\":\"\\d+\","
                + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\""
                + "\\},"
                + "\"message\":\"Hello, world!\","
                + "\"severity\":\"INFO\","
                + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}"
                + "\\}\n"),
        List.of(formattingResult));
  }

  static ExtLogRecord makeNestedRecord() {
    var logRecord = makeSimpleRecord();
    logRecord.setParameters(
        new Object[] {
          (StructuredParameter)
              () ->
                  JSON.createObjectBuilder()
                      .add("anObject", JSON.createObjectBuilder().add("a", "b\nc\nd\0\1\2e"))
                      .add("anArray", JSON.createArrayBuilder().add(1).add(2).add(3))
                      .add(
                          "anArrayOfObjects",
                          JSON.createArrayBuilder()
                              .add(JSON.createObjectBuilder().add("a", 1).add("b", 2))
                              .add(JSON.createObjectBuilder().add("b", 2).add("c", 3))
                              .add(JSON.createObjectBuilder().add("c", 3).add("d", 4)))
        });
    return logRecord;
  }
}
