{-|
Module      : Network.Nakadi.Subscriptions.Stats
Description : Implementation of Nakadi Subscription API
Copyright   : (c) Moritz Schulte 2017, 2018
License     : BSD3
Maintainer  : mtesseract@silverratio.net
Stability   : experimental
Portability : POSIX

This module implements the @\/subscriptions@ API.
-}

{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TupleSections         #-}

module Network.Nakadi.Subscriptions
  ( module Network.Nakadi.Subscriptions.Cursors
  , module Network.Nakadi.Subscriptions.Events
  , module Network.Nakadi.Subscriptions.Stats
  , module Network.Nakadi.Subscriptions.Subscription
  , subscriptionCreate'
  , subscriptionCreate
  , subscriptionsList'
  , subscriptionsSource
  , subscriptionsList
  ) where

import           Network.Nakadi.Internal.Prelude

import           Conduit
import qualified Control.Exception.Safe                    as Safe
import           Control.Lens
import qualified Data.Text                                 as Text
import           Network.Nakadi.Internal.Http
import qualified Network.Nakadi.Internal.Lenses            as L
import           Network.Nakadi.Internal.Util
import           Network.Nakadi.Subscriptions.Cursors
import           Network.Nakadi.Subscriptions.Events
import           Network.Nakadi.Subscriptions.Stats
import           Network.Nakadi.Subscriptions.Subscription

path :: ByteString
path = "/subscriptions"

-- | @POST@ to @\/subscriptions@. Creates a new subscription. Low
-- level interface.
subscriptionCreate' ::
  MonadNakadi b m
  => Subscription
  -> m Subscription
subscriptionCreate' subscription =
  httpJsonBody status201 [(ok200, errorSubscriptionExistsAlready)]
  (setRequestMethod "POST"
   . setRequestPath path
   . setRequestBodyJSON subscription)

-- | @POST@ to @\/subscriptions@. Creates a new subscription. Does not
-- fail if the requested subscription does already exist.
subscriptionCreate ::
  (MonadNakadi b m, MonadCatch m)
  => Subscription
  -> m Subscription
subscriptionCreate subscription = do
  Safe.catchJust exceptionPredicate (subscriptionCreate' subscription) return

  where exceptionPredicate (SubscriptionExistsAlready s) = Just s
        exceptionPredicate _                             = Nothing

-- | @GET@ to @\/subscriptions@. Internal low-level interface.
subscriptionsGet ::
  MonadNakadi b m
  => [(ByteString, ByteString)]
  -> m SubscriptionsListResponse
subscriptionsGet queryParameters =
  httpJsonBody ok200 []
  (setRequestMethod "GET"
   . setRequestPath path
   . setRequestQueryParameters queryParameters)

buildQueryParameters :: Maybe ApplicationName
                     -> Maybe [EventTypeName]
                     -> Maybe Limit
                     -> Maybe Offset
                     -> [(ByteString, ByteString)]
buildQueryParameters maybeOwningApp maybeEventTypeNames maybeLimit maybeOffset =
  catMaybes $
  [ ("owning_application",) . encodeUtf8 . unApplicationName <$> maybeOwningApp
  , ("limit",) . encodeUtf8 . tshow <$> maybeLimit
  , ("offset",) . encodeUtf8 . tshow <$> maybeOffset ]
  ++ case maybeEventTypeNames of
       Just eventTypeNames -> map (Just . ("event_type",) . encodeUtf8 . unEventTypeName) eventTypeNames
       Nothing -> []

-- | @GET@ to @\/subscriptions@. Retrieves all subscriptions matching
-- the provided filter criteria. Low-level interface using pagination.
subscriptionsList' ::
  (MonadNakadi b m)
  => Maybe ApplicationName
  -> Maybe [EventTypeName]
  -> Maybe Limit
  -> Maybe Offset
  -> m SubscriptionsListResponse
subscriptionsList' maybeOwningApp maybeEventTypeNames maybeLimit maybeOffset = do
  subscriptionsGet queryParameters
  where queryParameters =
          buildQueryParameters maybeOwningApp maybeEventTypeNames maybeLimit maybeOffset

-- | @GET@ to @\/subscriptions@. Retrieves all subscriptions matching
-- the provided filter criteria. High-level Conduit interface.
subscriptionsSource :: ( MonadNakadi b m)
                    => Maybe ApplicationName
                    -> Maybe [EventTypeName]
                    -> m (ConduitM () [Subscription] m ())
subscriptionsSource maybeOwningApp maybeEventTypeNames =
  pure $ nextPage initialQueryParameters

  where nextPage queryParameters = do
          resp <- lift $ subscriptionsGet queryParameters
          yield (resp^.L.items)
          let maybeNextPath = Text.unpack . (view L.href) <$> (resp^.L.links.L.next)
          case maybeNextPath >>= extractQueryParametersFromPath  of
            Just nextQueryParameters -> do
              nextPage nextQueryParameters
            Nothing ->
              return ()

        initialQueryParameters =
          buildQueryParameters maybeOwningApp maybeEventTypeNames Nothing Nothing

-- | @GET@ to @\/subscriptions@. Retrieves all subscriptions matching
-- the provided filter criteria. High-level list interface.
subscriptionsList ::
  MonadNakadi b m
  => Maybe ApplicationName
  -> Maybe [EventTypeName]
  -> m [Subscription]
subscriptionsList maybeOwningApp maybeEventTypeNames = do
  source <- subscriptionsSource maybeOwningApp maybeEventTypeNames
  runConduit $ source .| concatC .| sinkList