{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}

-- 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 Spar.Sem.SAMLUserStore.Mem
  ( samlUserStoreToMem,
    UserRefOrd,
  )
where

import Cassandra (Page (..), emptyPage)
import Control.Lens (view)
import qualified Data.Bifunctor
import Data.Id
import qualified Data.Map as M
import Imports
import Polysemy
import Polysemy.State (gets, modify, runState)
import SAML2.WebSSO (uidTenant)
import qualified SAML2.WebSSO as SAML
import Spar.Sem.SAMLUserStore

newtype UserRefOrd = UserRefOrd {unUserRefOrd :: SAML.UserRef}
  deriving (Eq, Show)

instance Ord UserRefOrd where
  compare (UserRefOrd (SAML.UserRef is ni)) (UserRefOrd (SAML.UserRef is' ni')) =
    compare is is' <> compare ni ni'

samlUserStoreToMem :: Sem (SAMLUserStore ': r) a -> Sem r (Map UserRefOrd UserId, a)
samlUserStoreToMem = (runState @(Map UserRefOrd UserId) mempty .) $
  reinterpret $ \case
    Insert ur uid -> modify $ M.insert (UserRefOrd ur) uid
    Get ur -> gets $ M.lookup $ UserRefOrd ur
    DeleteByIssuer is -> modify $ M.filterWithKey (\ref _ -> not $ eqIssuer is ref)
    Delete _uid ur -> modify $ M.delete $ UserRefOrd ur
    -- 'GetAllByIssuerPaginated' and 'NextPage' are workarounds, please also see docs at
    -- 'Spar.Sem.SAMLUserStore.Cassandra.getAllSAMLUsersByIssuerPaginated'
    --
    -- This mock only returns one `Page` for all results. This should be fine
    -- for tests with small test samples.
    GetAllByIssuerPaginated is -> gets $ \userMap ->
      let entries =
            Data.Bifunctor.first unUserRefOrd
              <$> M.assocs (M.filterWithKey (\ref _ -> eqIssuer is ref) userMap)
       in emptyPage {result = entries}
    NextPage _ -> pure emptyPage
  where
    eqIssuer :: SAML.Issuer -> UserRefOrd -> Bool
    eqIssuer is = (== is) . view uidTenant . unUserRefOrd
