{-# LANGUAGE OverloadedLabels #-}
{-# OPTIONS_GHC -Wno-ambiguous-fields #-}

-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2025 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 API.Galley where

import API.Common
import Control.Lens hiding ((.=))
import Control.Monad.Reader
import Control.Retry
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Base64.URL as B64U
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ProtoLens as Proto
import Data.ProtoLens.Labels ()
import qualified Data.UUID as UUID
import Numeric.Lens
import Proto.Otr as Proto
import Testlib.Prelude

data CreateConv = CreateConv
  { qualifiedUsers :: [Value],
    name :: Maybe String,
    access :: Maybe [String],
    accessRole :: Maybe [String],
    team :: Maybe String,
    messageTimer :: Maybe Int,
    receiptMode :: Maybe Int,
    newUsersRole :: String,
    protocol :: String,
    groupConvType :: Maybe String,
    cells :: Bool,
    addPermission :: Maybe String,
    skipCreator :: Maybe Bool,
    parent :: Maybe String,
    history :: Maybe Value
  }

defProteus :: CreateConv
defProteus =
  CreateConv
    { qualifiedUsers = [],
      name = Nothing,
      access = Nothing,
      accessRole = Nothing,
      team = Nothing,
      messageTimer = Nothing,
      receiptMode = Nothing,
      newUsersRole = "wire_admin",
      protocol = "proteus",
      groupConvType = Nothing,
      cells = False,
      addPermission = Nothing,
      skipCreator = Nothing,
      parent = Nothing,
      history = Nothing
    }

defMLS :: CreateConv
defMLS = defProteus {protocol = "mls"}

defConv :: ConversationProtocol -> CreateConv
defConv ConversationProtocolProteus = defProteus
defConv ConversationProtocolMLS = defMLS

allowAll :: CreateConv -> CreateConv
allowAll cc =
  cc
    { access = Just ["code", "link", "invite"],
      accessRole = Just ["team_member", "guest", "non_team_member", "service"]
    }

allowGuests :: CreateConv -> CreateConv
allowGuests cc =
  cc
    { access = Just ["code"],
      accessRole = Just ["team_member", "non_team_member", "service"]
    }

instance MakesValue CreateConv where
  make cc = do
    quids <- for (cc.qualifiedUsers) objQidObject
    pure
      $ Aeson.object
      $ ( [ "qualified_users" .= quids,
            "conversation_role" .= cc.newUsersRole,
            "protocol" .= cc.protocol,
            "cells" .= cc.cells
          ]
            <> catMaybes
              [ "name" .=? cc.name,
                "access" .=? cc.access,
                "access_role" .=? cc.accessRole,
                "team" .=? (cc.team <&> \tid -> Aeson.object ["teamid" .= tid, "managed" .= False]),
                "message_timer" .=? cc.messageTimer,
                "receipt_mode" .=? cc.receiptMode,
                "group_conv_type" .=? cc.groupConvType,
                "add_permission" .=? cc.addPermission,
                "skip_creator" .=? cc.skipCreator,
                "parent" .=? cc.parent,
                "history" .=? cc.history
              ]
        )

postConversation ::
  ( HasCallStack,
    MakesValue user
  ) =>
  user ->
  CreateConv ->
  App Response
postConversation user cc = do
  req <- baseRequest user Galley Versioned "/conversations"
  ccv <- make cc
  submit "POST" $ req & addJSON ccv

deleteTeamConversation ::
  ( HasCallStack,
    MakesValue user,
    MakesValue conv
  ) =>
  String ->
  conv ->
  user ->
  App Response
deleteTeamConversation tid qcnv user = do
  cnv <- snd <$> objQid qcnv
  let path = joinHttpPath ["teams", tid, "conversations", cnv]
  req <- baseRequest user Galley Versioned path
  submit "DELETE" req

deleteTeamMember ::
  ( HasCallStack,
    MakesValue owner,
    MakesValue member
  ) =>
  String ->
  owner ->
  member ->
  App Response
deleteTeamMember tid owner mem = do
  memId <- objId mem
  let path = joinHttpPath ["teams", tid, "members", memId]
  req <- baseRequest owner Galley Versioned path
  submit "DELETE" (addJSONObject ["password" .= defPassword] req)

data TeamPermissions = Partner | Member | Admin | Owner

instance ToJSON TeamPermissions where
  toJSON perms = object ["self" .= toInt perms, "copy" .= toInt perms]
    where
      toInt Partner = Number 1025
      toInt Member = Number 1587
      toInt Admin = Number 5951
      toInt Owner = Number 8191

updateTeamMember :: (HasCallStack, MakesValue user, MakesValue member) => String -> user -> member -> TeamPermissions -> App Response
updateTeamMember tid owner mem permissions = do
  memId <- objId mem
  let path = joinHttpPath ["teams", tid, "members"]
  req <- baseRequest owner Galley Versioned path
  submit "PUT" (req & addJSONObject ["member" .= object ["user" .= memId, "permissions" .= permissions]])

putConversationProtocol ::
  ( HasCallStack,
    MakesValue user,
    MakesValue protocol
  ) =>
  user ->
  ConvId ->
  protocol ->
  App Response
putConversationProtocol user convId protocol = do
  p <- asString protocol
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convId.domain, convId.id_, "protocol"])
  submit "PUT" (req & addJSONObject ["protocol" .= p])

