package ch.epfl.bluebrain.nexus.cli.influx

import cats.effect.{Blocker, IO, Resource}
import ch.epfl.bluebrain.nexus.cli.{AbstractCliSpec, Console}
import ch.epfl.bluebrain.nexus.cli.clients.InfluxClient
import ch.epfl.bluebrain.nexus.cli.config.AppConfig
import ch.epfl.bluebrain.nexus.cli.influx.InfluxDocker.InfluxHostConfig
import izumi.distage.model.definition.{Module, ModuleDef}
import org.http4s.client.blaze.BlazeClientBuilder

import scala.concurrent.duration._

class AbstractInfluxSpec extends AbstractCliSpec {

  override protected def defaultModules: Module = {
    super.defaultModules ++ new InfluxDocker.Module[IO]
  }

  override def testModule: ModuleDef =
    new ModuleDef {
      make[AppConfig].fromEffect { host: InfluxHostConfig =>
        copyConfigs.flatMap {
          case (envFile, _, influxFile) =>
            AppConfig.load[IO](Some(envFile), influxConfigFile = Some(influxFile)).flatMap {
              case Left(value)  => IO.raiseError(value)
              case Right(value) =>
                val influxOffsetFile = influxFile.getParent.resolve("influx.offset")
                val cfg              = value.copy(influx =
                  value.influx.copy(
                    endpoint = host.endpoint,
                    offsetFile = influxOffsetFile,
                    offsetSaveInterval = 100.milliseconds
                  )
                )
                IO.pure(cfg)
            }
        }
      }
      make[InfluxClient[IO]].fromResource {
        (_: InfluxDocker.Container, cfg: AppConfig, blocker: Blocker, console: Console[IO]) =>
          BlazeClientBuilder[IO](blocker.blockingContext).resource.flatMap { client =>
            val influxClient = InfluxClient(client, cfg, console)
            waitForInfluxReady(influxClient).map(_ => influxClient)
          }
      }
    }

  private def waitForInfluxReady(
      client: InfluxClient[IO],
      maxDelay: FiniteDuration = 90.seconds
  ): Resource[IO, Unit] = {
    import retry.CatsEffect._
    import retry.RetryPolicies._
    import retry._
    val policy   = limitRetriesByCumulativeDelay[IO](maxDelay, constantDelay(5.second))
    val healthIO = retryingOnAllErrors(
      policy = policy,
      onError = (_: Throwable, _) => IO.delay(println("Influx Container not ready, retrying..."))
    ) {
      client.health.map {
        case Left(err) => throw err
        case Right(_)  => ()
      }
    }
    Resource.liftF(healthIO)
  }
}