/*
 * 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) 2021 - 2023 Lightbend Inc. <https://www.lightbend.com>
 */

package org.apache.pekko.persistence.r2dbc

import org.apache.pekko
import pekko.Done
import pekko.actor.typed.scaladsl.ActorContext
import pekko.actor.typed.scaladsl.Behaviors
import pekko.actor.typed.{ ActorRef, Behavior }
import pekko.persistence.r2dbc.query.scaladsl.R2dbcReadJournal
import pekko.persistence.typed.PersistenceId
import pekko.persistence.typed.ReplicaId
import pekko.persistence.typed.ReplicationId
import pekko.persistence.typed.scaladsl.EventSourcedBehavior
import pekko.persistence.typed.scaladsl.ReplicatedEventSourcing
import pekko.persistence.typed.state.scaladsl.DurableStateBehavior

object TestActors {
  object Persister {

    import pekko.persistence.typed.scaladsl.Effect

    sealed trait Command
    final case class Persist(payload: Any) extends Command
    final case class PersistWithAck(payload: Any, replyTo: ActorRef[Done]) extends Command
    final case class PersistAll(payloads: List[Any]) extends Command
    final case class Ping(replyTo: ActorRef[Done]) extends Command
    final case class GetState(replyTo: ActorRef[String]) extends Command
    final case class GetSeqNr(replyTo: ActorRef[Long]) extends Command
    final case class Stop(replyTo: ActorRef[Done]) extends Command

    def apply(pid: String): Behavior[Command] =
      apply(PersistenceId.ofUniqueId(pid))

    def apply(pid: PersistenceId): Behavior[Command] = {
      apply(pid, "", "", Set.empty)
    }

    def apply(pid: PersistenceId, tags: Set[String]): Behavior[Command] = {
      apply(pid, "", "", tags)
    }

    def apply(
        pid: PersistenceId,
        journalPluginId: String,
        snapshotPluginId: String,
        tags: Set[String]): Behavior[Command] = {
      Behaviors.setup { context =>
        eventSourcedBehavior(pid, context)
          .withJournalPluginId(journalPluginId)
          .withSnapshotPluginId(snapshotPluginId)
          .snapshotWhen { case (_, event, _) =>
            event.toString.contains("snap")
          }
          .withTagger(_ => tags)
      }
    }

    def eventSourcedBehavior(
        pid: PersistenceId,
        context: ActorContext[Command]): EventSourcedBehavior[Command, Any, String] = {
      EventSourcedBehavior[Command, Any, String](
        persistenceId = pid,
        "",
        { (state, command) =>
          command match {
            case command: Persist =>
              context.log.debug(
                "Persist [{}], pid [{}], seqNr [{}]": String,
                command.payload.toString,
                pid.id: Object,
                EventSourcedBehavior.lastSequenceNumber(context) + 1: java.lang.Long)
              Effect.persist(command.payload)
            case command: PersistWithAck =>
              context.log.debug(
                "Persist [{}], pid [{}], seqNr [{}]",
                command.payload.toString,
                pid.id,
                EventSourcedBehavior.lastSequenceNumber(context) + 1: java.lang.Long)
              Effect.persist(command.payload).thenRun(_ => command.replyTo ! Done)
            case command: PersistAll =>
              if (context.log.isDebugEnabled)
                context.log.debug(
                  "PersistAll [{}], pid [{}], seqNr [{}]",
                  command.payloads.mkString(","),
                  pid.id,
                  EventSourcedBehavior.lastSequenceNumber(context) + 1: java.lang.Long)
              Effect.persist(command.payloads)
            case Ping(replyTo) =>
              replyTo ! Done
              Effect.none
            case GetState(replyTo) =>
              replyTo ! state
              Effect.none
            case GetSeqNr(replyTo) =>
              replyTo ! EventSourcedBehavior.lastSequenceNumber(context)
              Effect.none
            case Stop(replyTo) =>
              replyTo ! Done
              Effect.stop()
          }
        },
        (state, event) => if (state == "") event.toString else s"$state|$event")
    }
  }

  object DurableStatePersister {
    import pekko.persistence.typed.state.scaladsl.Effect

    sealed trait Command
    final case class Persist(payload: Any) extends Command
    final case class PersistWithAck(payload: Any, replyTo: ActorRef[Done]) extends Command
    final case class DeleteWithAck(replyTo: ActorRef[Done]) extends Command
    final case class Ping(replyTo: ActorRef[Done]) extends Command
    final case class GetState(replyTo: ActorRef[Any]) extends Command
    final case class GetRevision(replyTo: ActorRef[Long]) extends Command
    final case class Stop(replyTo: ActorRef[Done]) extends Command

    def apply(pid: String): Behavior[Command] =
      apply(PersistenceId.ofUniqueId(pid))

    def apply(pid: PersistenceId): Behavior[Command] = {
      Behaviors.setup { context =>
        DurableStateBehavior[Command, Any](
          persistenceId = pid,
          "",
          { (state, command) =>
            command match {
              case command: Persist =>
                context.log.debug(
                  "Persist [{}], pid [{}], seqNr [{}]",
                  command.payload.toString,
                  pid.id: Object,
                  (DurableStateBehavior.lastSequenceNumber(context) + 1: java.lang.Long): Object)
                Effect.persist(command.payload)
              case command: PersistWithAck =>
                context.log.debug(
                  "Persist [{}], pid [{}], seqNr [{}]",
                  command.payload.toString,
                  pid.id: Object,
                  (DurableStateBehavior.lastSequenceNumber(context) + 1: java.lang.Long): Object)
                Effect.persist(command.payload).thenRun(_ => command.replyTo ! Done)
              case command: DeleteWithAck =>
                context.log
                  .debug("Delete pid [{}], seqNr [{}]", pid.id, DurableStateBehavior.lastSequenceNumber(context) + 1)
                Effect.delete[Any]().thenRun(_ => command.replyTo ! Done)
              case GetState(replyTo) =>
                replyTo ! state
                Effect.none
              case GetRevision(replyTo) =>
                replyTo ! DurableStateBehavior.lastSequenceNumber(context)
                Effect.none
              case Ping(replyTo) =>
                replyTo ! Done
                Effect.none
              case Stop(replyTo) =>
                replyTo ! Done
                Effect.stop()
            }
          })
      }
    }
  }

  def replicatedEventSourcedPersister(entityType: String, entityId: String): Behavior[Persister.Command] = {
    Behaviors.setup[Persister.Command] { context =>
      ReplicatedEventSourcing.commonJournalConfig(
        ReplicationId(entityType, entityId, ReplicaId("dc-1")),
        Set(ReplicaId("dc-1")),
        R2dbcReadJournal.Identifier) { replicationContext =>
        Persister.eventSourcedBehavior(PersistenceId(entityType, entityId), context)
      }
    }
  }
}