getConversation ::
  ( HasCallStack,
    MakesValue user,
    MakesValue qcnv
  ) =>
  user ->
  qcnv ->
  App Response
getConversation = getConversationVersioned Versioned

getConversationVersioned ::
  ( HasCallStack,
    MakesValue user,
    MakesValue qcnv
  ) =>
  Versioned ->
  user ->
  qcnv ->
  App Response
getConversationVersioned version user qcnv = do
  (domain, cnv) <- objQid qcnv
  req <- baseRequest user Galley version (joinHttpPath ["conversations", domain, cnv])
  submit "GET" req

getConversationInternal ::
  ( HasCallStack,
    MakesValue user,
    MakesValue qcnv
  ) =>
  user ->
  qcnv ->
  App Response
getConversationInternal user qcnv = do
  (_domain, cnv) <- objQid qcnv
  req <- baseRequest user Galley Unversioned (joinHttpPath ["i", "conversations", cnv])
  submit "GET" req

getSubConversation ::
  ( HasCallStack,
    MakesValue user
  ) =>
  user ->
  ConvId ->
  String ->
  App Response
getSubConversation user conv sub = do
  req <-
    baseRequest user Galley Versioned
      $ joinHttpPath
        [ "conversations",
          conv.domain,
          conv.id_,
          "subconversations",
          sub
        ]
  submit "GET" req

deleteSubConversation ::
  (HasCallStack, MakesValue user, MakesValue sub) =>
  user ->
  sub ->
  App Response
deleteSubConversation user sub = do
  (conv, Just subId) <- objSubConv sub
  (domain, convId) <- objQid conv
  groupId <- sub %. "group_id" & asString
  epoch :: Int <- sub %. "epoch" & asIntegral
  req <-
    baseRequest user Galley Versioned
      $ joinHttpPath ["conversations", domain, convId, "subconversations", subId]
  submit "DELETE" $ req & addJSONObject ["group_id" .= groupId, "epoch" .= epoch]

leaveSubConversation ::
  (HasCallStack, MakesValue user) =>
  user ->
  ConvId ->
  App Response
leaveSubConversation user convId = do
  let Just subId = convId.subconvId
  req <-
    baseRequest user Galley Versioned
      $ joinHttpPath ["conversations", convId.domain, convId.id_, "subconversations", subId, "self"]
  submit "DELETE" req

getSelfConversation :: (HasCallStack, MakesValue user) => user -> App Response
getSelfConversation = getSelfConversationVersioned Versioned

getSelfConversationVersioned :: (HasCallStack, MakesValue user) => Versioned -> user -> App Response
getSelfConversationVersioned version user = do
  req <- baseRequest user Galley version "/conversations/mls-self"
  submit "GET" $ req

data ListConversationIds = ListConversationIds {pagingState :: Maybe String, size :: Maybe Int}

instance Default ListConversationIds where
  def = ListConversationIds Nothing Nothing

listConversationIds :: (MakesValue user) => user -> ListConversationIds -> App Response
listConversationIds user args = do
  req <- baseRequest user Galley Versioned "/conversations/list-ids"
  submit "POST"
    $ req
    & addJSONObject
      ( ["paging_state" .= s | s <- toList args.pagingState]
          <> ["size" .= s | s <- toList args.size]
      )

listConversations :: (MakesValue user) => user -> [Value] -> App Response
listConversations = listConversationsVersioned Versioned

listConversationsVersioned :: (MakesValue user) => Versioned -> user -> [Value] -> App Response
listConversationsVersioned version user cnvs = do
  req <- baseRequest user Galley version "/conversations/list"
  submit "POST"
    $ req
    & addJSONObject ["qualified_ids" .= cnvs]

getConversationsVersioned :: (MakesValue user) => Versioned -> user -> String -> App Response
getConversationsVersioned version user ids = do
  req <- baseRequest user Galley version "/conversations"
  submit "GET" $ addQueryParams [("ids", ids)] req

getMLSPublicKeys :: (HasCallStack, MakesValue user) => user -> App Response
getMLSPublicKeys user = do
  req <- baseRequest user Galley Versioned "/mls/public-keys"
  submit "GET" req

getMLSPublicKeysJWK :: (HasCallStack, MakesValue user) => user -> App Response
getMLSPublicKeysJWK user = do
  req <- baseRequest user Galley Versioned "/mls/public-keys"
  submit "GET" $ addQueryParams [("format", "jwk")] req

postMLSMessage :: (HasCallStack) => ClientIdentity -> ByteString -> App Response
postMLSMessage cid msg = do
  req <- baseRequest cid Galley Versioned "/mls/messages"
  submit "POST" (addMLS msg req)

