-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.
-- Disabling for HasCallStack
{-# OPTIONS_GHC -Wno-redundant-constraints #-}

-- | TODO: most of this module is deprecated; use "Util.Test.SQS" from the types-common-aws package
-- instead.
module API.SQS where

import Control.Lens hiding ((.=))
import Data.ByteString.Lazy (fromStrict)
import Data.Currency qualified as Currency
import Data.Id
import Data.Set qualified as Set
import Data.Text (pack)
import Data.UUID qualified as UUID
import Imports
import Network.HTTP.Client
import Network.HTTP.Client.OpenSSL
import OpenSSL.Session as Ssl
import Proto.TeamEvents as E
import Proto.TeamEvents_Fields as E
import Ssl.Util
import System.Logger.Class qualified as L
import Test.Tasty.HUnit
import TestSetup
import Util.Test.SQS qualified as SQS
import Wire.AWS qualified as Aws
import Wire.Options.Galley (JournalOpts, endpoint, queueName)

withTeamEventWatcher :: (HasCallStack) => (SQS.SQSWatcher TeamEvent -> TestM ()) -> TestM ()
withTeamEventWatcher action = do
  view tsTeamEventWatcher >>= \case
    Nothing -> pure ()
    Just w -> action w

assertIfWatcher :: (HasCallStack) => String -> (TeamEvent -> Bool) -> (String -> Maybe TeamEvent -> TestM ()) -> TestM ()
assertIfWatcher l matcher assertion =
  view tsTeamEventWatcher >>= \case
    Nothing -> pure ()
    Just w -> SQS.assertMessage w l matcher assertion

tActivateWithCurrency :: (HasCallStack, MonadIO m) => Maybe Currency.Alpha -> String -> Maybe E.TeamEvent -> m ()
tActivateWithCurrency c l (Just e) = liftIO $ do
  assertEqual (l <> ": eventType") E.TeamEvent'TEAM_ACTIVATE (e ^. eventType)
  assertEqual "count" 1 (e ^. eventData . memberCount)
  -- NOTE: protobuf used to decodes absent, optional fields as (Just "") but not when using `maybe'<field>`
  let cur = pack . show <$> c
  assertEqual "currency" cur (e ^. eventData . maybe'currency)
tActivateWithCurrency _ l Nothing = liftIO $ assertFailure $ l <> ": Expected 1 TeamActivate, got nothing"

assertTeamActivateWithCurrency :: (HasCallStack) => String -> TeamId -> Maybe Currency.Alpha -> TestM ()
assertTeamActivateWithCurrency l tid c =
  assertIfWatcher l (teamActivateMatcher tid) (tActivateWithCurrency c)

tActivate :: (HasCallStack, MonadIO m) => String -> Maybe E.TeamEvent -> m ()
tActivate l (Just e) = liftIO $ do
  assertEqual (l <> ": eventType") E.TeamEvent'TEAM_ACTIVATE (e ^. eventType)
  assertEqual "count" 1 (e ^. eventData . memberCount)
tActivate l Nothing = liftIO $ assertFailure $ l <> ": Expected 1 TeamActivate, got nothing"

assertTeamActivate :: (HasCallStack) => String -> TeamId -> TestM ()
assertTeamActivate l tid =
  assertIfWatcher l (teamActivateMatcher tid) tActivate

teamActivateMatcher :: TeamId -> TeamEvent -> Bool
teamActivateMatcher tid e = e ^. eventType == E.TeamEvent'TEAM_ACTIVATE && decodeIdFromBS (e ^. teamId) == tid

tDelete :: (HasCallStack, MonadIO m) => String -> Maybe E.TeamEvent -> m ()
tDelete l (Just e) = liftIO $ assertEqual (l <> ": eventType") E.TeamEvent'TEAM_DELETE (e ^. eventType)
tDelete l Nothing = liftIO $ assertFailure $ l <> ": Expected 1 TeamDelete, got nothing"

assertTeamDelete :: (HasCallStack) => Int -> String -> TeamId -> TestM ()
assertTeamDelete maxWaitSeconds l tid =
  withTeamEventWatcher $ \w -> do
    mEvent <- SQS.waitForMessage w maxWaitSeconds (\e -> e ^. eventType == E.TeamEvent'TEAM_DELETE && decodeIdFromBS (e ^. teamId) == tid)
    tDelete l mEvent

tSuspend :: (HasCallStack, MonadIO m) => String -> Maybe E.TeamEvent -> m ()
tSuspend l (Just e) = liftIO $ assertEqual (l <> "eventType") E.TeamEvent'TEAM_SUSPEND (e ^. eventType)
tSuspend l Nothing = liftIO $ assertFailure $ l <> ": Expected 1 TeamSuspend, got nothing"

assertTeamSuspend :: (HasCallStack) => String -> TeamId -> TestM ()
assertTeamSuspend l tid =
  assertIfWatcher l (\e -> e ^. eventType == E.TeamEvent'TEAM_SUSPEND && decodeIdFromBS (e ^. teamId) == tid) tSuspend

tUpdate :: (HasCallStack, MonadIO m) => Int32 -> [UserId] -> String -> Maybe E.TeamEvent -> m ()
tUpdate expectedCount uids l (Just e) = liftIO $ do
  assertEqual (l <> ": eventType") E.TeamEvent'TEAM_UPDATE (e ^. eventType)
  assertEqual (l <> ": member count") expectedCount (e ^. eventData . memberCount)
  let maybeBillingUserIds = map (UUID.fromByteString . fromStrict) (e ^. eventData . billingUser)
  assertBool "Invalid UUID found" (all isJust maybeBillingUserIds)
  let billingUserIds = catMaybes maybeBillingUserIds
  assertEqual
    (l <> ": billing users")
    (Set.fromList $ toUUID <$> uids)
    (Set.fromList $ billingUserIds)
tUpdate _ _ l Nothing = liftIO $ assertFailure $ l <> ": Expected 1 TeamUpdate, got nothing"

assertTeamUpdate :: (HasCallStack) => String -> TeamId -> Int32 -> [UserId] -> TestM ()
assertTeamUpdate l tid c uids =
  assertIfWatcher l (\e -> e ^. eventType == E.TeamEvent'TEAM_UPDATE && decodeIdFromBS (e ^. teamId) == tid) $ tUpdate c uids

initHttpManager :: IO Manager
initHttpManager = do
  ctx <- Ssl.context
  Ssl.contextSetVerificationMode ctx $ Ssl.VerifyPeer True True Nothing
  Ssl.contextAddOption ctx SSL_OP_NO_SSLv2
  Ssl.contextAddOption ctx SSL_OP_NO_SSLv3
  Ssl.contextAddOption ctx SSL_OP_NO_TLSv1
  Ssl.contextSetCiphers ctx rsaCiphers
  Ssl.contextSetDefaultVerifyPaths ctx
  newManager
    (opensslManagerSettings (pure ctx)) -- see Note [SSL context]
      { managerResponseTimeout = responseTimeoutMicro 10000000,
        managerConnCount = 100,
        managerIdleConnectionCount = 300
      }

mkAWSEnv :: JournalOpts -> IO Aws.Env
mkAWSEnv opts = do
  l <- L.new $ L.setOutput L.StdOut . L.setFormat Nothing $ L.defSettings -- TODO: use mkLogger'?
  mgr <- initHttpManager
  Aws.mkEnv l mgr (opts ^. endpoint) (opts ^. queueName)

decodeIdFromBS :: ByteString -> Id a
decodeIdFromBS = Id . fromMaybe (error "failed to decode userId") . UUID.fromByteString . fromStrict
