-- 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.Brig where

import API.BrigCommon
import API.Common
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString.Base64 as Base64
import Data.Foldable
import Data.Function
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Vector as V
import GHC.Stack
import Testlib.Prelude

data AddUser = AddUser
  { name :: Maybe String,
    email :: Maybe String,
    teamCode :: Maybe String,
    password :: Maybe String,
    newTeamName :: Maybe String
  }

instance Default AddUser where
  def = AddUser Nothing Nothing Nothing Nothing Nothing

data NewProvider = NewProvider
  { newProviderName :: String,
    newProviderDesc :: String,
    newProviderPassword :: Maybe String,
    newProviderUrl :: String
  }

instance Default NewProvider where
  def =
    NewProvider
      "New Provider"
      "Just a provider"
      Nothing
      "https://example.com"

data NewService = NewService
  { newServiceName :: String,
    newServiceSummary :: String,
    newServiceDescr :: String,
    newServiceUrl :: String,
    newServiceKey :: ByteString,
    newServiceToken :: Maybe String,
    newServiceAssets :: [String],
    newServiceTags :: [String]
  }

instance Default NewService where
  def =
    NewService
      "New Service"
      "Just a service"
      "Just a service description"
      "https://example.com"
      ( T.encodeUtf8 . T.unlines . fmap T.pack $
          [ "-----BEGIN PUBLIC KEY-----",
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu+Kg/PHHU3atXrUbKnw0",
            "G06FliXcNt3lMwl2os5twEDcPPFw/feGiAKymxp+7JqZDrseS5D9THGrW+OQRIPH",
            "WvUBdiLfGrZqJO223DB6D8K2Su/odmnjZJ2z23rhXoEArTplu+Dg9K+c2LVeXTKV",
            "VPOaOzgtAB21XKRiQ4ermqgi3/njr03rXyq/qNkuNd6tNcg+HAfGxfGvvCSYBfiS",
            "bUKr/BeArYRcjzr/h5m1In6fG/if9GEI6m8dxHT9JbY53wiksowy6ajCuqskIFg8",
            "7X883H+LA/d6X5CTiPv1VMxXdBUiGPuC9IT/6CNQ1/LFt0P37ax58+LGYlaFo7la",
            "nQIDAQAB",
            "-----END PUBLIC KEY-----"
          ]
      )
      (Just "secret-token")
      []
      ["music", "quiz", "weather"]

instance ToJSON NewService where
  toJSON NewService {..} =
    Aeson.object
      [ "name" .= newServiceName,
        "summary" .= newServiceSummary,
        "description" .= newServiceDescr,
        "base_url" .= newServiceUrl,
        "public_key" .= (T.unpack . T.decodeUtf8) newServiceKey,
        "auth_token" .= newServiceToken,
        "assets" .= Aeson.Array (V.fromList (Aeson.String . T.pack <$> newServiceAssets)),
        "tags" .= Aeson.Array (V.fromList (Aeson.String . T.pack <$> newServiceTags))
      ]

addUser :: (HasCallStack, MakesValue dom) => dom -> AddUser -> App Response
addUser dom opts = do
  req <- baseRequest dom Brig Versioned "register"
  name <- maybe randomName pure opts.name
  submit "POST" $
    req
      & addClientIP
      & addJSONObject
        ( [ "name" .= name,
            "email" .= opts.email,
            "team_code" .= opts.teamCode,
            "password" .= fromMaybe defPassword opts.password
          ]
            <> ["team" .= object ["name" .= n, "icon" .= "default"] | n <- toList opts.newTeamName]
        )

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_users__uid_domain___uid_
getUser ::
  (HasCallStack, MakesValue user, MakesValue target) =>
  user ->
  target ->
  App Response
getUser user target = do
  (domain, uid) <- objQid target
  req <-
    baseRequest user Brig Versioned $
      joinHttpPath ["users", domain, uid]
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_clients__client_
getClient ::
  (HasCallStack, MakesValue user, MakesValue client) =>
  user ->
  client ->
  App Response
getClient u cli = do
  c <- make cli & asString
  req <-
    baseRequest u Brig Versioned $
      joinHttpPath ["clients", c]
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_clients
getSelfClients ::
  (HasCallStack, MakesValue user) =>
  user ->
  App Response
getSelfClients u =
  baseRequest u Brig Versioned (joinHttpPath ["clients"])
    >>= submit "GET"

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/delete_self
deleteUser :: (HasCallStack, MakesValue user) => user -> App Response
deleteUser user = do
  req <- baseRequest user Brig Versioned "/self"
  submit "DELETE" $
    req & addJSONObject ["password" .= defPassword]

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/post_clients
addClient ::
  (HasCallStack, MakesValue user) =>
  user ->
  AddClient ->
  App Response
addClient user args = do
  req <- baseRequest user Brig Versioned $ "/clients"
  val <- mkAddClientValue args
  submit "POST" $ req & addJSONObject val

data UpdateClient = UpdateClient
  { prekeys :: [Value],
    lastPrekey :: Maybe Value,
    label :: Maybe String,
    capabilities :: Maybe [String],
    mlsPublicKeys :: Maybe Value
  }

instance Default UpdateClient where
  def =
    UpdateClient
      { prekeys = [],
        lastPrekey = Nothing,
        label = Nothing,
        capabilities = Nothing,
        mlsPublicKeys = Nothing
      }

updateClient ::
  (HasCallStack) =>
  ClientIdentity ->
  UpdateClient ->
  App Response
updateClient cid args = do
  req <- baseRequest cid Brig Versioned $ "/clients/" <> cid.client
  submit "PUT" $
    req
      & addJSONObject
        ( ["prekeys" .= args.prekeys]
            <> ["lastkey" .= k | k <- toList args.lastPrekey]
            <> ["label" .= l | l <- toList args.label]
            <> ["capabilities" .= c | c <- toList args.capabilities]
            <> ["mls_public_keys" .= k | k <- toList args.mlsPublicKeys]
        )

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/delete_clients__client_
deleteClient ::
  (HasCallStack, MakesValue user, MakesValue client) =>
  user ->
  client ->
  App Response