postMLSCommitBundle :: (HasCallStack) => ClientIdentity -> ByteString -> App Response
postMLSCommitBundle cid msg = do
  req <- baseRequest cid Galley Versioned "/mls/commit-bundles"
  submit "POST" (addMLS msg req)

postProteusMessage :: (HasCallStack, MakesValue user, MakesValue conv) => user -> conv -> QualifiedNewOtrMessage -> App Response
postProteusMessage user conv msgs = do
  convDomain <- objDomain conv
  convId <- objQidObject conv & objId
  let bytes = Proto.encodeMessage msgs
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convDomain, convId, "proteus", "messages"])
  submit "POST" (addProtobuf bytes req)

mkProteusRecipient :: (HasCallStack, MakesValue user, MakesValue client) => user -> client -> String -> App Proto.QualifiedUserEntry
mkProteusRecipient user client = mkProteusRecipients user [(user, [client])]

mkProteusRecipients :: (HasCallStack, MakesValue domain, MakesValue user, MakesValue client) => domain -> [(user, [client])] -> String -> App Proto.QualifiedUserEntry
mkProteusRecipients dom userClients msg = do
  userDomain <- asString =<< objDomain dom
  userEntries <- mapM mkUserEntry userClients
  pure
    $ Proto.defMessage
    & #domain .~ fromString userDomain
    & #entries .~ userEntries
  where
    mkUserEntry (user, clients) = do
      userId <- LBS.toStrict . UUID.toByteString . fromJust . UUID.fromString <$> objId user
      clientEntries <- mapM mkClientEntry clients
      pure
        $ Proto.defMessage
        & #user . #uuid .~ userId
        & #clients .~ clientEntries
    mkClientEntry client = do
      clientId <- (^?! hex) <$> objId client
      pure
        $ Proto.defMessage
        & #client . #client .~ clientId
        & #text .~ fromString msg

getGroupInfo ::
  (HasCallStack, MakesValue user) =>
  user ->
  ConvId ->
  App Response
getGroupInfo user conv = do
  let path = joinHttpPath $ case conv.subconvId of
        Nothing -> ["conversations", conv.domain, conv.id_, "groupinfo"]
        Just sub -> ["conversations", conv.domain, conv.id_, "subconversations", sub, "groupinfo"]
  req <- baseRequest user Galley Versioned path
  submit "GET" req

removeConversationMember ::
  (HasCallStack, MakesValue user, MakesValue conv) =>
  user ->
  conv ->
  App Response
removeConversationMember user conv = do
  (convDomain, convId) <- objQid conv
  (userDomain, userId) <- objQid user
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convDomain, convId, "members", userDomain, userId])
  submit "DELETE" req

updateConversationMember ::
  (HasCallStack, MakesValue user, MakesValue conv, MakesValue target) =>
  user ->
  conv ->
  target ->
  String ->
  App Response
updateConversationMember user conv target role = do
  (convDomain, convId) <- objQid conv
  (targetDomain, targetId) <- objQid target
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convDomain, convId, "members", targetDomain, targetId])
  submit "PUT" (req & addJSONObject ["conversation_role" .= role])

updateChannelAddPermission :: (HasCallStack, MakesValue user, MakesValue conv) => user -> conv -> String -> App Response
updateChannelAddPermission user conv perm = do
  (convDomain, convId) <- objQid conv
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convDomain, convId, "add-permission"])
  submit "PUT" (req & addJSONObject ["add_permission" .= perm])

deleteTeamConv ::
  (HasCallStack, MakesValue team, MakesValue conv, MakesValue user) =>
  team ->
  conv ->
  user ->
  App Response
deleteTeamConv team conv user = do
  teamId <- objId team
  convId <- objId $ objQidObject conv
  req <- baseRequest user Galley Versioned (joinHttpPath ["teams", teamId, "conversations", convId])
  submit "DELETE" req

getMLSOne2OneConversationLegacy ::
  (HasCallStack, MakesValue self, MakesValue other) =>
  self ->
  other ->
  App Response
getMLSOne2OneConversationLegacy self other = do
  (domain, uid) <- objQid other
  req <-
    baseRequest self Galley Versioned
      $ joinHttpPath ["conversations", "one2one", domain, uid]
  submit "GET" req

getMLSOne2OneConversation ::
  (HasCallStack, MakesValue self, MakesValue other) =>
  self ->
  other ->
  App Response
getMLSOne2OneConversation = getMLSOne2OneConversationVersioned Versioned

getMLSOne2OneConversationVersioned ::
  (HasCallStack, MakesValue self, MakesValue other) =>
  Versioned ->
  self ->
  other ->
  App Response
getMLSOne2OneConversationVersioned version self other = do
  (domain, uid) <- objQid other
  req <-
    baseRequest self Galley version
      $ joinHttpPath ["one2one-conversations", domain, uid]
  submit "GET" req

