{- HLINT ignore "Reduce duplication" -}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TypeApplications  #-}

module AWS.Lambda.RuntimeClientSpec where

import AWS.Lambda.HttpClient
import AWS.Lambda.RuntimeClient
import AWS.Lambda.TestHttpClient
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Logger
import Data.Aeson hiding (Error)
import Data.HashMap.Strict as MAP
import Data.Key
import Data.Text (Text)
import System.Environment
import Test.Hspec

spec :: Spec
spec =
  beforeAll_ clearTraceId $
  around_ withRuntimeEnvVars $
  describe "the runtime client" $ do
    it "sets aws xray trace ID environment variable"               testSetsTraceId
    it "returns an event ID obtained from an http response header" testReturnsEventId
    it "returns an event deserialized from the response body"      testReturnsEvent
    it "uses the correct url to retrieve the next event"           testGetUrl
    it "uses the correct url to post a response"                   testPostResponseUrl
    it "uses the correct url to post an error"                     testPostErrorUrl
    it "uses the correct url to post an init error"                testPostInitErrorUrl
    it "sends a response"                                          testPostResponseBody
    it "sends an error"                                            testPostErrorBody
    it "sends an initialization error"                             testPostInitErrorBody
  where
    clearTraceId = unsetEnv "_X_AMZN_TRACE_ID"

withRuntimeEnvVars :: IO () -> IO ()
withRuntimeEnvVars = bracket_ setEnvVars unsetEnvVars
  where
    envVars =
      MAP.fromList
        [ ("AWS_LAMBDA_RUNTIME_API", "example.com")
        ]
    setEnvVars   = mapWithKeyM_ setEnv envVars
    unsetEnvVars = mapM_ unsetEnv (keys envVars)

testSetsTraceId :: Expectation
testSetsTraceId = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  traceId <- liftIO $ lookupEnv "_X_AMZN_TRACE_ID"
  liftIO $ traceId `shouldBe` Just "67890"
  where
    runtimeClientWith' = runtimeClientWith @_ @Text @Text
    httpClient = defaultTestHttpClient @Text "Hello, world"

testReturnsEventId :: Expectation
testReturnsEventId = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  liftIO $ show eventID `shouldBe` "EventID \"12345\""
  where
    runtimeClientWith' = runtimeClientWith @_ @Text @Text
    httpClient = defaultTestHttpClient @Text "Hello, world"

testReturnsEvent :: Expectation
testReturnsEvent = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  liftIO $ eventBody `shouldBe` Right "Hello, world"
  where
    runtimeClientWith' = runtimeClientWith @_ @Text @Text
    httpClient = defaultTestHttpClient @Text "Hello, world"

withGetUrl :: HttpClient a -> String -> HttpClient a
withGetUrl (HttpClient get' post') url =
  HttpClient (\x -> shouldBe x url >> get' x) post'

testGetUrl :: Expectation
testGetUrl = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  void getNextEvent
  where
    runtimeClientWith' = runtimeClientWith @_ @Value @Value
    httpClient =
      defaultTestHttpClient Null
        `withGetUrl` "http://example.com/2018-06-01/runtime/invocation/next"

withPostUrl :: HttpClient a -> String -> HttpClient a
withPostUrl (HttpClient get' post') url = HttpClient get' (\x y -> shouldBe x url >> post' x y)

testPostResponseUrl :: Expectation
testPostResponseUrl = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  void $ postResponse eventID Null
  where
    runtimeClientWith' = runtimeClientWith @_ @Value @Value
    httpClient =
      defaultTestHttpClient Null
        `withPostUrl` "http://example.com/2018-06-01/runtime/invocation/12345/response"

testPostErrorUrl :: Expectation
testPostErrorUrl = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  void $ postError eventID $ Error "Test" "Test"
  where
    runtimeClientWith' = runtimeClientWith @_ @Value @Value
    httpClient =
      defaultTestHttpClient Null
        `withPostUrl` "http://example.com/2018-06-01/runtime/invocation/12345/error"

testPostInitErrorUrl :: Expectation
testPostInitErrorUrl = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  void . postInitError $ Error "Test" "Test"
  where
    runtimeClientWith' = runtimeClientWith @_ @Value @Value
    httpClient =
      defaultTestHttpClient Null
        `withPostUrl` "http://example.com/2018-06-01/runtime/init/error"

withPostBody :: (ToJSON b) => HttpClient a -> b -> HttpClient a
withPostBody (HttpClient get' post') b = HttpClient get' (\x y -> shouldBe y body' >> post' x y)
  where body' = toEncoding b

testPostResponseBody :: Expectation
testPostResponseBody = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  void $ postResponse eventID body'
  where
    runtimeClientWith' = runtimeClientWith @_ @Value @Text
    httpClient = defaultTestHttpClient Null `withPostBody` body'
    body' :: Text
    body' = "Hello, World"

testPostErrorBody :: Expectation
testPostErrorBody = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  void $ postError eventID error'
  where
    runtimeClientWith' = runtimeClientWith @_ @Value @Value
    httpClient = defaultTestHttpClient Null `withPostBody` error'
    error' = Error "Test" "Hello, World"

testPostInitErrorBody :: Expectation
testPostInitErrorBody = runNoLoggingT $ do
  RuntimeClient{..} <- runtimeClientWith' httpClient
  Event{..} <- getNextEvent
  void $ postInitError error'
  where
    runtimeClientWith' = runtimeClientWith @_ @Value @Value
    httpClient = defaultTestHttpClient Null `withPostBody` error'
    error' = Error "Test" "Hello, World"

