module Handler.Exam.AddUser
  ( getEAddUserR, postEAddUserR
  ) where

import Import hiding (Option(..))
import Handler.Exam.RegistrationInvite

import Handler.Utils
import Handler.Utils.Exam
import Handler.Utils.Invitations

import qualified Data.Set as Set

import Data.Semigroup (Option(..))

import Control.Monad.Error.Class (MonadError(..))

import Jobs.Queue

import Generics.Deriving.Monoid


data AddRecipientsResult = AddRecipientsResult
  { aurAlreadyRegistered
  , aurNoCourseRegistration
  , aurSuccess
  , aurSuccessCourse        :: [UserEmail]
  } deriving (Read, Show, Generic, Typeable)

instance Semigroup AddRecipientsResult where
  (<>) = mappenddefault

instance Monoid AddRecipientsResult where
  mempty = memptydefault
  mappend = (<>)


getEAddUserR, postEAddUserR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
getEAddUserR = postEAddUserR
postEAddUserR tid ssh csh examn = do
    eEnt@(Entity eid Exam{..}) <- runDB $ fetchExam tid ssh csh examn
    ((usersToEnlist,formWgt),formEncoding) <- runFormPost . renderWForm FormStandard $ do
      now <- liftIO getCurrentTime
      occurrences <- liftHandler . runDB $ selectList [ExamOccurrenceExam ==. eid] []

      let
        localNow = utcToLocalTime now
        tomorrowEndOfDay = case localTimeToUTC (LocalTime (addDays 2 $ localDay localNow) midnight) of
          LTUUnique utc' _ -> utc'
          _other           -> UTCTime (addDays 2 $ utctDay now) 0
        earliestDate = getOption . fmap getMin $ mconcat
          [ Option $ Min <$> examStart
          , foldMap (Option . Just . Min . examOccurrenceStart . entityVal) occurrences
          ]
        modifiedEarliestDate = earliestDate <&> \earliestDate'@(utcToLocalTime -> localEarliestDate')
          -> case localTimeToUTC (LocalTime (addDays (-1) $ localDay localEarliestDate') midnight) of
               LTUUnique utc' _ -> utc'
               _other           -> UTCTime (addDays (-1) $ utctDay earliestDate') 0
        defDeadline
          | Just registerTo <- examRegisterTo
          , registerTo > now
          = registerTo
          | Just earliestDate' <- modifiedEarliestDate
          = max tomorrowEndOfDay earliestDate'
          | otherwise
          = tomorrowEndOfDay

      deadline <- wreq utcTimeField (fslI MsgExamRegistrationInviteDeadline) (Just defDeadline)
      enlist <- wpopt checkBoxField (fslI MsgExamRegistrationEnlistDirectly & setTooltip MsgExamRegistrationEnlistDirectlyTip) (Just False)
      registerCourse <- wpopt checkBoxField (fslI MsgExamRegistrationRegisterCourse & setTooltip MsgExamRegistrationRegisterCourseTip) (Just False)
      occurrence <- wopt (examOccurrenceField eid) (fslI MsgTableExamOccurrence) Nothing
      mr <- getMessageRender
      users <- wreq (multiUserInvitationField . maybe MUIAlwaysInvite (const $ MUILookupAnyUser Nothing) $ formResultToMaybe enlist)
        (fslpI MsgExamRegistrationInviteField (mr MsgLdapIdentificationOrEmail) & setTooltip MsgMultiEmailFieldTip) Nothing
      return $ (,,,) <$> deadline <*> registerCourse <*> occurrence <*> users

    formResultModal usersToEnlist (CExamR tid ssh csh examn EUsersR) $ processUsers eEnt

    let heading = prependCourseTitle tid ssh csh MsgExamParticipantsRegisterHeading

    siteLayoutMsg heading $ do
      setTitleI heading
      wrapForm formWgt def
        { formEncoding
        , formAction = Just . SomeRoute $ CExamR tid ssh csh examn EAddUserR
        }
  where
    processUsers :: Entity Exam -> (UTCTime, Bool, Maybe ExamOccurrenceId, Set (Either UserEmail UserId)) -> WriterT [Message] Handler ()
    processUsers (Entity eid Exam{..}) (deadline, registerCourse, occId, users) = do
      let (emails,uids) = partitionEithers $ Set.toList users
      AddRecipientsResult{..} <- lift . runDBJobs $ do
        -- send Invitation eMails to unkown users
        sinkInvitationsF examRegistrationInvitationConfig [(mail,eid,(InvDBDataExamRegistration occId deadline registerCourse, InvTokenDataExamRegistration)) | mail <- emails]
        -- register known users
        execWriterT $ mapM (registerUser examCourse eid registerCourse occId) uids

      unless (null emails) $
        tell . pure <=< messageI Success . MsgExamParticipantsInvited $ length emails

      unless (null aurSuccess) $
        tell . pure <=< messageI Success . MsgExamRegistrationParticipantsRegistered $ length aurSuccess

      unless (null aurNoCourseRegistration) $ do
        let modalTrigger = [whamlet|_{MsgExamRegistrationNotRegisteredWithoutCourse (length aurNoCourseRegistration)}|]
            modalContent = $(widgetFile "messages/examRegistrationInvitationNotRegisteredWithoutCourse")
        tell . pure <=< messageWidget Error $ msgModal modalTrigger (Right modalContent)

      unless (null aurSuccessCourse) $
        tell . pure <=< messageI Success . MsgExamRegistrationAndCourseParticipantsRegistered $ length aurSuccessCourse

    registerUser :: CourseId -> ExamId -> Bool -> Maybe ExamOccurrenceId -> UserId -> WriterT AddRecipientsResult (YesodJobDB UniWorX) ()
    registerUser cid eid registerCourse occId uid = exceptT tell tell $ do
      User{..} <- lift . lift $ getJust uid
      now <- liftIO getCurrentTime

      let
        examRegister :: YesodJobDB UniWorX ()
        examRegister = do
          insert_ $ ExamRegistration eid uid occId now
          audit $ TransactionExamRegister eid uid

      whenM (lift . lift . existsBy $ UniqueExamRegistration eid uid) $
        throwError $ mempty { aurAlreadyRegistered = pure userEmail }

      whenM (lift . lift $ exists [ CourseParticipantCourse ==. cid, CourseParticipantUser ==. uid, CourseParticipantState ==. CourseParticipantActive ]) $ do
        lift $ lift examRegister
        throwError $ mempty { aurSuccess = pure userEmail }

      unless registerCourse $
        throwError $ mempty { aurNoCourseRegistration = pure userEmail }

      lift . lift . hoist lift $ guardAuthResult =<< evalAccessDB (CourseR tid ssh csh CAddUserR) True


      lift . lift . void $ upsert
        CourseParticipant
          { courseParticipantCourse = cid
          , courseParticipantUser = uid
          , courseParticipantRegistration = now
          , courseParticipantAllocated = Nothing
          , courseParticipantState = CourseParticipantActive
          , ..
          }
        [ CourseParticipantRegistration =. now
        , CourseParticipantAllocated =. Nothing
        , CourseParticipantState =. CourseParticipantActive
        ]
      lift . lift . audit $ TransactionCourseParticipantEdit cid uid
      lift . lift . queueDBJob . JobQueueNotification $ NotificationCourseRegistered uid cid
      lift $ lift examRegister

      return $ mempty { aurSuccessCourse = pure userEmail }


