package com.evolutiongaming.kafka.journal.util

import cats.effect._
import cats.effect.concurrent.{Deferred, Ref}
import cats.implicits._
import cats.{Applicative, Foldable}
import com.evolutiongaming.kafka.journal.IOSuite._
import org.scalatest.funsuite.AsyncFunSuite
import org.scalatest.matchers.should.Matchers

import scala.util.control.NoStackTrace

class ResourceRegistrySpec extends AsyncFunSuite with Matchers {

  val error: Throwable = new RuntimeException with NoStackTrace
  for {
    exitCase <- List(
      ExitCase.complete,
      ExitCase.error(error),
      ExitCase.canceled)
  } yield {

    test(s"ResRegistry releases resources, exitCase: $exitCase") {
      val result = exitCase match {
        case ExitCase.Completed    => testError(none)
        case ExitCase.Canceled     => testCancel
        case ExitCase.Error(error) => testError(error.some)
      }
      result.run()
    }
  }

  private def testError(error: Option[Throwable]) = {
    val n = 3

    def logic(release: IO[Unit]) = {
      ResourceRegistry.of[IO].use { registry =>
        val resource = Resource.make(().pure[IO]) { _ => release }
        val fa = registry.allocate(resource)
        implicit val monoidUnit = Applicative.monoid[IO, Unit]
        for {
          _ <- Foldable[List].fold(List.fill(n)(fa))
          _ <- error.fold(().pure[IO])(_.raiseError[IO, Unit])
        } yield {}
      }
    }

    for {
      ref      <- Ref.of[IO, Int](0)
      fa        = logic(ref.update(_ + 1))
      result   <- fa.redeem(_.some, _ => none)
      releases <- ref.get
    } yield {
      result shouldEqual result
      releases shouldEqual n
    }
  }

  private def testCancel = {
    for {
      released <- Ref.of[IO, Int](0)
      started  <- Deferred[IO, Unit]
      fiber    <- Concurrent[IO].start {
        ResourceRegistry.of[IO].use { registry =>
          val resource = Resource.make(().pure[IO]) { _ => released.update(_ + 1) }
          for {
            _ <- registry.allocate(resource)
            _ <- started.complete(())
            _ <- IO.never.as(())
          } yield {}
        }
      }
      _        <- started.get
      _        <- fiber.cancel
      released <- released.get
    } yield {
      released shouldEqual 1
    }
  }
}