module Handler.ExternalExam.Correct
  ( getEECorrectR, postEECorrectR
  ) where

import Import

import qualified Data.Set as Set
import qualified Data.List.NonEmpty as NonEmpty (toList)

import qualified Database.Esqueleto as E
--import qualified Database.Esqueleto.Utils as E
import Database.Persist.Sql (transactionUndo)

import Handler.Utils
import Handler.Utils.ExternalExam
import Handler.Utils.Users

import Utils.Exam.Correct


-- | Minimum length of a participant identifier. Identifiers that are shorter would result in too many query results and are therefor rejected.
minNeedleLength :: Int
minNeedleLength = 3  -- TODO rethink

-- | Maximum number of user matches to show. Also serves as an upper limit to the number of query results from user and/or ldap lookups.
maxCountUserMatches :: Integral a => a
maxCountUserMatches = 10


getEECorrectR :: TermId -> SchoolId -> CourseName -> ExamName -> Handler Html
getEECorrectR tid ssh coursen examn = do
  MsgRenderer mr <- getMsgRenderer

  Entity eeId ExternalExam{..} <- runDB $ fetchExternalExam tid ssh coursen examn

  mayEditResults <- hasWriteAccessTo $ EExamR tid ssh coursen examn EEUsersR

  let
    heading = mr $ MsgExternalExamCorrectHeading coursen examn

    ptsInput :: ExamPartNumber -> Widget
    ptsInput = const mempty

    examParts :: [ExamPart]
    examParts = []

    examGrades :: [ExamGrade]
    examGrades = universeF

    examGradingMode = externalExamGradingMode

  examCorrectIdent <- encrypt eeId :: Handler CryptoUUIDExternalExam

  siteLayoutMsg heading $ do
    setTitleI heading
    let examCorrectExplanation = $(i18nWidgetFile "external-exam-correct-explanation")
    $(widgetFile "exam-correct")


postEECorrectR :: TermId -> SchoolId -> CourseName -> ExamName -> Handler Void
postEECorrectR tid ssh coursen examn = do
  MsgRenderer mr <- getMsgRenderer

  CorrectInterfaceRequest{..} <- requireCheckJsonBody

  mayEditResults <- hasWriteAccessTo $ EExamR tid ssh coursen examn EEUsersR

  response <- runDB . exceptT (<$ transactionUndo) return $ do
    Entity eeId ExternalExam{..} <- lift $ fetchExternalExam tid ssh coursen examn
    euid <- traverse decrypt ciqUser

    guardMExceptT (maybe True ((>= minNeedleLength) . length) $ euid ^? _Left) $
      CorrectInterfaceResponseFailure Nothing <$> (getMessageRender <*> pure MsgExamCorrectErrorNeedleTooShort)

    matches <- case euid of
      Right uid   -> lift . E.select . E.from $ \user -> E.where_ (user E.^. UserId E.==. E.val uid) >> return user
      Left  ident ->
        let pdnf = PredDNF . Set.fromList $ impureNonNull . Set.fromList . pure . PLVariable <$>
              [ GuessUserMatrikelnummer (ident :: UserMatriculation)
              , GuessUserDisplayName    (ident :: UserDisplayName)
              , GuessUserSurname        (ident :: UserSurname)
              , GuessUserFirstName      (ident :: UserFirstName)
              ]
        in  maybe [] (either NonEmpty.toList pure) <$> lift (guessUser pdnf $ Just $ maxCountUserMatches+1)

    if
      | is _Nothing ciqResults, is _Nothing ciqGrade -> do
        users <- traverse userToResponse $ take maxCountUserMatches matches
        return CorrectInterfaceResponseNoOp
          { cirnUsers   = Set.fromList users
          , cirnHasMore = length matches > maxCountUserMatches
          }
      | [match@(Entity uid _)] <- matches -> do
        now <- liftIO getCurrentTime
        newExamResult <- for ciqGrade $ \ciqGrade' -> lift $ do
          unless mayEditResults $
            permissionDeniedI MsgUnauthorizedExamCorrectorGrade
          mOldResult <- getBy $ UniqueExternalExamResult eeId uid
          if
            | Just (Entity oldId _) <- mOldResult, is _Nothing ciqGrade' -> do
              delete oldId
              audit $ TransactionExternalExamResultDelete eeId uid
              return Nothing
            | Just resultGrade <- ciqGrade' -> let
                mOld = externalExamResultResult . entityVal <$> mOldResult
              in if
                | ciqGrade' /= mOld -> do
                  let resultTime = fromMaybe now externalExamDefaultTime
                  newResult <- upsert ExternalExamResult
                    { externalExamResultExam        = eeId
                    , externalExamResultUser        = uid
                    , externalExamResultResult      = resultGrade
                    , externalExamResultTime        = resultTime
                    , externalExamResultLastChanged = now
                    }
                    [ ExternalExamResultResult      =. resultGrade
                    , ExternalExamResultTime        =. resultTime
                    , ExternalExamResultLastChanged =. now
                    ]
                  audit $ TransactionExternalExamResultEdit eeId uid
                  return $ newResult ^? _entityVal . _externalExamResultResult
                | otherwise -> return $ mOldResult ^? _Just . _entityVal . _externalExamResultResult
            | otherwise -> return Nothing

        user <- userToResponse match
        return CorrectInterfaceResponseSuccess
          { cirsUser    = user
          , cirsResults = mempty
          , cirsGrade   = newExamResult
          , cirsTime    = now
          }

      | [] <- matches -> return CorrectInterfaceResponseFailure
        { cirfUser    = Nothing
        , cirfMessage = mr MsgExternalExamCorrectErrorNoMatchingUsers
        }

      | otherwise -> do
        users <- traverse userToResponse $ take maxCountUserMatches matches
        return CorrectInterfaceResponseAmbiguous
          { ciraUsers   = Set.fromList users
          , ciraHasMore = length matches > maxCountUserMatches
          , ciraMessage = mr MsgExternalExamCorrectErrorMultipleMatchingUsers
          }

  whenM acceptsJson $
    sendResponseStatus (ciResponseStatus response) $ toJSON response

  redirect $ EExamR tid ssh coursen examn EEShowR
