{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell #-}

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

module Util.Types
  ( BrigReq,
    GalleyReq,
    SparReq,
    TestSpar,
    TestEnv (..),
    teMgr,
    teCql,
    teBrig,
    teGalley,
    teSpar,
    teSparEnv,
    teOpts,
    teTstOpts,
    teWireIdPAPIVersion,
    Select,
    ResponseLBS,
    IntegrationConfig (..),
    TestErrorLabel (..),
    skipIdPAPIVersions,
  )
where

import Bilge
import Cassandra as Cas
import Control.Lens (makeLenses, view)
import Crypto.Random.Types (MonadRandom (..))
import Data.Aeson
import qualified Data.Aeson as Aeson
import Data.Aeson.TH
import Imports
import Spar.API ()
import qualified Spar.App as Spar
import Spar.Options
import Test.Hspec (pendingWith)
import Util.Options
import Wire.API.User.IdentityProvider (WireIdPAPIVersion)

type BrigReq = Request -> Request

type GalleyReq = Request -> Request

type SparReq = Request -> Request

type TestSpar = ReaderT TestEnv IO

instance MonadRandom TestSpar where
  getRandomBytes = lift . getRandomBytes

-- | See 'mkEnv' about what's in here.
data TestEnv = TestEnv
  { _teMgr :: Manager,
    _teCql :: Cas.ClientState,
    _teBrig :: BrigReq,
    _teGalley :: GalleyReq,
    _teSpar :: SparReq,
    _teSparEnv :: Spar.Env,
    -- | spar config
    _teOpts :: Opts,
    -- | integration test config
    _teTstOpts :: IntegrationConfig,
    -- | If True, run tests against legacy SAML API where team is derived from idp issuer
    -- instead of teamid.  See Section "using the same IdP (same entityID, or Issuer) with
    -- different teams" in "/docs/reference/spar-braindump.md" for more details.
    --
    -- NB: this has no impact on the tested spar code; the rest API supports both legacy and
    -- multi-sp mode.  this falg merely determines how the rest API is used.
    _teWireIdPAPIVersion :: WireIdPAPIVersion
  }

type Select = TestEnv -> (Request -> Request)

data IntegrationConfig = IntegrationConfig
  { brig :: Endpoint,
    galley :: Endpoint,
    spar :: Endpoint,
    brigSettingsTeamInvitationTimeout :: Int
  }
  deriving (Show, Generic)

deriveFromJSON Aeson.defaultOptions ''IntegrationConfig

makeLenses ''TestEnv

newtype TestErrorLabel = TestErrorLabel {fromTestErrorLabel :: Text}
  deriving (Eq, Show, IsString)

instance FromJSON TestErrorLabel where
  parseJSON = fmap TestErrorLabel . withObject "TestErrorLabel" (.: "label")

-- A quick unit test that serves two purposes: (1) shows that it works (and helped with debugging);
-- (2) demonstrates how to use it.
-- _unitTestTestErrorLabel :: IO ()
-- _unitTestTestErrorLabel = do
--   let val :: Either String TestErrorLabel
--       val = Aeson.eitherDecode "{\"code\":404,\"message\":\"Not found.\",\"label\":\"not-found\"}"
--   unless (val == Right "not-found") $
--     throwIO . ErrorCall . show $
--       val

-- | FUTUREWORK(fisx): we're running all tests for all constructors of `WireIdPAPIVersion`,
-- which sometimes makes little sense.  'skipIdPAPIVersions' can be used to pend individual
-- tests that do not even work in both versions (most of them should), or ones that aren't
-- that interesting to run twice (like if SAML is not involved at all).  A more scalable
-- solution would be to pass the versions that a test should be run on as an argument to
-- describe ('skipIdPAPIVersions' only works on individual leafs of the test tree, not on
-- sub-trees), but that would be slightly (only slightly) more involved than I would like.
-- so, some other time.  (Context: `make -C services/spar i` takes currently takes 3m22.476s
-- on my laptop, including all the uninteresting tests.  So this is the maximum time
-- improvement that we can get out of this.)
skipIdPAPIVersions :: (MonadIO m, MonadReader TestEnv m) => [WireIdPAPIVersion] -> m ()
skipIdPAPIVersions skip = do
  view teWireIdPAPIVersion >>= \vers -> when (vers `elem` skip) . liftIO $ do
    pendingWith $ "skipping " <> show vers <> " for this test case (behavior covered by other versions)"
