/*
 * =========================================================================================
 * Copyright © 2013-2018 the kamon project <http://kamon.io/>
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language governing permissions
 * and limitations under the License.
 * =========================================================================================
 */

package kamon.http4s

import cats.effect._
import kamon.testkit.InstrumentInspection
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import org.http4s.server.Server
import org.http4s.server.blaze.BlazeServerBuilder
import org.scalatest.concurrent.Eventually
import org.scalatest.time.SpanSugar
import org.scalatest.{Matchers, OptionValues, WordSpec}
import cats.implicits._
import kamon.http4s.middleware.server.KamonSupport
import kamon.instrumentation.http.HttpServerMetrics
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.client.Client

import scala.concurrent.ExecutionContext
import org.http4s.implicits._

class HttpMetricsSpec extends WordSpec
  with Matchers
  with Eventually
  with SpanSugar
  with InstrumentInspection.Syntax
  with OptionValues
 {

  implicit val contextShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)

  val srv =
    BlazeServerBuilder[IO]
      .bindLocal(43567)
      .withHttpApp(KamonSupport(HttpRoutes.of[IO] {
        case GET -> Root / "tracing" / "ok" =>  Ok("ok")
        case GET -> Root / "tracing" / "not-found"  => NotFound("not-found")
        case GET -> Root / "tracing" / "error"  => InternalServerError("This page will generate an error!")
      }, "/127.0.0.1", 43567).orNotFound)
      .resource

  val client =
    BlazeClientBuilder[IO](ExecutionContext.global).withMaxTotalConnections(10).resource

   val metrics =
    Resource.liftF(IO(HttpServerMetrics.of("http4s.server", "/127.0.0.1", 43567)))


  def withServerAndClient[A](f: (Server[IO], Client[IO], HttpServerMetrics.HttpServerInstruments) => IO[A]): A =
   (srv, client, metrics).tupled.use(f.tupled).unsafeRunSync()

  private def get[F[_]: ConcurrentEffect](path: String)(server: Server[F], client: Client[F]): F[String] = {
    client.expect[String](s"http://127.0.0.1:${server.address.getPort}$path")
  }

  "The HttpMetrics" should {

    "track the total of active requests" in withServerAndClient { (server, client, serverMetrics) =>

      val requests = List
        .fill(100) {
          get("/tracing/ok")(server, client)
        }.parSequence_

      val test = IO {
        serverMetrics.activeRequests.distribution().max should be > 1L
        serverMetrics.activeRequests.distribution().min shouldBe 0L
      }
      requests *> test
    }

    "track the response time with status code 2xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/ok")(server, client)).sequence_

      val test = IO(serverMetrics.requestsSuccessful.value should be >= 0L)

      requests *> test
    }

    "track the response time with status code 4xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/not-found")(server, client).attempt).sequence_

      val test = IO(serverMetrics.requestsClientError.value should be >= 0L)

      requests *> test
    }

    "track the response time with status code 5xx" in withServerAndClient { (server, client, serverMetrics) =>
      val requests: IO[Unit] = List.fill(100)(get("/tracing/error")(server, client).attempt).sequence_

      val test = IO(serverMetrics.requestsServerError.value should be >= 0L)

      requests *> test
    }
  }
}