{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Monad (replicateM_, liftM)
import Control.Concurrent (threadDelay)
import Control.Monad.IO.Class (liftIO)
import Data.Int (Int64)

import Test.Tasty
import Test.Tasty.HUnit
import qualified Test.Tasty.QuickCheck as QC
import qualified Test.QuickCheck.Monadic as QCM
import qualified Data.ByteString as BS

import Web.Scotty (scottyApp, middleware, get, html, raise)

import Network.Wai (Application)
import qualified Network.Wai.Test as WT

import System.Metrics
import qualified System.Metrics.Counter as Counter
import qualified System.Metrics.Distribution as Distribution

import Network.Wai.Metrics

-- Send a GET request to a WAI Application
httpGet :: BS.ByteString -> Application -> IO WT.SResponse
httpGet path =  WT.runSession (WT.srequest (WT.SRequest req ""))
  where req = WT.setRawPathInfo WT.defaultRequest path

between :: Ord a => a -> a -> a -> Bool
between low high x = low <= x && x <= high

-- Return the state of Wai Metrics after running n times
-- an action over a fresh scotty server
testServer :: (Application -> IO a) -> Int -> IO WaiMetrics
testServer action times = do
  store <- newStore
  waiMetrics <- registerWaiMetrics store
  app <- scottyApp $ do
    middleware (metrics waiMetrics)
    get "/" $ html "Ping"
    get "/error" $ raise "error"
    get "/wait" $ liftIO (threadDelay 100000) >> html "Ping"
  replicateM_ times (action app)
  return waiMetrics

-- Return the number of requests after running n times
-- an action over a fresh scotty server
readRequestCounter :: (Application -> IO a) -> Int -> IO Int64
readRequestCounter action times = do
  waiMetrics <- testServer action times
  Counter.read (requestCounter waiMetrics)

-- Return the number of server errors after running n times
-- an action over a fresh scotty server
readErrorCounter :: (Application -> IO a) -> Int -> IO Int64
readErrorCounter action times = do
  waiMetrics <- testServer action times
  Counter.read (serverErrorCounter waiMetrics)

-- Return the response time distribution after running n times
-- an action over a fresh scotty server
readResponseTime :: (Application -> IO a) -> Int -> IO Distribution.Stats
readResponseTime action times = do
  waiMetrics <- testServer action times
  Distribution.read (latencyDistribution waiMetrics)

testRequestCounterScotty :: QC.NonNegative Int -> QC.Property
testRequestCounterScotty (QC.NonNegative n) =  QCM.monadicIO test
  where test = do c <- QCM.run $ readRequestCounter (httpGet "") n
                  QCM.assert $ fromIntegral c == n

testErrorCounterScotty :: QC.NonNegative Int -> QC.Property
testErrorCounterScotty (QC.NonNegative n) =  QCM.monadicIO test
  where test = do c <- QCM.run $ readErrorCounter (httpGet "/error") n
                  QCM.assert $ fromIntegral c == n

testResponseTimeScotty :: IO()
testResponseTimeScotty =  do s <- readResponseTime (httpGet "/wait") 3
                             assert $ between 0.1 0.11 (Distribution.mean s)

tests :: TestTree
tests = testGroup "Metrics tests" [
    QC.testProperty "Request counter must be incremented in middleware" testRequestCounterScotty
  , QC.testProperty "Error counter must be incremented in middleware" testErrorCounterScotty
  , testCase "Request time average must be measured in middleware" testResponseTimeScotty]

main :: IO()
main = defaultMain tests