postOne2OneConversation ::
  (HasCallStack, MakesValue self, MakesValue other) =>
  self ->
  other ->
  String ->
  String ->
  App Response
postOne2OneConversation self other tid convName = do
  qUid <- objQidObject other
  req <-
    baseRequest self Galley Versioned
      $ joinHttpPath ["one2one-conversations"]
  submit
    "POST"
    ( req
        & addJSONObject
          [ "name" .= convName,
            "qualified_users" .= [qUid],
            "team" .= Aeson.object ["teamid" .= tid, "managed" .= False]
          ]
    )

getGroupClients ::
  (HasCallStack, MakesValue user) =>
  user ->
  String ->
  App Response
getGroupClients user groupId = do
  req <-
    baseRequest
      user
      Galley
      Unversioned
      (joinHttpPath ["i", "group", BS.unpack . B64U.encodeUnpadded . B64.decodeLenient $ BS.pack groupId])
  submit "GET" req

data AddMembers = AddMembers
  { users :: [Value],
    role :: Maybe String,
    version :: Maybe Int
  }

instance Default AddMembers where
  def = AddMembers {users = [], role = Nothing, version = Nothing}

addMembers ::
  (HasCallStack, MakesValue user, MakesValue conv) =>
  user ->
  conv ->
  AddMembers ->
  App Response
addMembers usr qcnv opts = do
  (convDomain, convId) <- objQid qcnv
  qUsers <- mapM objQidObject opts.users
  let path = case opts.version of
        Just v | v <= 1 -> ["conversations", convId, "members", "v2"]
        _ -> ["conversations", convDomain, convId, "members"]
  req <-
    baseRequest
      usr
      Galley
      (maybe Versioned ExplicitVersion opts.version)
      (joinHttpPath path)
  submit "POST"
    $ req
    & addJSONObject
      ( ["qualified_users" .= qUsers]
          <> ["conversation_role" .= r | r <- toList opts.role]
      )

replaceMembers ::
  (HasCallStack, MakesValue user, MakesValue conv) =>
  user ->
  conv ->
  AddMembers ->
  App Response
replaceMembers usr qcnv opts = do
  (convDomain, convId) <- objQid qcnv
  qUsers <- mapM objQidObject opts.users
  let path = ["conversations", convDomain, convId, "members"]
  req <-
    baseRequest
      usr
      Galley
      (maybe Versioned ExplicitVersion opts.version)
      (joinHttpPath path)
  submit "PUT"
    $ req
    & addJSONObject
      ( ["qualified_users" .= qUsers]
          <> ["conversation_role" .= r | r <- toList opts.role]
      )

removeMember :: (HasCallStack, MakesValue remover, MakesValue conv, MakesValue removed) => remover -> conv -> removed -> App Response
removeMember remover qcnv removed = do
  (convDomain, convId) <- objQid qcnv
  (removedDomain, removedId) <- objQid removed
  req <- baseRequest remover Galley Versioned (joinHttpPath ["conversations", convDomain, convId, "members", removedDomain, removedId])
  submit "DELETE" req

postConversationCode ::
  (HasCallStack, MakesValue user, MakesValue conv) =>
  user ->
  conv ->
  Maybe String ->
  Maybe String ->
  App Response
postConversationCode user conv mbpassword mbZHost = do
  convId <- objQidObject conv & objId
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convId, "code"])
  submit
    "POST"
    ( req
        & addJSONObject ["password" .= pw | pw <- maybeToList mbpassword]
        & maybe id zHost mbZHost
    )

getConversationCode ::
  (HasCallStack, MakesValue user, MakesValue conv) =>
  user ->
  conv ->
  Maybe String ->
  App Response
getConversationCode user conv mbZHost = do
  convId <- objQidObject conv & objId
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convId, "code"])
  submit
    "GET"
    ( req
        & addQueryParams [("cnv", convId)]
        & maybe id zHost mbZHost
    )

deleteConversationCode :: (HasCallStack, MakesValue user, MakesValue conv) => user -> conv -> App Response
deleteConversationCode user conv = do
  convId <- objQidObject conv & objId
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convId, "code"])
  submit "DELETE" req

getJoinCodeConv :: (HasCallStack, MakesValue user) => user -> String -> String -> App Response
getJoinCodeConv u k v = do
  req <- baseRequest u Galley Versioned (joinHttpPath ["conversations", "join"])
  submit "GET" (req & addQueryParams [("key", k), ("code", v)])

postJoinCodeConv :: (HasCallStack, MakesValue user) => user -> String -> String -> App Response
postJoinCodeConv u k v = do
  req <- baseRequest u Galley Versioned (joinHttpPath ["conversations", "join"])
  submit
    "POST"
    ( req
        & zType "access"
        & addJSONObject
          [ "key" .= k,
            "code" .= v
          ]
    )

-- https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/put_conversations__cnv_domain___cnv__name
changeConversationName ::
  (HasCallStack, MakesValue user, MakesValue conv, MakesValue name) =>
  user ->
  conv ->
  name ->
  App Response
