{-# LANGUAGE DeriveAnyClass    #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE OverloadedStrings #-}

module TestFeature
  ( testFeature
  ) where

import qualified Data.Text.Lazy             as LT (toStrict)
import           Data.Text.Lazy.Encoding    (decodeUtf8)

import           Data.Aeson                 (FromJSON, Value, decode, encode)
import           Data.ByteString.Lazy.Char8 (ByteString)
import qualified Data.ByteString.Lazy.Char8 as LB (unpack)
import           Data.Morpheus.Types        (GQLRequest (..), GQLResponse (..))
import           Data.Semigroup             ((<>))
import           Data.Text                  (Text, unpack)
import qualified Data.Text                  as T (concat)
import           GHC.Generics
import           Lib                        (getCases, getGQLBody, getResponseBody, maybeVariables)
import           Test.Tasty                 (TestTree, testGroup)
import           Test.Tasty.HUnit           (assertFailure, testCase)

packGQLRequest :: ByteString -> Maybe Value -> GQLRequest
packGQLRequest queryBS variables =
  GQLRequest {operationName = Nothing, query = LT.toStrict $ decodeUtf8 queryBS, variables}

data Case = Case
  { path        :: Text
  , description :: String
  } deriving (Generic, FromJSON)

testFeature :: (GQLRequest -> IO GQLResponse) -> Text -> IO TestTree
testFeature api dir = do
  cases' <- getCases (unpack dir)
  test' <- sequence $ testByFiles api <$> map (\x -> x {path = T.concat [dir, "/", path x]}) cases'
  return $ testGroup (unpack dir) test'

testByFiles :: (GQLRequest -> IO GQLResponse) -> Case -> IO TestTree
testByFiles testApi Case {path, description} = do
  testCaseQuery <- getGQLBody path
  testCaseVariables <- maybeVariables path
  expectedResponse <- getResponseBody path
  actualResponse <- encode <$> testApi (packGQLRequest testCaseQuery testCaseVariables)
  case decode actualResponse of
    Nothing -> assertFailure "Bad Response"
    Just response -> return $ testCase (unpack path ++ " | " ++ description) $ customTest expectedResponse response
      where customTest expected value
              | expected == value = return ()
              | otherwise =
                assertFailure $ LB.unpack $ "expected: \n " <> encode expected <> " \n but got: \n " <> actualResponse