deleteClient user client = do
  cid <- objId client
  req <- baseRequest user Brig Versioned $ "/clients/" <> cid
  submit "DELETE" $
    req
      & addJSONObject
        [ "password" .= defPassword
        ]

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_users__uid_domain___uid__clients
--
-- this end-point has been removed from V16, but we keep this helper
-- and call listUserClients under the hood.
getClientsQualified ::
  ( HasCallStack,
    MakesValue user,
    MakesValue domain,
    MakesValue otherUser
  ) =>
  user ->
  domain ->
  otherUser ->
  App Response
getClientsQualified user _domain otherUser = do
  (dom, uid) <- objQid otherUser
  r <- listUsersClients user [otherUser]
  case (r.status, r.json) of
    (200, Just v) -> do
      let lookupKey k (Object m) = KM.lookup (fromString k) m
          lookupKey _ _ = Nothing
          clients = fromMaybe (Array mempty) $ do
            qum <- lookupKey "qualified_user_map" v
            domMap <- lookupKey dom qum
            lookupKey uid domMap
      pure r {json = Just clients}
    _ -> pure r

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/post_users_list_clients
listUsersClients :: (HasCallStack, MakesValue user, MakesValue qualifiedUserIds) => user -> [qualifiedUserIds] -> App Response
listUsersClients usr qualifiedUserIds = do
  qUsers <- mapM objQidObject qualifiedUserIds
  req <- baseRequest usr Brig Versioned $ joinHttpPath ["users", "list-clients"]
  submit "POST" (req & addJSONObject ["qualified_users" .= qUsers])

listUsers :: (HasCallStack, MakesValue user, MakesValue qualifiedUserIds) => user -> [qualifiedUserIds] -> App Response
listUsers usr qualifiedUserIds = do
  qUsers <- mapM objQidObject qualifiedUserIds
  req <- baseRequest usr Brig Versioned $ joinHttpPath ["list-users"]
  submit "POST" (req & addJSONObject ["qualified_ids" .= qUsers])

listUsersWithContactStatus :: (HasCallStack, MakesValue user, MakesValue qualifiedUserIds) => user -> [qualifiedUserIds] -> App Response
listUsersWithContactStatus usr qualifiedUserIds = do
  qUsers <- mapM objQidObject qualifiedUserIds
  req <- baseRequest usr Brig Versioned $ joinHttpPath ["list-users"]
  submit
    "POST"
    ( req
        & addQueryParams [("include-contact-status", "true")]
        & addJSONObject ["qualified_ids" .= qUsers]
    )

data SearchContactsCfg = SearchContactsCfg
  { user :: Value,
    searchTerm :: String,
    domain :: String,
    types :: Maybe [String]
  }
  deriving (Eq, Show)

searchContactsWith ::
  SearchContactsCfg ->
  App Response
searchContactsWith cfg = do
  req <- baseRequest cfg.user Brig Versioned "/search/contacts"
  q <- asString cfg.searchTerm
  d <- objDomain cfg.domain
  let typeFilter = maybe [] f cfg.types
        where
          f :: [String] -> [(String, String)]
          f userTypes = [("type", intercalate "," userTypes)]
  submit "GET" (req & addQueryParams ([("q", q), ("domain", d)] <> typeFilter))

searchContacts ::
  ( MakesValue user,
    MakesValue searchTerm,
    MakesValue domain
  ) =>
  user ->
  searchTerm ->
  domain ->
  App Response
searchContacts user searchTerm domain = do
  u <- make user
  s <- make searchTerm & asString
  d <- make domain & asString
  searchContactsWith (SearchContactsCfg u s d Nothing)

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_teams__tid__search
searchTeam :: (HasCallStack, MakesValue user) => user -> [(String, String)] -> App Response
searchTeam user params = do
  tid <- user %. "team" & asString
  req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", tid, "search"]
  submit "GET" (req & addQueryParams params)

searchTeamWithSearchTerm :: (HasCallStack, MakesValue user) => user -> String -> App Response
searchTeamWithSearchTerm user q = searchTeam user [("q", q)]

searchTeamAll :: (HasCallStack, MakesValue user) => user -> App Response
searchTeamAll user = searchTeam user [("q", ""), ("size", "100"), ("sortby", "created_at"), ("sortorder", "desc")]

setUserSearchable :: (MakesValue user) => user -> String -> Bool -> App Response
setUserSearchable self uid searchable = do
  req <- baseRequest self Brig Versioned $ joinHttpPath ["users", uid, "searchable"]
  submit "POST" $ addJSONObject ["set_searchable" .= searchable] req

getAPIVersion :: (HasCallStack, MakesValue domain) => domain -> App Response
getAPIVersion domain = do
  req <- baseRequest domain Brig Unversioned $ "/api-version"
  submit "GET" req

postConnection ::
  ( HasCallStack,
    MakesValue userFrom,
    MakesValue userTo
  ) =>
  userFrom ->
  userTo ->
  App Response
postConnection userFrom userTo = do
  (userToDomain, userToId) <- objQid userTo
  req <-
    baseRequest userFrom Brig Versioned $
      joinHttpPath ["/connections", userToDomain, userToId]
  submit "POST" req

getConnection ::
  ( HasCallStack,
    MakesValue userFrom,
    MakesValue userTo
  ) =>
  userFrom ->
  userTo ->
  App Response
getConnection userFrom userTo = do
  (userToDomain, userToId) <- objQid userTo
  req <-
    baseRequest userFrom Brig Versioned $
      joinHttpPath ["/connections", userToDomain, userToId]
  submit "GET" req

putConnection ::
  ( HasCallStack,
    MakesValue userFrom,
    MakesValue userTo,
    MakesValue status
  ) =>
  userFrom ->
  userTo ->
  status ->
  App Response
putConnection userFrom userTo status = do
  (userToDomain, userToId) <- objQid userTo
  req <-
    baseRequest userFrom Brig Versioned $
      joinHttpPath ["/connections", userToDomain, userToId]
  statusS <- asString status
  submit "PUT" (req & addJSONObject ["status" .= statusS])

