package com.github.stijndehaes.playprometheusfilters.filters
import akka.stream.Materializer
import com.github.stijndehaes.playprometheusfilters.metrics.RequestMetric
import io.prometheus.client.Collector
import play.api.Configuration
import play.api.mvc.{Filter, RequestHeader, Result}

import scala.concurrent.{ExecutionContext, Future}

/**
  * Generic filter implementation to add metrics for a request.
  * Subclasses only have to define the `metrics` property to apply metrics.
  *
  * {{{
  * @Singleton
  * class MyFilter @Inject()(registry: CollectorRegistry)(implicit mat: Materializer, ec: ExecutionContext) extends MetricsFilter {
  *
  *   override val metrics = List(
  *     LatencyOnlyRequestMetricsBuilder.build(registry, DefaultUnmatchedDefaults)
  *   )
  * }
  * }}}
  *
  * Metrics can be created by using a [[com.github.stijndehaes.playprometheusfilters.metrics.RequestMetricBuilder RequestMetricBuilder]].
  * The builder creates and configures the metrics for the class instance.
  *
  * See [[com.github.stijndehaes.playprometheusfilters.metrics.CounterRequestMetrics CounterRequestMetrics]] and
  * [[com.github.stijndehaes.playprometheusfilters.metrics.LatencyRequestMetrics LatencyRequestMetrics]] for some provided
  * builders.
  *
  * @param mat
  * @param ec
  */
abstract class MetricsFilter(configuration: Configuration)(implicit val mat: Materializer, ec: ExecutionContext) extends Filter {

  val metrics: List[RequestMetric[_, RequestHeader, Result]]

  val excludePaths = {
    import collection.JavaConverters._
    Option(configuration.underlying)
      .map(_.getStringList("play-prometheus-filters.exclude.paths"))
      .map(_.asScala.toSet)
      .map(_.map(_.r))
      .getOrElse(Set.empty)
  }

  def apply(nextFilter: RequestHeader => Future[Result])
           (requestHeader: RequestHeader): Future[Result] = {

    // check if current uri is excluded from metrics
    def urlIsExcluded = excludePaths.exists(_.findFirstMatchIn(requestHeader.uri).isDefined)

    val startTime = System.nanoTime

    nextFilter(requestHeader).map { implicit result =>
      implicit val rh = requestHeader

      if (!urlIsExcluded) {
        val endTime = System.nanoTime
        val requestTime = (endTime - startTime) / Collector.NANOSECONDS_PER_SECOND

        metrics.foreach(_.mark(requestTime))
      }

      result
    }
  }
}