{-# 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 Test.One2OneTeamConv where

import API.Galley
import Notifications (isTeamMemberJoinNotif)
import SetupHelpers
import Testlib.Prelude

-- | Team member roles that the one2one team conversation test is parametrized
-- over (the standard non-owner roles). Each constructor is enumerated into a
-- separate test case by the test discovery machinery, mirroring the way the
-- original galley test invoked @testCreateOne2OneWithMembers@ once per role.
data One2OneRole = One2OneMember | One2OnePartner
  deriving stock ((forall x. One2OneRole -> Rep One2OneRole x)
-> (forall x. Rep One2OneRole x -> One2OneRole)
-> Generic One2OneRole
forall x. Rep One2OneRole x -> One2OneRole
forall x. One2OneRole -> Rep One2OneRole x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. One2OneRole -> Rep One2OneRole x
from :: forall x. One2OneRole -> Rep One2OneRole x
$cto :: forall x. Rep One2OneRole x -> One2OneRole
to :: forall x. Rep One2OneRole x -> One2OneRole
Generic, One2OneRole -> One2OneRole -> Bool
(One2OneRole -> One2OneRole -> Bool)
-> (One2OneRole -> One2OneRole -> Bool) -> Eq One2OneRole
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: One2OneRole -> One2OneRole -> Bool
== :: One2OneRole -> One2OneRole -> Bool
$c/= :: One2OneRole -> One2OneRole -> Bool
/= :: One2OneRole -> One2OneRole -> Bool
Eq, Int -> One2OneRole -> ShowS
[One2OneRole] -> ShowS
One2OneRole -> String
(Int -> One2OneRole -> ShowS)
-> (One2OneRole -> String)
-> ([One2OneRole] -> ShowS)
-> Show One2OneRole
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> One2OneRole -> ShowS
showsPrec :: Int -> One2OneRole -> ShowS
$cshow :: One2OneRole -> String
show :: One2OneRole -> String
$cshowList :: [One2OneRole] -> ShowS
showList :: [One2OneRole] -> ShowS
Show)

-- | Map a parametrized role to the team-role string accepted by
-- 'createTeamMember' (see @Wire.API.Team.Role@: @member@, @partner@). The
-- original galley test covered @RoleMember@ and @RoleExternalPartner@.
one2OneRoleName :: One2OneRole -> String
one2OneRoleName :: One2OneRole -> String
one2OneRoleName = \case
  One2OneRole
One2OneMember -> String
"member"
  One2OneRole
One2OnePartner -> String
"partner"

-- | An owner adds a second team member with the given role and creates a
-- binding one2one team conversation with them. Ported from
-- @services/galley/test/integration/API/Teams.hs@
-- (@testCreateOne2OneWithMembers@).
testCreateOne2OneWithMembers :: (HasCallStack) => One2OneRole -> App ()
testCreateOne2OneWithMembers :: HasCallStack => One2OneRole -> App ()
testCreateOne2OneWithMembers One2OneRole
memberRole = do
  (owner, tid, []) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
  teamMember <-
    withWebSockets [owner] $ \[WebSocket
wsOwner] -> do
      m <- Value -> CreateTeamMember -> App Value
forall inviter.
(HasCallStack, MakesValue inviter) =>
inviter -> CreateTeamMember -> App Value
createTeamMember Value
owner CreateTeamMember
forall a. Default a => a
def {role = one2OneRoleName memberRole}
      -- The owner is notified of the new member joining the team. This mirrors
      -- @checkTeamMemberJoin@ in the galley test suite.
      memberJoin <- awaitMatch isTeamMemberJoinNotif wsOwner
      memberJoin %. "payload.0.team" `shouldMatch` tid
      memberJoin %. "payload.0.data.user" `shouldMatch` objId m
      -- The original test additionally asserts a @team.update@ event via the
      -- galley SQS team-event queue (@assertTeamUpdate tid 2 [owner]@). That
      -- queue is galley-test-specific and has no websocket / Testlib
      -- equivalent (galley only pushes @team.member-join@ over websockets on a
      -- member join), so we verify the updated team membership directly.
      bindResponse (getTeamMembers owner tid) $ \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
        members <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members" App Value -> (Value -> App [Value]) -> App [Value]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
        length members `shouldMatchInt` 2
      pure m
  -- Creating the one2one team conversation is eventually consistent: retry
  -- until it returns 201.
  eventually $ postOne2OneConversation owner teamMember tid "chit-chat" >>= assertStatus 201
  -- Recreating the one2one is a no-op and returns 200.
  bindResponse (postOne2OneConversation owner teamMember tid "chit-chat") $ \Response
resp ->
    Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200

-- | Two owners each create their own binding team. Attempting to create a
-- one2one team conversation with a member of a different (binding) team fails
-- with @non-binding-team-members@. Ported from
-- @testCreateOne2OneFailForNonTeamMembers@.
testCreateOne2OneFailForNonTeamMembers :: (HasCallStack) => App ()
testCreateOne2OneFailForNonTeamMembers :: HasCallStack => App ()
testCreateOne2OneFailForNonTeamMembers = do
  (owner1, tid1, []) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
  (owner2, _tid2, []) <- createTeam OwnDomain 1
  postOne2OneConversation owner1 owner2 tid1 "chit-chat"
    >>= assertLabel 403 "non-binding-team-members"