/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

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

package org.apache.pekko.persistence.typed.scaladsl

import java.util.concurrent.atomic.AtomicInteger

import scala.util.Success
import scala.util.Try

import com.typesafe.config.ConfigFactory
import org.scalatest.wordspec.AnyWordSpecLike

import org.apache.pekko
import pekko.actor.testkit.typed.scaladsl._
import pekko.actor.typed.scaladsl.Behaviors
import pekko.persistence.testkit.PersistenceTestKitPlugin
import pekko.persistence.testkit.PersistenceTestKitSnapshotPlugin
import pekko.persistence.typed.DeleteEventsCompleted
import pekko.persistence.typed.EventSourcedSignal
import pekko.persistence.typed.PersistenceId

object EventSourcedBehaviorRetentionOnlyOneSnapshotSpec {
  private val config = ConfigFactory.parseString(s"""
    ${PersistenceTestKitSnapshotPlugin.PluginId} {
      only-one-snapshot = true
    }
    """).withFallback(PersistenceTestKitPlugin.config.withFallback(PersistenceTestKitSnapshotPlugin.config))
}

class EventSourcedBehaviorRetentionOnlyOneSnapshotSpec
    extends ScalaTestWithActorTestKit(EventSourcedBehaviorRetentionOnlyOneSnapshotSpec.config)
    with AnyWordSpecLike
    with LogCapturing {

  import EventSourcedBehaviorRetentionSpec._

  val pidCounter = new AtomicInteger(0)
  private def nextPid(): PersistenceId = PersistenceId.ofUniqueId(s"c${pidCounter.incrementAndGet()}")

  "EventSourcedBehavior with retention and only-one-snapshot" must {

    "snapshot every N sequence nrs" in {
      val pid = nextPid()
      val c = spawn(Behaviors.setup[Command](ctx =>
        counter(ctx, pid).withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 2))))

      val replyProbe = TestProbe[State]()

      c ! Increment
      c ! GetValue(replyProbe.ref)
      replyProbe.expectMessage(State(1, Vector(0)))
      c ! StopIt
      val watchProbe = TestProbe()
      watchProbe.expectTerminated(c)

      // no snapshot should have happened
      val probeC2 = TestProbe[(State, Event)]()
      val snapshotProbe = createTestProbe[WrappedSignal]()

      val c2 = spawn(
        Behaviors.setup[Command](ctx =>
          counter(ctx, pid, probe = Some(probeC2.ref), snapshotSignalProbe = Some(snapshotProbe.ref))
            .withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 2))))
      probeC2.expectMessage[(State, Event)]((State(0, Vector()), Incremented(1)))

      c2 ! Increment
      snapshotProbe.expectSnapshotCompleted(2)
      c2 ! StopIt
      watchProbe.expectTerminated(c2)

      val probeC3 = TestProbe[(State, Event)]()
      val c3 = spawn(Behaviors.setup[Command](ctx =>
        counter(ctx, pid, Some(probeC3.ref)).withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 2))))
      // this time it should have been snapshotted so no events to replay
      probeC3.expectNoMessage()
      c3 ! GetValue(replyProbe.ref)
      replyProbe.expectMessage(State(2, Vector(0, 1)))
    }

    "not delete snapshots" in {
      val pid = nextPid()
      val snapshotSignalProbe = TestProbe[WrappedSignal]()
      val deleteSnapshotSignalProbe = TestProbe[WrappedSignal]()
      val replyProbe = TestProbe[State]()

      val persistentActor = spawn(
        Behaviors.setup[Command](ctx =>
          counter(
            ctx,
            pid,
            snapshotSignalProbe = Some(snapshotSignalProbe.ref),
            deleteSnapshotSignalProbe = Some(deleteSnapshotSignalProbe.ref))
            .withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 3, keepNSnapshots = 2))))

      (1 to 10).foreach(_ => persistentActor ! Increment)
      persistentActor ! GetValue(replyProbe.ref)
      replyProbe.expectMessage(State(10, (0 until 10).toVector))
      snapshotSignalProbe.expectSnapshotCompleted(3)
      snapshotSignalProbe.expectSnapshotCompleted(6)
      snapshotSignalProbe.expectSnapshotCompleted(9)
      // this is the difference compared to EventSourcedBehaviorRetentionSpec
      deleteSnapshotSignalProbe.expectNoMessage()

      (1 to 10).foreach(_ => persistentActor ! Increment)
      persistentActor ! GetValue(replyProbe.ref)
      replyProbe.expectMessage(State(20, (0 until 20).toVector))
      snapshotSignalProbe.expectSnapshotCompleted(12)
      snapshotSignalProbe.expectSnapshotCompleted(15)
      snapshotSignalProbe.expectSnapshotCompleted(18)
      // this is the difference compared to EventSourcedBehaviorRetentionSpec
      deleteSnapshotSignalProbe.expectNoMessage()

      snapshotSignalProbe.expectNoMessage()
    }

    "optionally delete old events" in {
      val pid = nextPid()
      val snapshotSignalProbe = TestProbe[WrappedSignal]()
      val deleteSnapshotSignalProbe = TestProbe[WrappedSignal]()
      val eventProbe = TestProbe[Try[EventSourcedSignal]]()
      val replyProbe = TestProbe[State]()

      val persistentActor = spawn(
        Behaviors.setup[Command](ctx =>
          counter(
            ctx,
            pid,
            snapshotSignalProbe = Some(snapshotSignalProbe.ref),
            deleteSnapshotSignalProbe = Some(deleteSnapshotSignalProbe.ref),
            eventSignalProbe = Some(eventProbe.ref)).withRetention(
            // tests the Java API as well
            RetentionCriteria.snapshotEvery(numberOfEvents = 3).withDeleteEventsOnSnapshot)))

      (1 to 10).foreach(_ => persistentActor ! Increment)
      persistentActor ! GetValue(replyProbe.ref)
      replyProbe.expectMessage(State(10, (0 until 10).toVector))
      snapshotSignalProbe.expectSnapshotCompleted(3)
      snapshotSignalProbe.expectSnapshotCompleted(6)
      snapshotSignalProbe.expectSnapshotCompleted(9)

      eventProbe.expectMessageType[Success[DeleteEventsCompleted]].value.toSequenceNr shouldEqual 3
      eventProbe.expectMessageType[Success[DeleteEventsCompleted]].value.toSequenceNr shouldEqual 6
      eventProbe.expectMessageType[Success[DeleteEventsCompleted]].value.toSequenceNr shouldEqual 9
      deleteSnapshotSignalProbe.expectNoMessage()

      // one at a time since snapshotting+event-deletion switches to running state before deleting events so ordering
      // if sending many commands in one go is not deterministic
      persistentActor ! Increment // 11
      persistentActor ! Increment // 12
      snapshotSignalProbe.expectSnapshotCompleted(12)
      eventProbe.expectMessageType[Success[DeleteEventsCompleted]].value.toSequenceNr shouldEqual 12
      deleteSnapshotSignalProbe.expectNoMessage()

      persistentActor ! Increment // 13
      persistentActor ! Increment // 14
      persistentActor ! Increment // 15
      snapshotSignalProbe.expectSnapshotCompleted(15)
      eventProbe.expectMessageType[Success[DeleteEventsCompleted]].value.toSequenceNr shouldEqual 15
      deleteSnapshotSignalProbe.expectNoMessage()

      persistentActor ! Increment // 16
      persistentActor ! Increment // 17
      persistentActor ! Increment // 18
      snapshotSignalProbe.expectSnapshotCompleted(18)
      eventProbe.expectMessageType[Success[DeleteEventsCompleted]].value.toSequenceNr shouldEqual 18
      deleteSnapshotSignalProbe.expectNoMessage()

      eventProbe.expectNoMessage()
      snapshotSignalProbe.expectNoMessage()
    }

  }
}
