-- 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 #-}

-- 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.PasswordResetCodeStore.Cassandra
  ( passwordResetCodeStoreToCassandra,
    interpretClientToIO,
  )
where

import Cassandra
import Data.Id
import Data.Text (pack)
import Data.Text.Ascii
import Data.Time.Clock
import Imports
import OpenSSL.BN (randIntegerZeroToNMinusOne)
import OpenSSL.Random (randBytes)
import Polysemy
import Text.Printf
import Wire.API.User.Password
import Wire.PasswordResetCodeStore

passwordResetCodeStoreToCassandra ::
  forall m r a.
  (MonadClient m, Member (Embed m) r) =>
  Sem (PasswordResetCodeStore ': r) a ->
  Sem r a
passwordResetCodeStoreToCassandra =
  interpret $
    embed @m
      . \case
        GenerateEmailCode -> genEmailCode
        GeneratePhoneCode -> genPhoneCode
        CodeSelect prk ->
          (fmap . fmap) toRecord
            . retry x1
            . query1 codeSelectQuery
            . params LocalQuorum
            . Identity
            $ prk
        CodeInsert prk (PRQueryData prc uid n ut) ttl ->
          retry x5
            . write codeInsertQuery
            . params LocalQuorum
            $ (prk, prc, uid, runIdentity n, runIdentity ut, ttl)
        CodeDelete prk -> codeDeleteImpl prk
  where
    toRecord ::
      (PasswordResetCode, UserId, Maybe Int32, Maybe UTCTime) ->
      PRQueryData Maybe
    toRecord (prqdCode, prqdUser, prqdRetries, prqdTimeout) =
      PRQueryData {..}

genEmailCode :: (MonadIO m) => m PasswordResetCode
genEmailCode = PasswordResetCode . encodeBase64Url <$> liftIO (randBytes 24)

genPhoneCode :: (MonadIO m) => m PasswordResetCode
genPhoneCode =
  PasswordResetCode . unsafeFromText . pack . printf "%06d"
    <$> liftIO (randIntegerZeroToNMinusOne 1000000)

-- FUTUREWORK(fisx,elland): this should be replaced by a method in a
-- future auth subsystem
codeDeleteImpl :: (MonadClient m) => PasswordResetKey -> m ()
codeDeleteImpl prk =
  retry x5
    . write codeDeleteQuery
    . params LocalQuorum
    . Identity
    $ prk

interpretClientToIO ::
  (Member (Final IO) r) =>
  ClientState ->
  Sem (Embed Cassandra.Client ': r) a ->
  Sem r a
interpretClientToIO ctx = interpret $ \case
  Embed action -> embedFinal @IO $ runClient ctx action

---------------------------------------------------------------------------------
-- Queries

codeSelectQuery :: PrepQuery R (Identity PasswordResetKey) (PasswordResetCode, UserId, Maybe Int32, Maybe UTCTime)
codeSelectQuery = "SELECT code, user, retries, timeout FROM password_reset WHERE key = ?"

codeInsertQuery :: PrepQuery W (PasswordResetKey, PasswordResetCode, UserId, Int32, UTCTime, Int32) ()
codeInsertQuery = "INSERT INTO password_reset (key, code, user, retries, timeout) VALUES (?, ?, ?, ?, ?) USING TTL ?"

codeDeleteQuery :: PrepQuery W (Identity PasswordResetKey) ()
codeDeleteQuery = "DELETE FROM password_reset WHERE key = ?"
