-- 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/>.
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}

-- 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 Wire.InvitationStore where

import Data.Id (InvitationId, TeamId, UserId)
import Data.Json.Util (UTCTimeMillis, toUTCTimeMillis)
import Data.Range (Range)
import Database.CQL.Protocol (Record (..), TupleType, recordInstance)
import Imports
import Polysemy
import URI.ByteString
import Util.Timeout
import Wire.API.Team.Invitation (Invitation (inviteeEmail))
import Wire.API.Team.Invitation qualified as Public
import Wire.API.Team.Role (Role, defaultRole)
import Wire.API.User (EmailAddress, InvitationCode, Name)
import Wire.Arbitrary (Arbitrary, GenericUniform (..))

data StoredInvitation = MkStoredInvitation
  { teamId :: TeamId,
    role :: Maybe Role,
    invitationId :: InvitationId,
    createdAt :: UTCTimeMillis,
    createdBy :: Maybe UserId,
    -- | The invitee's email address
    email :: EmailAddress,
    name :: Maybe Name,
    code :: InvitationCode
  }
  deriving (Show, Eq, Generic)
  deriving (Arbitrary) via (GenericUniform StoredInvitation)

recordInstance ''StoredInvitation

-- | The difference between this and `StoredInvitation` is the type of `createdAt` (plus
-- reordering and renaming of fields for some reason).  See 'insertInvToStoredInv' below.
data InsertInvitation = MkInsertInvitation
  { invitationId :: InvitationId,
    teamId :: TeamId,
    role :: Role,
    createdAt :: UTCTime,
    createdBy :: Maybe UserId,
    inviteeEmail :: EmailAddress,
    inviteeName :: Maybe Name,
    code :: InvitationCode
  }
  deriving (Show, Eq, Generic)
  deriving (Arbitrary) via (GenericUniform InsertInvitation)

recordInstance ''InsertInvitation

data PaginatedResult a
  = PaginatedResultHasMore a
  | PaginatedResult a
  deriving stock (Eq, Ord, Show, Functor, Foldable)

----------------------------

data InvitationStore :: Effect where
  InsertInvitation :: InsertInvitation -> Timeout -> InvitationStore m StoredInvitation
  LookupInvitation :: TeamId -> InvitationId -> InvitationStore m (Maybe StoredInvitation)
  LookupInvitationByCode :: InvitationCode -> InvitationStore m (Maybe StoredInvitation)
  LookupInvitationsByEmail :: EmailAddress -> InvitationStore m [StoredInvitation]
  InsertPendingScimUser :: TeamId -> EmailAddress -> UserId -> InvitationStore m ()
  LookupPendingScimUsers :: TeamId -> EmailAddress -> InvitationStore m [UserId]
  DeletePendingScimUser :: TeamId -> EmailAddress -> UserId -> InvitationStore m ()
  -- | Range is page size, it defaults to 100
  LookupInvitationsPaginated :: Maybe (Range 1 500 Int32) -> TeamId -> Maybe InvitationId -> InvitationStore m (PaginatedResult [StoredInvitation])
  CountInvitations :: TeamId -> InvitationStore m Int64
  DeleteInvitation :: TeamId -> InvitationId -> InvitationStore m ()
  DeleteAllTeamInvitations :: TeamId -> InvitationStore m ()

makeSem ''InvitationStore

----------------------------

invitationFromStored :: Maybe (URIRef Absolute) -> StoredInvitation -> Public.Invitation
invitationFromStored maybeUrl MkStoredInvitation {..} =
  Public.Invitation
    { team = teamId,
      role = fromMaybe defaultRole role,
      invitationId = invitationId,
      createdAt = createdAt,
      createdBy = createdBy,
      inviteeEmail = email,
      inviteeName = name,
      inviteeUrl = maybeUrl
    }

insertInvToStoredInv :: InsertInvitation -> StoredInvitation
insertInvToStoredInv (MkInsertInvitation invId teamId role (toUTCTimeMillis -> now) uid email name code) =
  MkStoredInvitation
    { teamId = teamId,
      role = Just role,
      invitationId = invId,
      createdAt = now,
      createdBy = uid,
      email = email,
      name = name,
      code = code
    }
