module Network.Nakadi.Internal.Retry
  ( retryAction
  ) where
import           Network.Nakadi.Internal.Prelude
import           Control.Lens
import           Control.Monad.IO.Class
import           Control.Retry
import           Network.HTTP.Client
import           Network.HTTP.Types.Status
import qualified Network.Nakadi.Internal.Lenses  as L
import           Network.Nakadi.Internal.Types
invokeHttpErrorCallback :: MonadIO m => Config -> Request -> HttpException -> RetryStatus -> m ()
invokeHttpErrorCallback config req exn retryStatus = liftIO $
  case config^.L.httpErrorCallback of
    Just cb -> do
      finalFailure <- applyPolicy (config^.L.retryPolicy) retryStatus >>= \case
        Just _  -> pure False
        Nothing -> pure True
      cb req exn retryStatus finalFailure
    Nothing -> pure ()
retryAction ::
  (MonadIO m, MonadMask m)
  => Config
  -> Request
  -> (Request -> m a)
  -> m a
retryAction config req ma =
  let policy = config^.L.retryPolicy
      nakadiRetryPolicy = RetryPolicyM $ \retryStatus ->
        liftIO (getRetryPolicyM policy retryStatus)
  in recovering nakadiRetryPolicy [handlerHttp] (const (ma req))
  where handlerHttp retryStatus = Handler $ \exn -> do
          invokeHttpErrorCallback config req exn retryStatus
          pure $ shouldRetry exn
        shouldRetry (HttpExceptionRequest _ exceptionContent) =
          case exceptionContent of
            StatusCodeException response _ ->
              responseStatus response `elem` [status500, status503]
            ResponseTimeout ->
              True
            ConnectionTimeout ->
              True
            ConnectionFailure _ ->
              True
            InternalException _ ->
              True
            ConnectionClosed ->
              True
            _ ->
              False
        shouldRetry _ = False