getConnections :: (HasCallStack, MakesValue user) => user -> App Response
getConnections user = do
  req <- baseRequest user Brig Versioned "/list-connections"
  submit "POST" (req & addJSONObject ["size" .= Aeson.Number 500])

uploadKeyPackages :: ClientIdentity -> [ByteString] -> App Response
uploadKeyPackages cid kps = do
  req <-
    baseRequest cid Brig Versioned $
      "/mls/key-packages/self/" <> cid.client
  submit
    "POST"
    (req & addJSONObject ["key_packages" .= map (T.decodeUtf8 . Base64.encode) kps])

claimKeyPackagesWithParams :: (MakesValue u, MakesValue v) => Ciphersuite -> u -> v -> [(String, String)] -> App Response
claimKeyPackagesWithParams suite u v params = do
  (targetDom, targetUid) <- objQid v
  req <-
    baseRequest u Brig Versioned $
      "/mls/key-packages/claim/" <> targetDom <> "/" <> targetUid
  submit "POST" $
    req
      & addQueryParams ([("ciphersuite", suite.code)] <> params)

claimKeyPackages :: (HasCallStack, MakesValue u, MakesValue v) => Ciphersuite -> u -> v -> App Response
claimKeyPackages suite u v = claimKeyPackagesWithParams suite u v []

countKeyPackages :: Ciphersuite -> ClientIdentity -> App Response
countKeyPackages suite cid = do
  req <- baseRequest cid Brig Versioned ("/mls/key-packages/self/" <> cid.client <> "/count")
  submit "GET" $
    req
      & addQueryParams [("ciphersuite", suite.code)]

deleteKeyPackages :: Ciphersuite -> ClientIdentity -> [String] -> App Response
deleteKeyPackages suite cid kps = do
  req <- baseRequest cid Brig Versioned ("/mls/key-packages/self/" <> cid.client)
  submit "DELETE" $
    req
      & addQueryParams [("ciphersuite", suite.code)]
      & addJSONObject ["key_packages" .= kps]

replaceKeyPackages :: ClientIdentity -> [Ciphersuite] -> [ByteString] -> App Response
replaceKeyPackages cid suites kps = do
  req <-
    baseRequest cid Brig Versioned $
      "/mls/key-packages/self/" <> cid.client
  submit "PUT" $
    req
      & addQueryParams [("ciphersuites", intercalate "," (map (.code) suites))]
      & addJSONObject ["key_packages" .= map (T.decodeUtf8 . Base64.encode) kps]

replaceKeyPackagesV7 :: ClientIdentity -> Maybe [Ciphersuite] -> [ByteString] -> App Response
replaceKeyPackagesV7 cid mSuites kps = do
  req <-
    baseRequest cid Brig (ExplicitVersion 7) $
      "/mls/key-packages/self/" <> cid.client
  submit "PUT" $
    req
      & maybe id (\suites -> addQueryParams [("ciphersuites", intercalate "," (map (.code) suites))]) mSuites
      & addJSONObject ["key_packages" .= map (T.decodeUtf8 . Base64.encode) kps]

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_self
getSelf :: (HasCallStack, MakesValue user) => user -> App Response
getSelf = getSelfWithVersion Versioned

getSelfWithVersion :: (HasCallStack, MakesValue user) => Versioned -> user -> App Response
getSelfWithVersion v user = baseRequest user Brig v "/self" >>= submit "GET"

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_self
-- this is a low-level version of `getSelf` for testing some error conditions.
getSelf' :: (HasCallStack) => String -> String -> App Response
getSelf' domain uid = getSelfWithVersion Versioned $ object ["domain" .= domain, "id" .= uid]

data PutSelf = PutSelf
  { accent :: Maybe Int,
    assets :: Maybe [Value], -- [{"key":"string", "size":"string", "type":"string"}]
    name :: Maybe String,
    picture :: Maybe [String]
  }

instance Default PutSelf where
  def = PutSelf Nothing Nothing Nothing Nothing

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/put_self
putSelf :: (HasCallStack, MakesValue caller) => caller -> PutSelf -> App Response
putSelf caller body = do
  req <- baseRequest caller Brig Versioned "/self"
  submit "PUT" $
    req
      & addJSONObject
        [ "accent_id" .= body.accent,
          "assets" .= body.assets,
          "name" .= body.name,
          "picture" .= body.picture
        ]

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/put_self_locale
putSelfLocale :: (HasCallStack, MakesValue caller) => caller -> String -> App Response
putSelfLocale caller locale = do
  req <- baseRequest caller Brig Versioned "/self/locale"
  submit "PUT" $ req & addJSONObject ["locale" .= locale]

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/put_users__uid__email
--
-- NOTE: the full process of changing (and confirming) the email address is more complicated.
-- see /services/brig/test/integration for details.
putUserEmail :: (HasCallStack, MakesValue caller, MakesValue target) => caller -> target -> String -> App Response
putUserEmail caller target emailAddress = do
  uid <- asString $ target %. "id"
  req <- baseRequest caller Brig Versioned $ joinHttpPath ["users", uid, "email"]
  submit "PUT" $ req & addJSONObject ["email" .= emailAddress]

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/delete_self_email
deleteSelfEmail :: (HasCallStack, MakesValue caller) => caller -> App Response
deleteSelfEmail caller = do
  req <- baseRequest caller Brig Versioned $ joinHttpPath ["self", "email"]
  submit "DELETE" req

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/put_self_handle
-- FUTUREWORK: rename to putSelfHandle for consistency
putHandle :: (HasCallStack, MakesValue user) => user -> String -> App Response
putHandle user handle = do
  req <- baseRequest user Brig Versioned "/self/handle"
  submit "PUT" $
    req & addJSONObject ["handle" .= handle]

putPassword :: (MakesValue user) => user -> String -> String -> App Response
putPassword user oldPassword newPassword = do
  req <- baseRequest user Brig Versioned "/self/password"
  submit "PUT" $
    req
      & addJSONObject
        [ "old_password" .= oldPassword,
          "new_password" .= newPassword
        ]

getUserSupportedProtocols ::
  (HasCallStack, MakesValue user, MakesValue target) =>
  user ->
  target ->
  App Response
