{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE QuasiQuotes         #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Security.AccessTokenProvider.Internal.Providers.Common
  ( tryNewProvider
  , tryEnvDeserialization
  ) where

import           Control.Exception.Safe
import           Control.Lens
import           Control.Monad.IO.Class
import           Control.Monad.Trans.Class
import           Control.Monad.Trans.Maybe
import           Data.Aeson
import           Data.Aeson.Lens
import           Data.Format
import           Data.List.NonEmpty                          (NonEmpty)
import qualified Data.List.NonEmpty                          as NonEmpty
import           Data.Text                                   (Text)
import qualified Data.Text.Encoding                          as Text
import           Katip

import           Security.AccessTokenProvider.Internal.Types
import           Security.AccessTokenProvider.Internal.Util

tryNewProvider
  :: (MonadIO m, MonadThrow m, KatipContext m)
  => AccessTokenName
  -> m (Maybe envConf)
  -> (envConf -> m conf)
  -> (AccessTokenName -> conf -> m (Maybe (AccessTokenProvider m t)))
  -> m (Maybe (AccessTokenProvider m t))
tryNewProvider tokenName makeEnvConf makeConf providerBuilder = do
  maybeEnvConf <- makeEnvConf
  case maybeEnvConf of
    Just envConf -> do
      conf <- makeConf envConf
      providerBuilder tokenName conf
    Nothing ->
      pure Nothing

atpConfVarName :: Text
atpConfVarName = "ATP_CONF"

tryEnvDeserialization
  :: ( MonadThrow m
     , MonadEnvironment m
     , FromJSON a
     , KatipContext m )
  => NonEmpty Text
  -> m (Maybe a)
tryEnvDeserialization providers = do
  maybeConf <- runMaybeT $ do
    envVal <- MaybeT $ environmentLookup atpConfVarName
    jsonVal :: Value <- lift $ throwDecode (Text.encodeUtf8 envVal)
    provider <- MaybeT . pure $ jsonVal ^? key "provider" . _String
    logFM DebugS (ls [fmt|ATP_CONF requests AccessTokenProvider '${provider}'|])
    if provider `elem` providers
      then do logFM InfoS (ls [fmt|Using AccessTokenProvider '${NonEmpty.head providers}'|])
              pure jsonVal
      else MaybeT (pure Nothing)
  case maybeConf of
    Just conf ->
      Just <$> throwDecodeValue conf
    Nothing ->
      pure Nothing