package io.odin.loggers

import cats.effect.Resource
import cats.effect.concurrent.Ref
import cats.instances.list._
import cats.syntax.all._
import io.odin.{Level, Logger, LoggerMessage, OdinSpec}
import monix.catnap.ConcurrentQueue
import monix.eval.Task
import monix.execution.schedulers.TestScheduler
import io.odin.syntax._

import scala.concurrent.duration._

class AsyncLoggerSpec extends OdinSpec {
  implicit private val scheduler: TestScheduler = TestScheduler()

  case class RefLogger(ref: Ref[Task, List[LoggerMessage]]) extends DefaultLogger[Task] {
    def log(msg: LoggerMessage): Task[Unit] = Task.raiseError(new IllegalStateException("Async should always batch"))

    override def log(msgs: List[LoggerMessage]): Task[Unit] = {
      ref.update(_ ::: msgs)
    }
  }

  it should "push logs down the chain" in {
    forAll { msgs: List[LoggerMessage] =>
      (for {
        ref <- Resource.liftF(Ref.of[Task, List[LoggerMessage]](List.empty))
        logger <- RefLogger(ref).withMinimalLevel(Level.Trace).withAsync()
        _ <- Resource.liftF(msgs.traverse(logger.log))
        _ = scheduler.tick(10.millis)
        reported <- Resource.liftF(ref.get)
      } yield {
        reported shouldBe msgs
      }).use(Task(_)).runSyncUnsafe()
    }
  }

  it should "push logs to the queue" in {
    forAll { msgs: List[LoggerMessage] =>
      (for {
        queue <- ConcurrentQueue.unbounded[Task, LoggerMessage]()
        logger = AsyncLogger(queue, 1.millis, Logger.noop[Task])
        _ <- msgs.traverse(logger.log)
        reported <- queue.drain(0, Int.MaxValue)
      } yield {
        reported shouldBe msgs
      }).runSyncUnsafe()
    }
  }

  it should "ignore errors in underlying logger" in {
    val errorLogger = new DefaultLogger[Task] {
      def log(msg: LoggerMessage): Task[Unit] = Task.raiseError(new Error)
    }
    forAll { msgs: List[LoggerMessage] =>
      (for {
        queue <- ConcurrentQueue.unbounded[Task, LoggerMessage]()
        logger = AsyncLogger(queue, 1.millis, errorLogger)
        _ <- logger.log(msgs)
        result <- logger.drain
      } yield {
        result shouldBe (())
      }).runSyncUnsafe()
    }
  }
}