getUserSupportedProtocols user target = do
  (domain, uid) <- objQid target
  req <-
    baseRequest user Brig Versioned $
      joinHttpPath ["users", domain, uid, "supported-protocols"]
  submit "GET" req

putUserSupportedProtocols ::
  (HasCallStack, MakesValue user) =>
  user ->
  [String] ->
  App Response
putUserSupportedProtocols user ps = do
  req <-
    baseRequest user Brig Versioned $
      joinHttpPath ["self", "supported-protocols"]
  submit "PUT" (req & addJSONObject ["supported_protocols" .= ps])

data PostInvitation = PostInvitation
  { email :: Maybe String,
    role :: Maybe String
  }

instance Default PostInvitation where
  def = PostInvitation Nothing Nothing

postInvitation ::
  (HasCallStack, MakesValue user) =>
  user ->
  PostInvitation ->
  App Response
postInvitation user inv = do
  tid <- user %. "team" & asString
  req <-
    baseRequest user Brig Versioned $
      joinHttpPath ["teams", tid, "invitations"]
  email <- maybe randomEmail pure inv.email
  submit "POST" $
    req & addJSONObject (["email" .= email] <> ["role" .= r | r <- toList inv.role])

getApiVersions :: (HasCallStack) => App Response
getApiVersions = do
  req <-
    rawBaseRequest OwnDomain Brig Unversioned $
      joinHttpPath ["api-version"]
  submit "GET" req

getSwaggerPublicTOC :: (HasCallStack) => App Response
getSwaggerPublicTOC = do
  req <-
    rawBaseRequest OwnDomain Brig Unversioned $
      joinHttpPath ["api", "swagger-ui"]
  submit "GET" req

getSwaggerPublicAllUI :: (HasCallStack) => Int -> App Response
getSwaggerPublicAllUI version = do
  req <-
    rawBaseRequest OwnDomain Brig (ExplicitVersion version) $
      joinHttpPath ["api", "swagger-ui"]
  submit "GET" req

getSwaggerPublicAllJson :: (HasCallStack) => Int -> App Response
getSwaggerPublicAllJson version = do
  req <-
    rawBaseRequest OwnDomain Brig (ExplicitVersion version) $
      joinHttpPath ["api", "swagger.json"]
  submit "GET" req

getSwaggerInternalUI :: (HasCallStack) => String -> App Response
getSwaggerInternalUI service = do
  req <-
    rawBaseRequest OwnDomain Brig Unversioned $
      joinHttpPath ["api-internal", "swagger-ui", service]
  submit "GET" req

getSwaggerInternalJson :: (HasCallStack) => String -> App Response
getSwaggerInternalJson service = do
  req <-
    rawBaseRequest OwnDomain Nginz Unversioned $
      joinHttpPath ["api-internal", "swagger-ui", service <> "-swagger.json"]
  submit "GET" req

newProvider ::
  ( HasCallStack,
    MakesValue provider,
    MakesValue user
  ) =>
  user ->
  provider ->
  App Value
newProvider user provider = do
  p <- make provider
  req <-
    baseRequest user Brig Versioned $
      joinHttpPath ["provider", "register"]
  submit "POST" (req & addJSON p & addClientIP) >>= getJSON 201

getProvider ::
  (HasCallStack, MakesValue domain) =>
  domain ->
  String ->
  App Response
getProvider domain pid = do
  req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["provider"]
  submit "GET" $
    req
      & zType "provider"
      & zProvider pid

activateProvider ::
  ( HasCallStack,
    MakesValue dom
  ) =>
  dom ->
  String ->
  String ->
  App ()
activateProvider dom key code = do
  d <- make dom
  req <-
    rawBaseRequest d Brig Versioned $
      joinHttpPath ["provider", "activate"]
  let ps = [("key", key), ("code", code)]
  submit "GET" (addQueryParams ps req) `bindResponse` \resp -> do
    resp.status `shouldMatchOneOf` [Number 200, Number 204]

activateUserV5 :: (HasCallStack, MakesValue dom, MakesValue bdy) => dom -> bdy -> App Response
activateUserV5 dom bdy = do
  b <- make bdy
  req <- rawBaseRequest dom Brig (ExplicitVersion 5) $ joinHttpPath ["activate", "send"]
  submit "POST" $ (addJSON b req)

-- | Returns the value of the Set-Cookie header that is to be used to
-- authenticate to provider endpoints.
loginProvider ::
  ( HasCallStack,
    MakesValue dom
  ) =>
  dom ->
  String ->
  String ->
  App Response
loginProvider dom email pass = do
  d <- asString dom
  req <-
    rawBaseRequest d Brig Versioned $
      joinHttpPath ["provider", "login"]
  submit "POST" (addJSONObject ["email" .= email, "password" .= pass] req)

requestProviderPasswordResetCode ::
  (HasCallStack, MakesValue domain) =>
  domain ->
  String ->
  App Response
requestProviderPasswordResetCode domain email = do
  req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["provider", "password-reset"]
  submit "POST" $ req & addJSONObject ["email" .= email]

completeProviderPasswordReset ::
  (HasCallStack, MakesValue domain) =>
  domain ->
  Value ->
  String ->
  App Response
completeProviderPasswordReset domain resetCode newPassword = do
  req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["provider", "password-reset", "complete"]
  body <- make (setField "password" newPassword resetCode)
  submit "POST" $ req & addJSON body

requestProviderEmailUpdateCode ::
  (HasCallStack, MakesValue domain) =>
  domain ->
  String ->
  String ->
  App Response
requestProviderEmailUpdateCode domain pid newEmail = do
  req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["provider", "email"]
  submit "PUT" $
    req
      & zType "provider"
      & zProvider pid
      & addJSONObject ["email" .= newEmail]

newService ::
  ( HasCallStack,
    MakesValue dom
  ) =>
  dom ->
  String ->
  NewService ->
  App Value
newService dom providerId service = do
  s <- make service
  domain <- asString dom
  req <-
    rawBaseRequest domain Brig Versioned $
      joinHttpPath ["provider", "services"]
  let addHdrs =
        addHeader "Z-Type" "provider"
          . addHeader "Z-Provider" providerId
  submit "POST" (addJSON s . addHdrs $ req) >>= getJSON 201

