{-# LANGUAGE OverloadedStrings #-}
-- | Internal helpers for APIs.
module Web.Mackerel.Internal.Api (request, emptyBody, createHandler, ApiError, errorStatusCode, errorMessage) where

import Control.Monad ((>=>))
import Data.Aeson (encode, decode, (.:), FromJSON, ToJSON)
import Data.Aeson.Types (parseMaybe)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as LBS
import Data.Maybe (maybe, fromMaybe)
import Data.Semigroup ((<>))
import Network.HTTP.Client
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Network.HTTP.Types (StdMethod, renderStdMethod, Query, statusCode)

import Web.Mackerel.Client

type ResponseHandler a = Response LBS.ByteString -> Either ApiError a

-- | Error information of the response of API.
data ApiError = ApiError { errorStatusCode :: Int, errorMessage :: String } deriving (Eq, Show)

-- | Request to Mackerel.
request :: ToJSON a => Client -> StdMethod -> BS.ByteString -> Query -> Maybe a -> ResponseHandler b -> IO (Either ApiError b)
request client method' path' query body handler = do
  initialRequest <- setQueryString query <$> parseRequest (apiBase client)
  handler <$> (httpLbs initialRequest {
    method = renderStdMethod method', path = path', requestBody = RequestBodyLBS $ maybe "" encode body,
    requestHeaders = requestHeaders initialRequest ++ [
      ("X-Api-Key", BS.pack $ apiKey client),
      ("User-Agent", BS.pack $ userAgent client),
      ("Content-Type", "application/json")
    ]
  } =<< newManager (if secure initialRequest then tlsManagerSettings else defaultManagerSettings))

-- | Empty body (to avoid type ambiguity).
emptyBody :: Maybe ()
emptyBody = Nothing

-- | Create an api response handler.
createHandler :: FromJSON a => (a -> b) -> ResponseHandler b
createHandler extractor response
  | statusCode (responseStatus response) == 200 = maybe (Left decodeError) (Right . extractor) (decode (responseBody response))
  | otherwise = Left $ maybe decodeError getApiError $ decode (responseBody response)
  where getApiError json = ApiError {
          errorStatusCode = statusCode $ responseStatus response,
          errorMessage = fromMaybe "" $ parseMaybe (((.: "error") >=> (.: "message")) <> (.: "error")) json
        }
        decodeError = ApiError {
          errorStatusCode = statusCode $ responseStatus response,
          errorMessage = show $ responseBody response
        }