-- | Helpers for dealing with HTTP requests.
module Strive.Internal.HTTP
  ( delete
  , get
  , post
  , put
  , buildRequest
  , performRequest
  , decodeValue
  ) where

import Data.Aeson (FromJSON, eitherDecode)
import Data.ByteString.Char8 (unpack)
import Data.ByteString.Lazy (ByteString)
import Network.HTTP.Conduit (Request, Response, checkStatus, method, parseUrl,
                             responseBody)
import Network.HTTP.Types (Method, Query, QueryLike, methodDelete, methodGet,
                           methodPost, methodPut, renderQuery, toQuery)
import Strive.Client (Client (client_accessToken, client_requester))

-- | Perform an HTTP DELETE request.
delete :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Either String j)
delete = http methodDelete

-- | Perform an HTTP GET request.
get :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Either String j)
get = http methodGet

-- | Perform an HTTP POST request.
post :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Either String j)
post = http methodPost

-- | Perform an HTTP PUT request.
put :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Either String j)
put = http methodPut

-- | Perform an HTTP request.
http :: (QueryLike q, FromJSON j) => Method -> Client -> String -> q -> IO (Either String j)
http httpMethod client resource query = do
  request <- buildRequest httpMethod client resource query
  response <- performRequest client request
  return (decodeValue response)

-- | Build a request.
buildRequest :: QueryLike q => Method -> Client -> String -> q -> IO Request
buildRequest httpMethod client resource query = do
  request <- parseUrl (buildUrl client resource query)
  return request
    { checkStatus = \ _ _ _ -> Nothing
    , method = httpMethod
    }

-- | Build a URL.
buildUrl :: QueryLike q => Client -> String -> q -> String
buildUrl client resource query = concat
  [ "https://www.strava.com/"
  , resource
  , unpack (renderQuery True (buildQuery client ++ toQuery query))
  ]

-- | Build a query.
buildQuery :: Client -> Query
buildQuery client = toQuery
  [ ("access_token", client_accessToken client)
  ]

-- | Actually perform an HTTP request.
performRequest :: Client -> Request -> IO (Response ByteString)
performRequest client request = (client_requester client) request

-- | Decode a response body as JSON.
decodeValue :: FromJSON j => Response ByteString -> Either String j
decodeValue response = eitherDecode (responseBody response)