getService :: (HasCallStack, MakesValue domain) => domain -> String -> String -> App Response
getService domain pid sid = do
  req <- baseRequest domain Brig Versioned $ joinHttpPath ["provider", "services", sid]
  submit "GET" $
    req
      & addHeader "Z-Type" "provider"
      & addHeader "Z-Provider" pid

updateService ::
  ( HasCallStack,
    MakesValue dom,
    MakesValue serviceId
  ) =>
  dom ->
  String ->
  serviceId ->
  Maybe String ->
  Maybe String ->
  App Response
updateService dom providerId serviceId mAcceptHeader newName = do
  sId <- asString serviceId
  domain <- asString dom
  req <-
    rawBaseRequest domain Brig Versioned $
      joinHttpPath ["provider", "services", sId]
  let addHdrs =
        zType "provider"
          . zProvider providerId
          . maybe id (addHeader "Accept") mAcceptHeader
  submit "PUT"
    . addHdrs
    . addJSONObject ["name" .= n | n <- maybeToList newName]
    $ req

updateServiceConn ::
  ( HasCallStack,
    MakesValue domain,
    MakesValue conn
  ) =>
  domain ->
  -- | providerId
  String ->
  -- | serviceId
  String ->
  -- | connection update as a Json object, with an obligatory "password" field
  conn ->
  App Response
updateServiceConn domain providerId serviceId connectionUpdate = do
  req <- baseRequest domain Brig Versioned do
    joinHttpPath ["provider", "services", serviceId, "connection"]
  upd <- make connectionUpdate
  submit "PUT"
    . zType "provider"
    . zProvider providerId
    . addJSON upd
    $ req

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_users__uid_domain___uid__prekeys__client_
getUsersPrekeysClient :: (HasCallStack, MakesValue caller, MakesValue targetUser) => caller -> targetUser -> String -> App Response
getUsersPrekeysClient caller targetUser targetClient = do
  dom <- asString $ targetUser %. "domain"
  uid <- asString $ targetUser %. "id"
  req <- baseRequest caller Brig Versioned $ joinHttpPath ["users", dom, uid, "prekeys", targetClient]
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_users__uid_domain___uid__prekeys
getUsersPrekeyBundle :: (HasCallStack, MakesValue caller, MakesValue targetUser) => caller -> targetUser -> App Response
getUsersPrekeyBundle caller targetUser = do
  dom <- asString $ targetUser %. "domain"
  uid <- asString $ targetUser %. "id"
  req <- baseRequest caller Brig Versioned $ joinHttpPath ["users", dom, uid, "prekeys"]
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/post_users_list_prekeys
getMultiUserPrekeyBundle :: (HasCallStack, MakesValue caller, ToJSON userClients) => caller -> userClients -> App Response
getMultiUserPrekeyBundle caller userClients = do
  req <- baseRequest caller Brig Versioned $ joinHttpPath ["users", "list-prekeys"]
  submit "POST" (addJSON userClients req)

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/post_access
renewToken :: (HasCallStack, MakesValue uid) => uid -> String -> App Response
renewToken caller cookie = do
  req <- baseRequest caller Brig Versioned "access"
  submit "POST" (addHeader "Cookie" ("zuid=" <> cookie) req)

-- | https://staging-nginz-https.zinfra.io/v5/api/swagger-ui/#/default/get_calls_config_v2
getCallsConfigV2 :: (HasCallStack, MakesValue user) => user -> App Response
getCallsConfigV2 user = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["calls", "config", "v2"]
  submit "GET" req

addBot :: (HasCallStack, MakesValue user) => user -> String -> String -> String -> App Response
addBot user providerId serviceId convId = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["bot", "conversations", convId]
  submit "POST" $
    req
      & zType "access"
      & addJSONObject ["provider" .= providerId, "service" .= serviceId]

setProperty :: (MakesValue user, ToJSON val) => user -> String -> val -> App Response
setProperty user propName val = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["properties", propName]
  submit "PUT" $ req & addJSON val

getProperty :: (MakesValue user) => user -> String -> App Response
getProperty user propName = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["properties", propName]
  submit "GET" req

deleteProperty :: (MakesValue user) => user -> String -> App Response
deleteProperty user propName = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["properties", propName]
  submit "DELETE" req

getAllPropertyNames :: (MakesValue user) => user -> App Response
getAllPropertyNames user = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["properties"]
  submit "GET" req

getAllPropertyValues :: (MakesValue user) => user -> App Response
getAllPropertyValues user = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["properties-values"]
  submit "GET" req

clearProperties :: (MakesValue user) => user -> App Response
clearProperties user = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["properties"]
  submit "DELETE" req

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/post_oauth_authorization_codes
generateOAuthAuthorizationCode :: (HasCallStack, MakesValue user, MakesValue cid) => user -> cid -> [String] -> String -> App Response
generateOAuthAuthorizationCode user cid scopes redirectUrl = do
  cidStr <- asString cid
  req <- baseRequest user Brig Versioned "/oauth/authorization/codes"
  submit "POST" $
    req
      & addJSONObject
        [ "client_id" .= cidStr,
          "scope" .= unwords scopes,
          "redirect_uri" .= redirectUrl,
          "code_challenge" .= "G7CWLBqYDT8doT_oEIN3un_QwZWYKHmOqG91nwNzITc",
          "code_challenge_method" .= "S256",
          "response_type" .= "code",
          "state" .= "abc"
        ]

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/post_oauth_token
createOAuthAccessToken :: (HasCallStack, MakesValue user, MakesValue cid) => user -> cid -> String -> String -> App Response
createOAuthAccessToken user cid code redirectUrl = do
  cidStr <- asString cid
  req <- baseRequest user Brig Versioned "/oauth/token"
  submit "POST" $
    req
      & addUrlEncodedForm
        [ ("grant_type", "authorization_code"),
          ("client_id", cidStr),
          ("code_verifier", "nE3k3zykOmYki~kriKzAmeFiGT7cWugcuToFwo1YPgrZ1cFvaQqLa.dXY9MnDj3umAmG-8lSNIYIl31Cs_.fV5r2psa4WWZcB.Nlc3A-t3p67NDZaOJjIiH~8PvUH_hR"),
          ("code", code),
          ("redirect_uri", redirectUrl)
        ]

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/post_oauth_token
createOAuthAccessTokenWithRefreshToken :: (HasCallStack, MakesValue user, MakesValue cid) => user -> cid -> String -> App Response
createOAuthAccessTokenWithRefreshToken user cid token = do
  cidStr <- asString cid
  req <- baseRequest user Brig Versioned "/oauth/token"
  submit "POST" $
    req
      & addUrlEncodedForm
        [ ("grant_type", "refresh_token"),
          ("client_id", cidStr),
          ("refresh_token", token)
        ]

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/get_oauth_applications
getOAuthApplications :: (HasCallStack, MakesValue user) => user -> App Response
getOAuthApplications user = do
  req <- baseRequest user Brig Versioned "/oauth/applications"
  submit "GET" req

