package akka.persistence.kafka.journal.circe

import akka.persistence.kafka.journal.EventSerializer.PersistentRepresentation
import akka.persistence.kafka.journal._
import akka.persistence.kafka.journal.circe.KafkaJournalCirce._
import cats.effect.{IO, Resource}
import cats.implicits._
import com.evolutiongaming.catshelper.MonadThrowable
import com.evolutiongaming.kafka.journal._
import com.evolutiongaming.kafka.journal.circe.Codecs._
import com.evolutiongaming.kafka.journal.circe.FromCirceResult
import com.evolutiongaming.kafka.journal.circe.Instances._
import com.evolutiongaming.kafka.journal.util.Fail
import com.typesafe.config.Config
import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._

class KafkaJournalCirce(config: Config) extends KafkaJournal(config) {

  override def adapterIO: Resource[IO, JournalAdapter[IO]] = {
    for {
      serializer       <- circeEventSerializer
      journalReadWrite <- Resource.liftF(circeJournalReadWrite)
      adapter          <- adapterIO(serializer, journalReadWrite)
    } yield adapter
  }

  def circeEventSerializer: Resource[IO, EventSerializer[IO, Json]] = {
    val serializer = JsonEventSerializer.of[IO].pure[IO]
    Resource.liftF(serializer)
  }

  def circeJournalReadWrite: IO[JournalReadWrite[IO, Json]] =
    JournalReadWrite.of[IO, Json].pure[IO]
}

object KafkaJournalCirce {

  implicit def persistentJsonEncoder[A : Encoder]: Encoder[PersistentJson[A]] = deriveEncoder
  implicit def persistentJsonDecoder[A : Decoder]: Decoder[PersistentJson[A]] = deriveDecoder

  object JsonEventSerializer {

    def of[F[_] : MonadThrowable : FromCirceResult]: EventSerializer[F, Json] = {

      def toEventPayload(repr: PersistentRepresentation): F[Json] = {

        def json(json: Json, payloadType: Option[PayloadType.TextOrJson] = None) = {
          val persistent = PersistentJson(
            manifest = repr.manifest,
            writerUuid = repr.writerUuid,
            payloadType = payloadType,
            payload = json
          )
          persistent.asJson.dropNullValues
        }

        repr.payload match {
          case payload: Json => json(payload).pure[F]
          case payload: String => json(Json.fromString(payload), PayloadType.Text.some).pure[F]
          case other => Fail.lift[F].fail(s"Event.payload is not supported, payload: $other")
        }
      }

      def fromEventPayload(json: Json): F[PersistentRepresentation] = {
        val fromCirceResult = FromCirceResult.summon[F]

        for {
          persistentJson <- fromCirceResult(json.as[PersistentJson[Json]])
          payloadType = persistentJson.payloadType getOrElse PayloadType.Json
          payload = persistentJson.payload
          anyRef <- payloadType match {
            case PayloadType.Text => fromCirceResult(payload.as[String]).widen[AnyRef]
            case PayloadType.Json => payload.pure[F].widen[AnyRef]
          }
        } yield {
          PersistentRepresentation(
            payload = anyRef,
            manifest = persistentJson.manifest,
            writerUuid = persistentJson.writerUuid
          )
        }
      }

      EventSerializer(toEventPayload, fromEventPayload)
    }
  }

}