changeConversationName user qcnv name = do
  (convDomain, convId) <- objQid qcnv
  let path = joinHttpPath ["conversations", convDomain, convId, "name"]
  nameReq <- make name
  req <- baseRequest user Galley Versioned path
  submit "PUT" (req & addJSONObject ["name" .= nameReq])

updateRole ::
  ( HasCallStack,
    MakesValue callerUser,
    MakesValue targetUser,
    MakesValue roleUpdate,
    MakesValue qcnv
  ) =>
  callerUser ->
  targetUser ->
  roleUpdate ->
  qcnv ->
  App Response
updateRole caller target role qcnv = do
  (cnvDomain, cnvId) <- objQid qcnv
  (tarDomain, tarId) <- objQid target
  roleReq <- make role
  req <-
    baseRequest
      caller
      Galley
      Versioned
      (joinHttpPath ["conversations", cnvDomain, cnvId, "members", tarDomain, tarId])
  submit "PUT" (req & addJSONObject ["conversation_role" .= roleReq])

updateReceiptMode ::
  ( HasCallStack,
    MakesValue user,
    MakesValue conv,
    MakesValue mode
  ) =>
  user ->
  conv ->
  mode ->
  App Response
updateReceiptMode user qcnv mode = do
  (cnvDomain, cnvId) <- objQid qcnv
  modeReq <- make mode
  let path = joinHttpPath ["conversations", cnvDomain, cnvId, "receipt-mode"]
  req <- baseRequest user Galley Versioned path
  submit "PUT" (req & addJSONObject ["receipt_mode" .= modeReq])

updateAccess ::
  ( HasCallStack,
    MakesValue user,
    MakesValue conv
  ) =>
  user ->
  conv ->
  [Aeson.Pair] ->
  App Response
updateAccess user qcnv update = do
  (cnvDomain, cnvId) <- objQid qcnv
  let path = joinHttpPath ["conversations", cnvDomain, cnvId, "access"]
  req <- baseRequest user Galley Versioned path
  submit "PUT" (req & addJSONObject update)

updateMessageTimer ::
  ( HasCallStack,
    MakesValue user,
    MakesValue conv
  ) =>
  user ->
  conv ->
  Word64 ->
  App Response
updateMessageTimer user qcnv update = do
  (cnvDomain, cnvId) <- objQid qcnv
  updateReq <- make update
  let path = joinHttpPath ["conversations", cnvDomain, cnvId, "message-timer"]
  req <- baseRequest user Galley Versioned path
  submit "PUT" (addJSONObject ["message_timer" .= updateReq] req)

updateHistory ::
  ( HasCallStack,
    MakesValue user,
    MakesValue conv
  ) =>
  user ->
  conv ->
  Value ->
  App Response
updateHistory user qcnv history = do
  (cnvDomain, cnvId) <- objQid qcnv
  let path = joinHttpPath ["conversations", cnvDomain, cnvId, "history"]
  req <- baseRequest user Galley Versioned path
  submit "PUT" (addJSONObject ["history" .= history] req)

getTeam :: (HasCallStack, MakesValue user, MakesValue tid) => user -> tid -> App Response
getTeam user tid = do
  tidStr <- asString tid
  req <- baseRequest user Galley Versioned (joinHttpPath ["teams", tidStr])
  submit "GET" req

getTeamMembers :: (HasCallStack, MakesValue user, MakesValue tid) => user -> tid -> App Response
getTeamMembers user tid = do
  tidStr <- asString tid
  req <- baseRequest user Galley Versioned (joinHttpPath ["teams", tidStr, "members"])
  submit "GET" req

getTeamMember ::
  (HasCallStack, MakesValue user, MakesValue tid, MakesValue mid) =>
  user -> tid -> mid -> App Response
getTeamMember user tid mid = do
  tidStr <- asString tid
  midStr <- asString mid
  req <- baseRequest user Galley Versioned (joinHttpPath ["teams", tidStr, "members", midStr])
  submit "GET" req

-- https://staging-nginz-https.zinfra.io/v16/api/swagger-ui/#/default/get-team-members-by-ids
getTeamMembersByIdsUsingPost :: (HasCallStack, MakesValue searcher, MakesValue target, MakesValue tid) => searcher -> tid -> [target] -> App Response
getTeamMembersByIdsUsingPost searcher tid targets = do
  tidStr <- asString tid
  targetStrs <- asString `mapM` targets
  req <- baseRequest searcher Galley Versioned (joinHttpPath ["teams", tidStr, "get-members-by-ids-using-post"])
  submit "POST" (addJSONObject ["user_ids" .= targetStrs] req)

data AppLockSettings = AppLockSettings
  { status :: String,
    enforce :: Bool,
    inactivityTimeoutSecs :: Int
  }

instance Default AppLockSettings where
  def = AppLockSettings "disabled" False 60

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/put_teams__tid__features_appLock
putAppLockSettings ::
  (HasCallStack, MakesValue tid, MakesValue caller) =>
  tid ->
  caller ->
  AppLockSettings ->
  App Response