deleteOAuthSession :: (HasCallStack, MakesValue user, MakesValue cid) => user -> cid -> String -> String -> App Response
deleteOAuthSession user cid password tokenId = do
  cidStr <- asString cid
  req <- baseRequest user Brig Versioned $ joinHttpPath ["oauth", "applications", cidStr, "sessions", tokenId]
  submit "DELETE" $ req & addJSONObject ["password" .= password]

-- | https://staging-nginz-https.zinfra.io/v6/api/swagger-ui/#/default/delete_oauth_applications__OAuthClientId_
revokeApplicationAccessV6 :: (HasCallStack, MakesValue user, MakesValue cid) => user -> cid -> App Response
revokeApplicationAccessV6 user cid = do
  cidStr <- asString cid
  req <- baseRequest user Brig (ExplicitVersion 6) $ joinHttpPath ["oauth", "applications", cidStr]
  submit "DELETE" req

revokeApplicationAccess :: (HasCallStack, MakesValue user, MakesValue cid) => user -> cid -> String -> App Response
revokeApplicationAccess user cid password = do
  cidStr <- asString cid
  req <- baseRequest user Brig Versioned $ joinHttpPath ["oauth", "applications", cidStr, "sessions"]
  submit "DELETE" $ req & addJSONObject ["password" .= password]

registerUser :: (HasCallStack, MakesValue domain) => domain -> String -> String -> App Response
registerUser domain email inviteeCode = registerUserWith domain email inviteeCode "Alice"

registerUserWith :: (HasCallStack, MakesValue domain) => domain -> String -> String -> String -> App Response
registerUserWith = registerUserWithVersioned Versioned

registerUserWithVersioned :: (HasCallStack, MakesValue domain) => Versioned -> domain -> String -> String -> String -> App Response
registerUserWithVersioned versioned domain email inviteeCode name = do
  req <- baseRequest domain Brig versioned "register"
  submit "POST" $
    req
      & addClientIP
      & addJSONObject
        [ "name" .= name,
          "email" .= email,
          "password" .= defPassword,
          "team_code" .= inviteeCode
        ]

activate :: (HasCallStack, MakesValue domain) => domain -> String -> String -> App Response
activate domain key code = do
  req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["activate"]
  submit "GET" $
    req
      & addQueryParams [("key", key), ("code", code)]

activateSend :: (HasCallStack, MakesValue domain) => domain -> String -> Maybe String -> App Response
activateSend domain email locale = do
  req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["activate", "send"]
  submit "POST" $ req & addJSONObject (["email" .= email] <> maybeToList (((.=) "locale") <$> locale))

-- https://staging-nginz-https.zinfra.io/v16/api/swagger-ui/#/default/get-team-size
getTeamSize :: (HasCallStack, MakesValue user) => user -> String -> App Response
getTeamSize user tid = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", tid, "size"]
  submit "GET" req

acceptTeamInvitation :: (HasCallStack, MakesValue user) => user -> String -> Maybe String -> App Response
acceptTeamInvitation user code mPw = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", "invitations", "accept"]
  submit "POST" $ req & addJSONObject (["code" .= code] <> maybeToList (((.=) "password") <$> mPw))

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

-- | https://staging-nginz-https.zinfra.io/v7/api/swagger-ui/#/default/get-team-invitation-info
getInvitationByCode :: (HasCallStack, MakesValue user) => user -> String -> App Response
getInvitationByCode user code = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", "invitations", "info"]
  submit "GET" (req & addQueryParams [("code", code)])

passwordReset :: (HasCallStack, MakesValue domain) => domain -> String -> App Response
passwordReset domain email = do
  req <- baseRequest domain Brig Versioned "password-reset"
  submit "POST" $ req & addJSONObject ["email" .= email]

completePasswordReset :: (HasCallStack, MakesValue domain) => domain -> String -> String -> String -> App Response
completePasswordReset domain key code pw = do
  req <- baseRequest domain Brig Versioned $ joinHttpPath ["password-reset", "complete"]
  submit "POST" $ req & addJSONObject ["key" .= key, "code" .= code, "password" .= pw]

login :: (HasCallStack, MakesValue domain) => domain -> String -> String -> App Response
login domain email password = do
  req <- baseRequest domain Brig Versioned "login"
  submit "POST" $ req & addJSONObject ["email" .= email, "password" .= password] & addQueryParams [("persist", "true")]

loginWithSessionCookie :: (HasCallStack, MakesValue domain) => domain -> String -> String -> App Response
loginWithSessionCookie domain email password = do
  req <- baseRequest domain Brig Versioned "login"
  submit "POST" $ req & addJSONObject ["email" .= email, "password" .= password]

updateEmail :: (HasCallStack, MakesValue user) => user -> String -> String -> String -> App Response
updateEmail user email cookie token = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["access", "self", "email"]
  submit "PUT" $ req & addJSONObject ["email" .= email] & setCookie cookie & addHeader "Authorization" ("Bearer " <> token)

