{-# language OverloadedStrings, DeriveGeneric #-}
module Network.Goggles.Auth.OAuth2
  (
    requestOAuth2Token
  , OAuth2Token(..)
  , OAuth2TokenUTC(..)
  , mkOAuth2TokenUTC
                                )
  where


import Network.HTTP.Req
import Control.Monad.Catch
import Control.Monad.IO.Class (MonadIO(..), liftIO)
import GHC.Generics
import qualified Data.Text as T
-- import qualified Data.Text.Encoding as T (encodeUtf8)
-- import qualified Data.ByteString.Char8            as B8
-- import qualified Data.ByteString.Lazy as LB
import qualified Data.Aeson as J
import Data.Aeson ((.:), (.:?))
import Network.Utils.HTTP
import Data.Time

import Network.Goggles.Control.Exceptions



data OAuth2TokenUTC = OAuth2TokenUTC {
    oauTokenExpiry :: UTCTime
  , oauTokenString :: T.Text
  , oauTokenType :: Maybe T.Text
                                     } deriving (Eq, Show)

mkOAuth2TokenUTC :: (MonadIO m, Integral t) => t -> OAuth2Token -> m OAuth2TokenUTC
mkOAuth2TokenUTC delta oa2 = liftIO $
  OAuth2TokenUTC <$>
  tokenExpiryTime delta (oaTokenExpirySeconds oa2) <*>
  pure (oaTokenString oa2) <*>
  pure (oaTokenType oa2)


data OAuth2Token = OAuth2Token {
    oaTokenExpirySeconds :: Int
  , oaTokenString :: T.Text
  , oaTokenType :: Maybe T.Text
  } deriving (Eq, Show, Generic)

instance J.FromJSON OAuth2Token where
  parseJSON = J.withObject "OAuth2Token" $ \js -> OAuth2Token
    <$> js .: "expires_in"
    <*> js .: "access_token"
    <*> js .:? "token_type"


-- | send a POST request over HTTPS to a given URI that will return a OAuth2Token
requestOAuth2Token
  :: (MonadHttp m, MonadThrow m) =>
     Url scheme         -- ^ Request URI
  -> [(T.Text, T.Text)] -- ^ parameter list as a list of (key, value) pairs
  -> Option scheme      -- ^ request options (e.g. headers)     
  -> m OAuth2Token
requestOAuth2Token uri args httpOpts = do
  let payload = encodeHttpParametersLB args
  r <- req POST
         uri
         (ReqBodyLbs payload)
         lbsResponse
         httpOpts
  maybe (throwM $ NotFound "Something went wrong with the token request") pure (J.decode (responseBody r) :: Maybe OAuth2Token)




-- | Returns the UTCTime (absolute) related to a delay in seconds from the time this function is executed. We need this helper function because the "expires_in" field in the OAuth2 response means "seconds from now".
tokenExpiryTime ::
  (Integral t, Integral t1) =>
     t          -- ^ Correction for system delays (e.g. processing and network time). Positive
  -> t1         -- ^ "seconds from now" parameter. Positive 
  -> IO UTCTime 
tokenExpiryTime delta s = do
  tnow <- getCurrentTime
  let sd = fromIntegral s
      sdelta = fromIntegral delta
  return $ addUTCTime (sd - sdelta) tnow