putAppLockSettings tid caller settings = do
  tidStr <- asString tid
  req <- baseRequest caller Galley Versioned (joinHttpPath ["teams", tidStr, "features", "appLock"])
  submit
    "PUT"
    ( addJSONObject
        [ "status" .= settings.status,
          "ttl" .= "unlimited",
          "config"
            .= object
              [ "enforceAppLock" .= settings.enforce,
                "inactivityTimeoutSecs" .= settings.inactivityTimeoutSecs
              ]
        ]
        req
    )

data TeamProperties = TeamProperties
  { icon :: String,
    iconKey :: String,
    name :: String,
    spashScreen :: String
  }

instance Default TeamProperties where
  def = TeamProperties "default" "default" "test" "default"

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/put_teams__tid_
putTeamProperties ::
  (HasCallStack, MakesValue tid, MakesValue caller) =>
  tid ->
  caller ->
  TeamProperties ->
  App Response
putTeamProperties tid caller properties = do
  tidStr <- asString tid
  req <- baseRequest caller Galley Versioned (joinHttpPath ["teams", tidStr])
  submit
    "PUT"
    ( addJSONObject
        [ "icon" .= properties.icon,
          "icon_key" .= properties.iconKey,
          "name" .= properties.name,
          "splash_screen" .= properties.spashScreen
        ]
        req
    )

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_teams__tid__legalhold__uid_
legalholdUserStatus :: (HasCallStack, MakesValue tid, MakesValue user, MakesValue owner) => tid -> owner -> user -> App Response
legalholdUserStatus tid ownerid user = do
  tidS <- asString tid
  uid <- objId user
  req <- baseRequest ownerid Galley Versioned (joinHttpPath ["teams", tidS, "legalhold", uid])
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/delete_teams__tid__legalhold__uid_
disableLegalHold ::
  (HasCallStack, MakesValue tid, MakesValue ownerid, MakesValue uid) =>
  tid ->
  ownerid ->
  uid ->
  -- | the password for user with $uid$
  String ->
  App Response
disableLegalHold tid ownerid uid pw = do
  tidStr <- asString tid
  uidStr <- objId uid
  req <- baseRequest ownerid Galley Versioned (joinHttpPath ["teams", tidStr, "legalhold", uidStr])
  submit "DELETE" (addJSONObject ["password" .= pw] req)

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/post_teams__tid__legalhold_consent
consentToLegalHold :: (HasCallStack, MakesValue tid, MakesValue zusr) => tid -> zusr -> String -> App Response
consentToLegalHold tid zusr pwd = do
  tidStr <- asString tid
  req <- baseRequest zusr Galley Versioned (joinHttpPath ["teams", tidStr, "legalhold", "consent"])
  submit "POST" (addJSONObject ["password" .= pwd] req)

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_teams__tid__legalhold__uid_
getLegalHoldStatus :: (HasCallStack, MakesValue tid, MakesValue zusr) => tid -> zusr -> App Response
getLegalHoldStatus tid zusr = do
  tidStr <- asString tid
  uidStr <- asString $ zusr %. "id"
  req <- baseRequest zusr Galley Versioned (joinHttpPath ["teams", tidStr, "legalhold", uidStr])
  submit "GET" req

getLegalHoldSettings :: (HasCallStack, MakesValue tid, MakesValue zusr) => tid -> zusr -> App Response
getLegalHoldSettings tid zusr = do
  tidStr <- asString tid
  req <- baseRequest zusr Galley Versioned (joinHttpPath ["teams", tidStr, "legalhold", "settings"])
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/post_teams__tid__legalhold_settings
postLegalHoldSettings :: (HasCallStack, MakesValue ownerid, MakesValue tid, MakesValue newService) => tid -> ownerid -> newService -> App Response
postLegalHoldSettings tid owner newSettings =
  asks ((* 1_000_000) . timeOutSeconds) >>= \tSecs -> retrying (policy tSecs) only412 $ \_ -> do
    tidStr <- asString tid
    req <- baseRequest owner Galley Versioned (joinHttpPath ["teams", tidStr, "legalhold", "settings"])
    newSettingsObj <- make newSettings
    submit "POST" (addJSON newSettingsObj req)
  where
    policy :: Int -> RetryPolicy
    policy tSecs = limitRetriesByCumulativeDelay tSecs $ exponentialBackoff 50

    only412 :: RetryStatus -> Response -> App Bool
    only412 _ resp = pure $ resp.status == 412

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/post_teams__tid__legalhold__uid_
requestLegalHoldDevice :: (HasCallStack, MakesValue tid, MakesValue ownerid, MakesValue uid) => tid -> ownerid -> uid -> App Response
requestLegalHoldDevice tid ownerid uid = do
  tidStr <- asString tid
  uidStr <- objId uid
  req <- baseRequest ownerid Galley Versioned (joinHttpPath ["teams", tidStr, "legalhold", uidStr])
  submit "POST" req

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/put_teams__tid__legalhold__uid__approve
--
--   like approveLegalHoldDevice' but approves for the requesting party
approveLegalHoldDevice :: (HasCallStack, MakesValue tid, MakesValue uid) => tid -> uid -> String -> App Response
approveLegalHoldDevice tid uid = approveLegalHoldDevice' tid uid uid

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/put_teams__tid__legalhold__uid__approve
--
--   useful for testing unauthorized requests
approveLegalHoldDevice' :: (HasCallStack, MakesValue tid, MakesValue uid, MakesValue forUid) => tid -> uid -> forUid -> String -> App Response
approveLegalHoldDevice' tid uid forUid pwd = do
  tidStr <- asString tid
  uidStr <- asString $ forUid %. "id"
  req <- baseRequest uid Galley Versioned (joinHttpPath ["teams", tidStr, "legalhold", uidStr, "approve"])
  submit "PUT" (addJSONObject ["password" .= pwd] req)

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/put_teams__tid__features_legalhold
putLegalholdStatus ::
  (HasCallStack, MakesValue tid, MakesValue usr) =>
  tid ->
  usr ->
  -- | the status to put to
  String ->
  App Response