upgradePersonalToTeam :: (HasCallStack, MakesValue user) => user -> String -> App Response
upgradePersonalToTeam user name = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["upgrade-personal-to-team"]
  submit "POST" $ req & addJSONObject ["name" .= name, "icon" .= "default"]

postServiceWhitelist ::
  ( HasCallStack,
    MakesValue user,
    MakesValue tid,
    MakesValue update
  ) =>
  user ->
  tid ->
  update ->
  App Response
postServiceWhitelist user tid update = do
  tidStr <- asString tid
  updateJson <- make update
  req <-
    baseRequest user Brig Versioned $
      joinHttpPath
        [ "teams",
          tidStr,
          "services",
          "whitelist"
        ]
  submit "POST" (addJSON updateJson req)

getDomainVerificationChallenge :: (HasCallStack, MakesValue domain) => domain -> String -> App Response
getDomainVerificationChallenge domain emailDomain = do
  req <- baseRequest domain Brig Versioned $ joinHttpPath ["domain-verification", emailDomain, "challenges"]
  submit "POST" req

verifyDomain :: (HasCallStack, MakesValue domain) => domain -> String -> String -> String -> App Response
verifyDomain domain emailDomain challengeId challengeToken = do
  req <-
    baseRequest domain Brig Versioned $
      joinHttpPath
        [ "domain-verification",
          emailDomain,
          "challenges",
          challengeId
        ]
  submit "POST" $ req & addJSONObject ["challenge_token" .= challengeToken]

verifyDomainForTeam :: (HasCallStack, MakesValue user) => user -> String -> String -> String -> App Response
verifyDomainForTeam user emailDomain challengeId challengeToken = do
  req <-
    baseRequest user Brig Versioned $
      joinHttpPath
        [ "domain-verification",
          emailDomain,
          "team",
          "challenges",
          challengeId
        ]
  submit "POST" $ req & addJSONObject ["challenge_token" .= challengeToken]

authorizeTeam :: (HasCallStack, MakesValue user) => user -> String -> String -> App Response
authorizeTeam user emailDomain ownershipToken = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["domain-verification", emailDomain, "authorize-team"]
  submit "POST" $ req & addJSONObject ["domain_ownership_token" .= ownershipToken]

-- brig expects an auth-token for this request. @mAuthToken@ is only `Maybe` for testing error cases!
updateDomainRedirect :: (HasCallStack, MakesValue domain) => domain -> Versioned -> String -> Maybe String -> Value -> App Response
updateDomainRedirect domain apiVersion emailDomain mAuthToken config = do
  req <-
    baseRequest domain Brig apiVersion $
      joinHttpPath ["domain-verification", emailDomain, "backend"]
  let req' = case mAuthToken of
        Just authToken -> addHeader "Authorization" ("Bearer " <> authToken) req
        Nothing -> req
  submit "POST" $ req' & addJSON config

-- | Generates a backend redirect `config` `Value` for `updateDomainRedirect`
mkDomainRedirectBackend :: Versioned -> String -> String -> Value
mkDomainRedirectBackend (ExplicitVersion v) configUrl _webappUrl
  | v <= 8 =
      object ["domain_redirect" .= "backend", "backend_url" .= configUrl]
mkDomainRedirectBackend _v configUrl webappUrl =
  object
    [ "domain_redirect" .= "backend",
      "backend" .= object ["config_url" .= configUrl, "webapp_url" .= webappUrl]
    ]

updateTeamInvite :: (HasCallStack, MakesValue user, MakesValue payload) => user -> String -> payload -> App Response
updateTeamInvite user emailDomain payload = do
  req <-
    baseRequest user Brig Versioned $
      joinHttpPath ["domain-verification", emailDomain, "team"]
  p <- make payload
  submit "POST" $ req & addJSON p

getDomainRegistrationFromEmail :: (HasCallStack, MakesValue domain) => domain -> Versioned -> String -> App Response
getDomainRegistrationFromEmail domain version email = do
  req <- baseRequest domain Brig version $ joinHttpPath ["get-domain-registration"]
  submit "POST" $ req & addJSONObject ["email" .= email]

getRegisteredDomainsByTeam :: (HasCallStack, MakesValue user) => user -> String -> App Response
getRegisteredDomainsByTeam user tid = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", tid, "registered-domains"]
  submit "GET" req

deleteRegisteredTeamDomain :: (HasCallStack, MakesValue user) => user -> String -> String -> App Response
deleteRegisteredTeamDomain user tid registeredDomain = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", tid, "registered-domains", registeredDomain]
  submit "DELETE" req

listTeamServiceProfilesByPrefix :: (MakesValue user) => user -> String -> Maybe String -> Bool -> Int -> App Response
listTeamServiceProfilesByPrefix user tid mPrefix filterDisabled size = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["teams", tid, "services", "whitelisted"]
  submit "GET" $
    req
      & addQueryParams
        ( catMaybes
            [ ("prefix",) <$> mPrefix,
              if filterDisabled then Nothing else Just ("filter_disabled", "false"),
              Just ("size", show size)
            ]
        )

createUserGroup :: (MakesValue user, MakesValue newUserGroup) => user -> newUserGroup -> App Response
createUserGroup user newUserGroup = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups"]
  body <- make newUserGroup
  submit "POST" $ req & addJSON body

getUserGroup :: (MakesValue user) => user -> String -> App Response
getUserGroup user gid = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid]
  submit "GET" req

getUserGroupWithChannels :: (MakesValue user) => user -> String -> App Response
getUserGroupWithChannels user gid = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid]
  submit "GET" $ req & addQueryParams [("include_channels", "true")]

updateUserGroupChannels :: (MakesValue user) => user -> String -> [String] -> App Response
updateUserGroupChannels user gid convIds = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid, "channels"]
  submit "PUT" $ req & addJSONObject ["channels" .= convIds]

data GetUserGroupsArgs = GetUserGroupsArgs
  { q :: Maybe String,
    sortByKeys :: Maybe String,
    sortOrder :: Maybe String,
    pSize :: Maybe Int,
    lastName :: Maybe String,
    lastCreatedAt :: Maybe String,
    lastId :: Maybe String,
    includeMemberCount :: Bool,
    includeChannels :: Bool
  }

