{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}

module Bank.Models.Account.CommandHandler
  ( accountCommandHandler
  , AccountCommand (..)
  ) where


import Control.Lens
import Data.Maybe (isNothing)
import SumTypes.TH

import Eventful

import Bank.Models.Account.Commands
import Bank.Models.Account.Events
import Bank.Models.Account.Projection

constructSumType "AccountCommand" (defaultSumTypeOptions { sumTypeOptionsTagOptions = AppendTypeNameToTags }) accountCommands

handleAccountCommand :: Account -> AccountCommand -> [AccountEvent]
handleAccountCommand account (OpenAccountAccountCommand OpenAccount{..}) =
  case account ^. accountOwner of
    Just _ -> [AccountOpenRejectedAccountEvent $ AccountOpenRejected "Account already open"]
    Nothing ->
      if openAccountInitialFunding < 0
      then [AccountOpenRejectedAccountEvent $ AccountOpenRejected "Invalid initial deposit"]
      else
        [ AccountOpenedAccountEvent
          AccountOpened
          { accountOpenedOwner = openAccountOwner
          , accountOpenedInitialFunding = openAccountInitialFunding
          }
        ]
handleAccountCommand _ (CreditAccountAccountCommand CreditAccount{..}) =
  [ AccountCreditedAccountEvent
    AccountCredited
    { accountCreditedAmount = creditAccountAmount
    , accountCreditedReason = creditAccountReason
    }
  ]
handleAccountCommand account (DebitAccountAccountCommand DebitAccount{..}) =
  if accountAvailableBalance account - debitAccountAmount < 0
  then [AccountDebitRejectedAccountEvent $ AccountDebitRejected $ accountAvailableBalance account]
  else
    [ AccountDebitedAccountEvent
      AccountDebited
      { accountDebitedAmount = debitAccountAmount
      , accountDebitedReason = debitAccountReason
      }
    ]
handleAccountCommand account (TransferToAccountAccountCommand TransferToAccount{..})
  | isNothing (account ^. accountOwner) =
      [AccountTransferRejectedAccountEvent $ AccountTransferRejected transferToAccountTransferId "Account doesn't exist"]
  | accountAvailableBalance account - transferToAccountAmount < 0 =
      [AccountTransferRejectedAccountEvent $ AccountTransferRejected transferToAccountTransferId "Not enough funds"]
  | otherwise =
      [ AccountTransferStartedAccountEvent
        AccountTransferStarted
        { accountTransferStartedTransferId = transferToAccountTransferId
        , accountTransferStartedAmount = transferToAccountAmount
        , accountTransferStartedTargetAccount = transferToAccountTargetAccount
        }
      ]
handleAccountCommand _ (AcceptTransferAccountCommand AcceptTransfer{..}) =
  [ AccountCreditedFromTransferAccountEvent
    AccountCreditedFromTransfer
    { accountCreditedFromTransferTransferId = acceptTransferTransferId
    , accountCreditedFromTransferSourceAccount = acceptTransferSourceAccount
    , accountCreditedFromTransferAmount = acceptTransferAmount
    }
  ]

accountCommandHandler :: CommandHandler Account AccountEvent AccountCommand
accountCommandHandler = CommandHandler handleAccountCommand accountProjection
