content: -- This file is part of the Wire Server implementation.
  --
  -- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
  --
  -- This program is free software: you can redistribute it and/or modify it under
  -- the terms of the GNU Affero General Public License as published by the Free
  -- Software Foundation, either version 3 of the License, or (at your option) any
  -- later version.
  --
  -- This program is distributed in the hope that it will be useful, but WITHOUT
  -- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  -- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  -- details.
  --
  -- You should have received a copy of the GNU Affero General Public License along
  -- with this program. If not, see <https://www.gnu.org/licenses/>.
  
  module Gundeck.API.Public
    ( servantSitemap,
    )
  where
  
  import Data.Id
  import Data.Range
  import Data.UUID as UUID
  import Data.UUID.Util qualified as UUID
  import Gundeck.Monad
  import Gundeck.Notification qualified as Notification
  import Gundeck.Notification.Data qualified as Data
  import Gundeck.Push qualified as Push
  import Imports
  import Servant (HasServer (..), (:<|>) (..))
  import Wire.API.Notification qualified as Public
  import Wire.API.Routes.Named (Named (Named))
  import Wire.API.Routes.Public.Gundeck
  
  -------------------------------------------------------------------------------
  -- Servant API
  
  servantSitemap :: ServerT GundeckAPI Gundeck
  servantSitemap = pushAPI :<|> notificationAPI :<|> timeAPI
    where
      pushAPI =
        Named @"register-push-token" Push.addToken
          :<|> Named @"delete-push-token" Push.deleteToken
          :<|> Named @"get-push-tokens" Push.listTokens
  
      notificationAPI =
        Named @"get-notification-by-id" Data.fetchId
          :<|> Named @"get-last-notification" Data.fetchLast
          :<|> Named @"get-notifications@v2" paginateUntilV2
          :<|> Named @"get-notifications" paginate
  
      timeAPI =
        Named @"get-server-time" getServerTime
  
  -- | Returns a list of notifications for given 'uid'
  --
  --
  -- Takes an optional parameter 'since' which is a V1 UUID, (which includes a
  -- timestamp).
  --
  -- If the parameter 'since' is omitted, all notifications of the user are
  -- returned. This is not recommended. (TODO: Ask client teams if they ever use
  -- this)
  --
  -- If the parameter 'since' fails to parse, all notifications of the user are
  -- returned but the status code is set to 404.
  -- FUTUREWORK: We should change this behaviour as it's extremely confusing. We
  -- could kindly reject with a 400, and not event hit the database at all.
  -- This was introduced in
  -- https://github.com/zinfra/orlop/pull/30/commits/a358dfc1cb225c92066ea79db28c8824531ae231
  --
  -- If the 'since' parameter is present, and a notification 'since' is actually
  -- found in the database, this returns all the notifications since 'since'
  -- (exclusive of 'since' itself) and returns a status code 200.
  --
  -- If the 'since' parameter is present, and a notification 'since' is not found
  -- in the database, then due to the fact that 'since' is a V1 UUID (which
  -- contains a timestamp) we can still return all the notifications that
  -- happened after it eventhough it is not present in the database. This can
  -- happen for example because a client hasn't been online for 30 days and we
  -- have deleted the notification in the backend in the meantime.
  -- We will return all the notifications that we have that happened after 'since'
  -- but return status code 404 to signal that 'since' itself was missing.
  --
  -- (arianvp): I am not sure why it is convenient for clients to distinguish
  -- between these two cases.
  paginateUntilV2 ::
    UserId ->
    Maybe Public.RawNotificationId ->
    Maybe ClientId ->
    Maybe (Range 100 10000 Int32) ->
    Gundeck Public.GetNotificationsResponse
  paginateUntilV2 uid mbSince mbClient mbSize = do
    let size = fromMaybe (unsafeRange 1000) mbSize
    Notification.PaginateResult gap page <- Notification.paginate uid (join since) mbClient size
    pure $
      if gap
        then Public.GetNotificationsWithStatusNotFound page
        else case since of
          Just (Just _) -> Public.GetNotificationsSuccess page
          Nothing -> Public.GetNotificationsSuccess page
          Just Nothing -> Public.GetNotificationsWithStatusNotFound page
    where
      since :: Maybe (Maybe Public.NotificationId)
      since = parseUUID <$> mbSince
  
      parseUUID :: Public.RawNotificationId -> Maybe Public.NotificationId
      parseUUID = (UUID.fromASCIIBytes . Public.unRawNotificationId) >=> isV1UUID >=> pure . Id
  
      isV1UUID :: UUID -> Maybe UUID
      isV1UUID u = if UUID.version u == 1 then Just u else Nothing
  
  paginate ::
    UserId ->
    Maybe Public.NotificationId ->
    Maybe ClientId ->
    Maybe (Range 100 10000 Int32) ->
    Gundeck (Maybe Public.QueuedNotificationList)
  paginate uid mbSince mbClient mbSize = do
    let size = fromMaybe (unsafeRange 1000) mbSize
    Notification.PaginateResult gap page <- Notification.paginate uid mbSince mbClient size
    pure $ if gap then Nothing else Just page
  
  getServerTime :: UserId -> Gundeck Public.ServerTime
  getServerTime _ = Public.ServerTime . msToUTCSecs <$> posixTime
  