instance Default GetUserGroupsArgs where
  def =
    GetUserGroupsArgs
      { q = Nothing,
        sortByKeys = Nothing,
        sortOrder = Nothing,
        pSize = Nothing,
        lastName = Nothing,
        lastCreatedAt = Nothing,
        lastId = Nothing,
        includeMemberCount = False,
        includeChannels = False
      }

getUserGroups :: (MakesValue user) => user -> GetUserGroupsArgs -> App Response
getUserGroups user GetUserGroupsArgs {..} = do
  req <- baseRequest user Brig Versioned "user-groups"
  submit "GET" $
    req
      & addQueryParams
        ( catMaybes
            [ ("q",) <$> q,
              ("sort_by",) <$> sortByKeys,
              ("sort_order",) <$> sortOrder,
              (("page_size",) . show) <$> pSize,
              ("last_seen_name",) <$> lastName,
              ("last_seen_created_at",) <$> lastCreatedAt,
              ("last_seen_id",) <$> lastId,
              (if includeMemberCount then Just ("include_member_count", "true") else Nothing),
              (if includeChannels then Just ("include_channels", "true") else Nothing)
            ]
        )

updateUserGroup :: (MakesValue user, MakesValue userGroupUpdate) => user -> String -> userGroupUpdate -> App Response
updateUserGroup user gid userGroupUpdate = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid]
  body <- make userGroupUpdate
  submit "PUT" $ req & addJSON body

deleteUserGroup :: (MakesValue user) => user -> String -> App Response
deleteUserGroup user gid = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid]
  submit "DELETE" req

addUserToGroup :: (MakesValue user) => user -> String -> String -> App Response
addUserToGroup user gid uid = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid, "users", uid]
  submit "POST" req

addUsersToGroup :: (MakesValue user) => user -> String -> [String] -> App Response
addUsersToGroup user gid uids = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid, "users"]
  submit "POST" $
    req
      & addJSONObject
        [ "members" .= uids
        ]

updateUserGroupUsers :: (MakesValue user) => user -> String -> [String] -> App Response
updateUserGroupUsers user gid uids = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid, "users"]
  submit "PUT" $
    req
      & addJSONObject
        [ "members" .= uids
        ]

removeUserFromGroup :: (MakesValue user) => user -> String -> String -> App Response
removeUserFromGroup user gid uid = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["user-groups", gid, "users", uid]
  submit "DELETE" req

data NewApp = NewApp
  { name :: String,
    assets :: Maybe [Value],
    accentId :: Maybe Int,
    category :: String,
    description :: String
  }

instance Default NewApp where
  def =
    NewApp
      { name = "default name",
        assets = Nothing,
        accentId = Nothing,
        category = "other",
        description = "default description"
      }

createApp :: (MakesValue creator) => creator -> String -> NewApp -> App Response
createApp creator tid new = do
  req <- baseRequest creator Brig Versioned $ joinHttpPath ["teams", tid, "apps"]
  submit "POST" $
    req
      & addJSONObject
        [ "name" .= new.name,
          "assets" .= new.assets,
          "accent_id" .= new.accentId,
          "category" .= new.category,
          "description" .= new.description,
          "password" .= defPassword
        ]

-- | https://staging-nginz-https.zinfra.io/v14/api/swagger-ui/#/default/get-app
getApp :: (MakesValue self, MakesValue app) => self -> String -> app -> App Response
getApp self tid appId = do
  appIdStr <- asString appId
  req <-
    baseRequest self Brig (ExplicitVersion 15 {- use `POST /list-users` from v16 onwards -}) $
      joinHttpPath ["teams", tid, "apps", appIdStr]
  submit "GET" req

-- | https://staging-nginz-https.zinfra.io/v14/api/swagger-ui/#/default/get-apps
getApps :: (MakesValue self) => self -> String -> App Response
getApps self tid = do
  req <- baseRequest self Brig Versioned $ joinHttpPath ["teams", tid, "apps"]
  submit "GET" req

putAppMetadata :: (HasCallStack, MakesValue user, MakesValue app) => String -> user -> app -> Value -> App Response
putAppMetadata tid owner appId appMetadata = do
  appIdStr <- asString appId
  let path = joinHttpPath ["teams", tid, "apps", appIdStr]
  req <- baseRequest owner Brig Versioned path
  submit "PUT" (req & addJSON appMetadata)

refreshAppCookie :: (MakesValue u) => u -> String -> String -> Maybe Value -> App Response
refreshAppCookie u tid appId mbBody = do
  req <- baseRequest u Brig Versioned $ joinHttpPath ["teams", tid, "apps", appId, "cookies"]
  submit "POST" $ req & maybe id addJSON mbBody

-- | https://staging-nginz-https.zinfra.io/v12/api/swagger-ui/#/default/check-user-handle
checkHandle :: (MakesValue user) => user -> String -> App Response
checkHandle self handle = do
  req <- baseRequest self Brig Versioned (joinHttpPath ["handles", handle])
  submit "HEAD" req

-- | https://staging-nginz-https.zinfra.io/v12/api/swagger-ui/#/default/check-user-handles
checkHandles :: (MakesValue user) => user -> [String] -> App Response
checkHandles self handles = do
  req <- baseRequest self Brig Versioned (joinHttpPath ["handles"]) <&> addJSONObject ["handles" .= handles]
  submit "POST" req

getCookies :: (MakesValue user) => user -> [String] -> App Response
getCookies user labels = do
  let param = intercalate "," labels
  req <- baseRequest user Brig Versioned "cookies"
  submit "GET" (req & addQueryParams [("labels", param)])

getSystemSettingsPublic :: (MakesValue domain) => domain -> App Response
getSystemSettingsPublic domain = do
  req <- rawBaseRequest domain Brig Versioned $ joinHttpPath ["system", "settings", "unauthorized"]
  submit "GET" req

getSystemSettingsInternal :: (MakesValue user) => user -> App Response
getSystemSettingsInternal user = do
  req <- baseRequest user Brig Versioned $ joinHttpPath ["system", "settings"]
  submit "GET" req
