{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, UnicodeSyntax, FlexibleContexts, FlexibleInstances, UndecidableInstances, ConstraintKinds #-}

-- | Provides an HTTP(S) client via http-client(-tls) in a Magicbane app context.
--   Also provides a simple composable interface for making arbitrary requests, based on http-client-conduit.
--   That lets you plug stream parsers (e.g. html-conduit: 'performWithFn ($$ sinkDoc)') directly into the reading of the response body.
module Magicbane.HTTPClient (
  module Magicbane.HTTPClient
, module X
) where

import           ClassyPrelude
import           Control.Monad.Trans.Either
import           Data.Has
import           Data.Conduit
import qualified Data.Conduit.Combinators as C
import           Data.String.Conversions
import           Network.URI as X
import           Network.HTTP.Types
import           Network.HTTP.Conduit as HC
import           Network.HTTP.Client.Conduit as HCC
import           Network.HTTP.Client.Internal (setUri) -- The fuck?
import           Network.HTTP.Client as X hiding (Proxy, path)
import           Network.HTTP.Client.TLS (newTlsManager)
import           Magicbane.Util (writeForm)

newtype ModHttpClient = ModHttpClient Manager

instance (Has ModHttpClient α)  HasHttpManager α where
  getHttpManager = (\(ModHttpClient m)  m) <$> getter

newHttpClient  IO ModHttpClient
newHttpClient = ModHttpClient <$> newTlsManager

type MonadHTTP ψ μ = (HasHttpManager ψ, MonadReader ψ μ, MonadIO μ, MonadBaseControl IO μ)

runHTTP  EitherT ε μ α  μ (Either ε α)
runHTTP = runEitherT

-- | Creates a request from a URI.
reqU  (MonadHTTP ψ μ)  URI  EitherT Text μ Request
reqU uri = hoistEither $ bimap tshow id $ setUri defaultRequest uri

-- | Creates a request from a string of any type, parsing it into a URI.
reqS  (MonadHTTP ψ μ, ConvertibleStrings σ String)  σ  EitherT Text μ Request
reqS uri = hoistEither $ bimap tshow id $ parseUrlThrow $ cs uri

-- | Configures the request to not throw errors on error status codes.
anyStatus  (MonadHTTP ψ μ)  Request  EitherT Text μ Request
anyStatus req = return $ setRequestIgnoreStatus req

-- | Sets a x-www-form-urlencoded form as the request body (also sets the content-type).
postForm  (MonadHTTP ψ μ)  [(Text, Text)]  Request  EitherT Text μ Request
postForm form req =
  return req { method = "POST"
             , requestHeaders = [ (hContentType, "application/x-www-form-urlencoded; charset=utf-8") ]
             , requestBody = RequestBodyBS $ writeForm form }

-- | Performs the request, using a given function to read the body. This is what all other performWith functions are based on.
performWithFn  (MonadHTTP ψ μ, MonadCatch μ)  (ConduitM ι ByteString μ ()  μ ρ)  Request  EitherT Text μ (Response ρ)
performWithFn fn req = do
  res  lift $ tryAny $ HCC.withResponse req $ \res  do
    body  fn $ responseBody res
    return res { responseBody = body }
  hoistEither $ bimap tshow id res

-- | Performs the request, ignoring the body.
performWithVoid  (MonadHTTP ψ μ, MonadCatch μ)  Request  EitherT Text μ (Response ())
performWithVoid = performWithFn (const $ return ())

-- | Performs the request, reading the body into a lazy ByteString.
performWithBytes  (MonadHTTP ψ μ, MonadCatch μ)  Request  EitherT Text μ (Response LByteString)
performWithBytes = performWithFn ($$ C.sinkLazy)