/*
 * Copyright (C) 2009-2021 Lightbend Inc. <https://www.lightbend.com>
 */

package akka.persistence.typed.javadsl;

import akka.actor.testkit.typed.javadsl.LogCapturing;
import akka.actor.testkit.typed.javadsl.LoggingTestKit;
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
import akka.actor.testkit.typed.javadsl.TestProbe;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import akka.persistence.typed.PersistenceId;
import akka.persistence.typed.RecoveryCompleted;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.scalatestplus.junit.JUnitSuite;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

public class LoggerSourceTest extends JUnitSuite {

  private static final Config config =
      ConfigFactory.parseString(
          "akka.persistence.journal.plugin = \"akka.persistence.journal.inmem\" \n"
              + "akka.persistence.journal.inmem.test-serialization = on \n");

  @ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource(config);

  @Rule public final LogCapturing logCapturing = new LogCapturing();

  private static final AtomicInteger idCounter = new AtomicInteger(0);

  public static PersistenceId nextId() {
    return PersistenceId.ofUniqueId("" + idCounter.incrementAndGet());
  }

  static class LoggingBehavior extends EventSourcedBehavior<String, String, String> {

    private final ActorContext<String> ctx;

    public LoggingBehavior(PersistenceId persistenceId, ActorContext<String> ctx) {
      super(persistenceId);
      this.ctx = ctx;
    }

    @Override
    public String emptyState() {
      return "";
    }

    @Override
    public CommandHandler<String, String, String> commandHandler() {
      return newCommandHandlerBuilder()
          .forAnyState()
          .onCommand(str -> str.equals("stop"), (cmd, state) -> Effect().stop())
          .onAnyCommand(
              (cmd, state) -> {
                ctx.getLog().info("command-received");
                return Effect().persist("evt");
              });
    }

    @Override
    public EventHandler<String, String> eventHandler() {
      return newEventHandlerBuilder()
          .forAnyState()
          .onAnyEvent(
              (evt, state) -> {
                ctx.getLog().info("event-received");
                return evt + state;
              });
    }

    @Override
    public SignalHandler<String> signalHandler() {
      return newSignalHandlerBuilder()
          .onSignal(
              RecoveryCompleted.instance(),
              (signal) -> {
                ctx.getLog().info("recovery-completed");
              })
          .build();
    }
  }

  public Behavior<String> behavior =
      Behaviors.setup(
          (ctx) -> {
            ctx.getLog().info("setting-up-behavior");
            return new LoggingBehavior(nextId(), ctx);
          });

  @Test
  public void verifyLogging() {
    Map<String, String> expectedMdc1 = new HashMap<>();
    expectedMdc1.put("persistenceId", "1");
    expectedMdc1.put("persistencePhase", "replay-evt");

    ActorRef<String> ref =
        LoggingTestKit.info("recovery-completed")
            .withMdc(expectedMdc1)
            .withCustom(event -> event.loggerName().equals(LoggingBehavior.class.getName()))
            .expect(
                testKit.system(),
                () -> {
                  return testKit.spawn(behavior);
                });

    // MDC persistenceId and persistencePhase for the "command-received" not included in the
    // "command-received" logging, because that is via ActorContext.log directly and
    // EventSourcedBehaviorImpl
    // isn't involved.

    LoggingTestKit.info("command-received")
        .withCustom(
            event -> {
              return event.loggerName().equals(LoggingBehavior.class.getName())
                  && event.getMdc().get("akkaSource").equals(ref.path().toString());
            })
        .expect(
            testKit.system(),
            () -> {
              ref.tell("command");
              return null;
            });

    Map<String, String> expectedMdc3 = new HashMap<>();
    expectedMdc3.put("persistenceId", "1");
    expectedMdc3.put("persistencePhase", "running-cmd");

    LoggingTestKit.info("event-received")
        .withMdc(expectedMdc3)
        .withCustom(
            event -> {
              return event.loggerName().equals(LoggingBehavior.class.getName())
                  && event.getMdc().get("akkaSource").equals(ref.path().toString());
            })
        .expect(
            testKit.system(),
            () -> {
              ref.tell("command");
              return null;
            });

    TestProbe<Object> probe = testKit.createTestProbe();
    ref.tell("stop");
    probe.expectTerminated(ref);
  }
}