putLegalholdStatus tid usr status = do
  tidStr <- asString tid

  baseRequest usr Galley Versioned (joinHttpPath ["teams", tidStr, "features", "legalhold"])
    >>= submit "PUT"
    . addJSONObject ["status" .= status, "ttl" .= "unlimited"]

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_feature_configs
getFeatureConfigs :: (HasCallStack, MakesValue user) => user -> App Response
getFeatureConfigs user = do
  req <- baseRequest user Galley Versioned "/feature-configs"
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_teams__tid__features
getTeamFeatures :: (HasCallStack, MakesValue user, MakesValue tid) => user -> tid -> App Response
getTeamFeatures user tid = do
  tidStr <- asString tid
  req <- baseRequest user Galley Versioned (joinHttpPath ["teams", tidStr, "features"])
  submit "GET" req

getTeamFeature :: (HasCallStack, MakesValue user, MakesValue tid) => user -> tid -> String -> App Response
getTeamFeature user tid featureName = do
  tidStr <- asString tid
  req <- baseRequest user Galley Versioned (joinHttpPath ["teams", tidStr, "features", featureName])
  submit "GET" req

setTeamFeatureConfig ::
  (HasCallStack, MakesValue user, MakesValue team, MakesValue featureName, MakesValue payload) =>
  user ->
  team ->
  featureName ->
  payload ->
  App Response
setTeamFeatureConfig = setTeamFeatureConfigVersioned Versioned

setTeamFeatureConfigVersioned ::
  (HasCallStack, MakesValue user, MakesValue team, MakesValue featureName, MakesValue payload) =>
  Versioned ->
  user ->
  team ->
  featureName ->
  payload ->
  App Response
setTeamFeatureConfigVersioned versioned user team featureName payload = do
  tid <- asString team
  fn <- asString featureName
  p <- make payload
  req <- baseRequest user Galley versioned $ joinHttpPath ["teams", tid, "features", fn]
  submit "PUT" $ req & addJSON p

-- | http://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_feature_configs
getFeaturesForUser :: (HasCallStack, MakesValue user) => user -> App Response
getFeaturesForUser user = baseRequest user Galley Versioned "feature-configs" >>= submit "GET"

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_teams_notifications
getTeamNotifications :: (HasCallStack, MakesValue user) => user -> Maybe String -> App Response
getTeamNotifications user mSince =
  baseRequest user Galley Versioned "teams/notifications" >>= \req ->
    submit "GET"
      $ addQueryParams [("since", since) | since <- maybeToList mSince] req

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_teams__tid__members_csv
getTeamMembersCsv :: (HasCallStack, MakesValue user) => user -> String -> App Response
getTeamMembersCsv user tid = do
  req <- baseRequest user Galley Versioned (joinHttpPath ["teams", tid, "members", "csv"])
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/post_conversations__cnv_domain___cnv__typing
sendTypingStatus :: (HasCallStack, MakesValue user, MakesValue conv) => user -> conv -> String -> App Response
sendTypingStatus user conv status = do
  convDomain <- objDomain conv
  convId <- objQidObject conv & objId
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", convDomain, convId, "typing"])
  submit "POST"
    $ addJSONObject ["status" .= status] req

updateConversationSelf :: (HasCallStack, MakesValue user, MakesValue conv) => user -> conv -> Value -> App Response
updateConversationSelf user conv payload = do
  (domain, cnv) <- objQid conv
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", domain, cnv, "self"])
  submit "PUT" $ req & addJSON payload

getSelfMember :: (HasCallStack, MakesValue user, MakesValue conv) => user -> conv -> App Response
getSelfMember user conv = do
  (domain, cnv) <- objQid conv
  req <- baseRequest user Galley Versioned (joinHttpPath ["conversations", domain, cnv, "self"])
  submit "GET" req

