-- |
-- Smith HTTP interactions.
--
-- Should be used as a qualified import from top-level module:
--
-- > import qualified Smith.Client as Smith
--
-- Example:
-- > Smith.runRequest configuration Smith.userinfo
--
{-# LANGUAGE OverloadedStrings #-}
module Smith.Client.Network (
    runRequest
  , runRequestT
  ) where

import           Control.Monad.IO.Class (MonadIO (..))
import           Control.Monad.Trans.Bifunctor (BifunctorTrans (..))
import           Control.Monad.Trans.Except (ExceptT (..), runExceptT)

import           Data.Bifunctor (Bifunctor (..))
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text

import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Types as  HTTP
import qualified Network.OAuth2.JWT.Client as OAuth2

import           Smith.Client.Config
import           Smith.Client.Error
import           Smith.Client.Request (Request (..), Requester (..))
import           Smith.Client.Response (Responder (..))

-- |
-- Takes Smith runtime data, and an API request definition and actually
-- runs the request. Results are in IO and the error cases handled
-- explicitly.
--
runRequest :: Smith -> Request a -> IO (Either SmithError a)
runRequest smith =
  runExceptT .  runRequestT smith

-- |
-- Takes Smith runtime data, and an API request definition and actually
-- runs the request. Results are embeded in ExceptT for convenience.
--
runRequestT :: Smith -> Request a -> ExceptT SmithError IO a
runRequestT (Smith endpoint manager oauth2) (Request method path responder requester) = do
  req <- ExceptT . pure . first (SmithUrlParseError . Text.pack . show) $
    HTTP.parseRequest (Text.unpack $ mconcat [getSmithEndpoint endpoint, "/", path])

  token <- firstT SmithAuthenticationError . ExceptT $
    OAuth2.grant oauth2

  res <- liftIO $ flip HTTP.httpLbs manager . runRequester requester $
    req {
        HTTP.method = HTTP.renderStdMethod method
      , HTTP.requestHeaders = [
            ("Authorization", mconcat ["Bearer ", Text.encodeUtf8 . OAuth2.renderAccessToken $ token])
          ]
      }

  ExceptT . pure $
    runResponder responder res