resetConversation :: (HasCallStack, MakesValue user) => user -> String -> Word64 -> App Response
resetConversation user groupId epoch = do
  req <- baseRequest user Galley Versioned (joinHttpPath ["mls", "reset-conversation"])
  let payload = object ["group_id" .= groupId, "epoch" .= epoch]
  submit "POST" $ req & addJSON payload

addTeamCollaborator :: (MakesValue owner, MakesValue collaborator, HasCallStack) => owner -> String -> collaborator -> [String] -> App Response
addTeamCollaborator owner tid collaborator permissions = do
  req <- baseRequest owner Galley Versioned $ joinHttpPath ["teams", tid, "collaborators"]
  (_, collabId) <- objQid collaborator
  submit "POST"
    $ req
    & addJSONObject
      [ "user" .= collabId,
        "permissions" .= permissions
      ]

getAllTeamCollaborators :: (MakesValue owner) => owner -> String -> App Response
getAllTeamCollaborators owner tid = do
  req <- baseRequest owner Galley Versioned $ joinHttpPath ["teams", tid, "collaborators"]
  submit "GET" req

updateTeamCollaborator :: (MakesValue owner, MakesValue collaborator, HasCallStack) => owner -> String -> collaborator -> [String] -> App Response
updateTeamCollaborator owner tid collaborator permissions = do
  (_, collabId) <- objQid collaborator
  req <- baseRequest owner Galley Versioned $ joinHttpPath ["teams", tid, "collaborators", collabId]
  submit "PUT"
    $ req
    & addJSON permissions

removeTeamCollaborator :: (MakesValue owner, MakesValue collaborator, HasCallStack) => owner -> String -> collaborator -> App Response
removeTeamCollaborator owner tid collaborator = do
  (_, collabId) <- objQid collaborator
  req <- baseRequest owner Galley Versioned $ joinHttpPath ["teams", tid, "collaborators", collabId]
  submit "DELETE" req

data SearchChannels = SearchChannels
  { q :: Maybe String,
    sortOrder :: Maybe String,
    pageSize :: Maybe Int,
    lastName :: Maybe String,
    lastId :: Maybe String,
    discoverable :: Bool
  }

instance Default SearchChannels where
  def =
    SearchChannels
      { q = Nothing,
        sortOrder = Nothing,
        pageSize = Nothing,
        lastName = Nothing,
        lastId = Nothing,
        discoverable = False
      }

searchChannels :: (MakesValue user) => user -> String -> SearchChannels -> App Response
searchChannels user tid args = do
  req <-
    baseRequest
      user
      Galley
      Versioned
      (joinHttpPath ["teams", tid, "channels", "search"])
  submit "GET"
    $ req
    & addQueryParams
      ( mconcat
          [ [("q", q) | q <- toList args.q],
            [("sort_order", o) | o <- toList args.sortOrder],
            [("page_size", show n) | n <- toList args.pageSize],
            [("last_seen_name", n) | n <- toList args.lastName],
            [("last_seen_id", x) | x <- toList args.lastId],
            [("discoverable", "true") | args.discoverable]
          ]
      )

postMeetings :: (HasCallStack, MakesValue user) => user -> Value -> App Response
postMeetings user newMeeting = do
  req <- baseRequest user Galley Versioned "/meetings"
  submit "POST" $ req & addJSON newMeeting

putMeeting :: (HasCallStack, MakesValue user) => user -> String -> String -> Aeson.Value -> App Response
putMeeting user domain meetingId updatedMeeting = do
  req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId])
  submit "PUT" $ req & addJSON updatedMeeting

deleteMeeting :: (HasCallStack, MakesValue user) => user -> String -> String -> App Response
deleteMeeting user domain meetingId = do
  req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId])
  submit "DELETE" req

getMeeting :: (HasCallStack, MakesValue user) => user -> String -> String -> App Response
getMeeting user domain meetingId = do
  req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId])
  submit "GET" req

getMeetingsList :: (HasCallStack, MakesValue user) => user -> App Response
getMeetingsList user = do
  req <- baseRequest user Galley Versioned "/meetings/list"
  submit "GET" req

postMeetingInvitation :: (HasCallStack, MakesValue user) => user -> String -> String -> Aeson.Value -> App Response
postMeetingInvitation user domain meetingId invitation = do
  req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId, "invitations"])
  submit "POST" $ req & addJSON invitation

deleteMeetingInvitation :: (HasCallStack, MakesValue user) => user -> String -> String -> Aeson.Value -> App Response
deleteMeetingInvitation user domain meetingId removeInvitation = do
  req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId, "invitations", "delete"])
  submit "POST" $ req & addJSON removeInvitation

putMeetingInvitation :: (HasCallStack, MakesValue user) => user -> String -> String -> Aeson.Value -> App Response
putMeetingInvitation user domain meetingId invitation = do
  req <- baseRequest user Galley Versioned (joinHttpPath ["meetings", domain, meetingId, "invitations"])
  submit "PUT" $ req & addJSON invitation
