-- SPDX-FileCopyrightText: 2022-25 Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <s.jost@fraport.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later

Qualification
  -- INVARIANT: 2*refreshWithin < validDuration
  school              SchoolId                  --  1
  shorthand           (CI Text)                 --  2
  name                (CI Text)                 --  3
  description         StoredMarkup Maybe        --  4 user-defined large Html, ought to contain full description
  validDuration       Int Maybe                 --  5 if > 0, qualification is valid indefinitely or for a specified number of months, use with addMonthsDay
  auditDuration       Int  default=366          --  6 number of days to keep LMS audit log and LmsUserIdents -- TODO: audit period for QualificationUser/Block as well
  refreshWithin       CalendarDiffDays Maybe    --  7 notify users about renewal within this number of month/days before expiry; to be used with addGregorianDurationClip
  refreshReminder     CalendarDiffDays Maybe    --  8 send a second notification about renewal within this number of month/days before expiry
  elearningStart      Bool                      --  9 automatically schedule e-refresher
  elearningRenews     Bool default=true         -- 10 successful e-learing automatically increases validity automatically by validDuration
  elearningLimit      Int Maybe                 -- 11 limit of e-learning attempts, currently only for informative purposes, as it is enforced by LMS only
  lmsReuses           QualificationId Maybe     -- 12 if set, lms is also included within the given qualification's lms, but only for direct routes. AuditDuration is used from this Qualification instead.
  expiryNotification  Bool default=true         -- 13 should expiryNotification be generated for this qualification?
  avsLicence          AvsLicence Maybe          -- 14 if set, valid QualificationUsers are synchronized to AVS as a driving licence
  sapId               Text Maybe                -- 15 if set, valid QualificationUsers with userCompanyPersonalNumber are transmitted via SAP interface under this id  
  SchoolQualificationShort school shorthand            -- must be unique per school and shorthand
  SchoolQualificationName  school name                 -- must be unique per school and name
    -- across all schools, only one qualification may be a driving licence -- NO LONGER TRUE
    -- UniqueQualificationAvsLicence avsLicence      !force -- either empty or unique 
    -- NOTE: two NULL values are not equal for the purpose of Uniqueness constraints!
  deriving Show Eq Generic Binary

-- TODOs:
--  - Enstehen Kosten, wenn Teilnehmer für KnowHow eingereiht werden, aber nicht am Kurs teilnehmen?
--    Falls ja, so sollte bei automatischem refresher vorher der Kunde durch FRADrive befragt werden?!
--    A: Der Inhaber per Email informieren!
--    A: Es kann gleich eine LMS Pin generiert und verschickt werden!
--  - Aufteilung Qualification "R" in zwei Teile: "R e-learning" und "R praxis" okay?

-- Besonderheiten:
--  - LmsIdent muss für alle Qualificationen einzigartig sein!
--  - Durchfallen wird mit UserList ständig erneut gesandt, bis Löschantrag gestellt wurde.
--  - Bestehen mit Result wird nur ein einziges mal gesendet! (Ausfallrisiko: keine Bestätigung der Kommunikation!)
--  - Explizites Löschen eines LmsIdent nach Success/Failure ist notwendig (feedback bei Block)
--  - LmsUser soll nur DELTA übermitteln. (GET Request will always return the same; until POST Request was processed!)
--  - PinReset==1 mit bestehendem Passwort kann problemlos erneut gesendet werden
--  - Flag "interner Mitarbeiter" wird von Know-How ignoriert / nicht ausgewertet (legacy)

-- -- QualificationPrecondition                       -- NOTE: this can only be enforced through a background job adding or removing qualifications
-- --  qualification     QualificationId OnDeleteCascade OnUpdateCascade -- AND: not unique, ie. qualification can have multiple required preconditions
-- --  required          [QualificationId]           -- OR : alternatives, any one will suffice -- we don't want array, since we have recursive CTEs
----   continuous        Bool                        -- expiring precondition blocks qualification
-- --  deriving Generic Show

-- Maybe an alternative for online qualification validity checking, transitivity through recursive CTEs? (already available in our version)
QualificationRequirement
 qualification     QualificationId OnDeleteCascade OnUpdateCascade
 requirement       QualificationId OnDeleteCascade OnUpdateCascade
 group             Int        -- OR: several requirements within the same group are considered equivalent; no order between groups
 note              Text       -- for humans only, no semantical effect
 UniqueQualificationRequirement qualification requirement
 deriving Generic Show

-- TODO: connect Qualifications with Exams!?

QualificationEdit
  user            UserId
  time            UTCTime
  qualification   QualificationId OnDeleteCascade OnUpdateCascade
  deriving Generic Show

QualificationUser
  user              UserId OnDeleteCascade OnUpdateCascade
  qualification     QualificationId OnDeleteCascade OnUpdateCascade
  validUntil        Day                           -- addGregorianMonthsRollOver (toInteger renewalMonths) qualificationUserValidUntil
  lastRefresh       Day                           -- lastRefresh > validUntil possible, if Qualification^elearningOnly == False
  firstHeld         Day                           -- first time the qualification was earned, should never change
  scheduleRenewal   Bool  default=true            -- if false, no automatic renewal is scheduled and the qualification expires
  lastNotified      UTCTime default=now()         -- last notficiation about actual licence validity changes (does not entail e-learning notifications)
  -- Reasons and temporary revocations are implemented through QualificationUserBlock
  -- TODO: adjust SAP interface to transmit end dates
  UniqueQualificationUser qualification user
  deriving Generic Show

QualificationUserBlock
  qualificationUser QualificationUserId OnDeleteCascade OnUpdateCascade
  unblock           Bool
  from              UTCTime  
  reason            Text  
  blocker           UserId Maybe
  -- precondition      Bool  default=false  -- if true, this was due to a precondition
  deriving Eq Ord Read Show Generic

  -- LMS Interface Tables, need regular processing by background jobs, per QualificationId:
  --
  -- 1. Daily Job: Add to LmsUser daily all qualification holders with
  --          QualificationUserValidUntil >= now
  --       /\ QualificationUserValudUntil <= now + QualificationRefreshWithin    (time to schedule refresher)
  --       /\ not already enlisted
  --
  -- 2. REST GET User.csv:
  --     - where LmsUserReceived == Nothing \/ (LmsUserResetPin /\ LmsUserEnded == Nothing)
  --     - delete-flag: isJust LmsUserStatus
  --   Note: REST means that LmsUserResetPin and LmsUserDelete remain unchanged by this GET request!
  --
  -- 3. REST POST Report.csv: just save as is to LmsReport for later processing
  --
  -- 4. When received: Job LmsReport:               -- Note: containment needs at-once processing
  --     - For all LmsUser:
  --         + if contained:
  --              set LmsUserReceived to Just now()
  --              if Failed:  set LmsUserStatus to Just LmsBlocked now
  --              if Success: set LmsUserStatus to Just LmsSuccess now
  --                          and renew QualificationValidTo
  --         + not contained, by LmsUserReceived is set: set LmsUserEnded to Just now()
  --     - move row to LmsAudit
  --
  -- 5. Daily Job: dequeue LMS Users
  --     - fail and mark expired LmsUser
  --     - remove from LmsUser after audit Period has passed

LmsUser
  qualification     QualificationId OnDeleteCascade OnUpdateCascade
  user              UserId          OnDeleteCascade OnUpdateCascade
  ident             LmsIdent                      -- must be unique accross all LMS courses!
  pin               Text
  resetPin          Bool            default=false -- should pin be reset?  
  datePin           UTCTime         default=now() -- time pin was created
  status            LmsStatus Maybe               -- Nothing=open, LmsSuccess, LmsBlocked or LmsExpired; status should never change unless isNothing; isJust indicates lms is finished and user shall be deleted from LMS
  --toDelete encoded by Handler.Utils.LMS.lmsUserToDelete
  statusDay         UTCTime Maybe                 -- last status change; should be isJust iff isJust status; modelling as a separate table too bothersome, unlike qualification block  
  started           UTCTime         default=now()
  received          UTCTime Maybe                 -- last acknowledgement by LMS
  notified          UTCTime Maybe                 -- last notified by FRADrive
  ended             UTCTime Maybe                 -- ident was deleted from LMS
  resetTries        Bool            default=false -- V2 should e-learning exam tries be reset?
  locked            Bool            default=false -- V2 last returned lock status
  -- Primary ident -- newtype Key LmsUserId = LmsUserKey { unLmsUser :: Text } -- change LmsIdent -> Text. Do we want this? No.
  UniqueLmsIdent ident                            -- idents must be unique accross all qualifications, since idents are global within LMS!
  UniqueLmsQualificationUser qualification user   -- each user may be enrolled at most once per course
  deriving Generic Show

-- LmsUserStatus
--   lmsUser LmsUserId OnDeleteCascade OnUpdateCascade
--   result  LmsStatus -- data LmsStatus = LmsBlocked | LmsExpired | LmsSuccess
--   day     Day   
--   UniqueLmsUserStatus lmsUser -- enforcing uniqueness prohibits history
--   deriving Generic

-- V2 Stores LMS upload for processing in Background Job
LmsReport
  qualification     QualificationId OnDeleteCascade OnUpdateCascade
  ident             LmsIdent
  date              UTCTime Maybe   -- BEWARE: timezone is local as submitted by LMS
  result            LmsState        -- (0|1|2) 0=LmsFailed[too many tries], 1=LmsOpen, 2=LmsPassed[success]
  lock              Bool            -- (0|1)
  timestamp         UTCTime         default=now()
  UniqueLmsReport qualification ident -- required by DBTable
  deriving Generic Show

-- LmsAudit removed by commit 71cde92a
-- due to frequent transmit errors, a separate lms tranmission log is necessary again
LmsReportLog
  qualification     QualificationId OnDeleteCascade OnUpdateCascade
  ident             LmsIdent
  date              UTCTime Maybe   -- BEWARE: timezone is local as submitted by LMS
  result            LmsState        -- (0|1|2) 0=LmsFailed[too many tries], 1=LmsOpen, 2=LmsPassed[success]
  lock              Bool            -- (0|1)
  timestamp         UTCTime         default=now()  
  missing           Bool            default=false
  deriving Generic Show

-- Table to manage unknown or orphaned lms identifiers
LmsOrphan
  qualification             QualificationId OnDeleteCascade OnUpdateCascade  
  ident                     LmsIdent                  -- must be unique accross all LMS courses!
  seenFirst                 UTCTime   default=now()   -- first time reported by LMS
  seenLast                  UTCTime   default=now()   -- last acknowledgement by LMS, deletion uses QualificationAuditDuration
  deletedLast               UTCTime   Maybe           -- last deletion request sent to LMS
  resultLast                LmsState                  -- last received learning state
  reason                    Text      Maybe           -- to mark explicit e-learning deletions, etc
  UniqueLmsOrphan qualification ident                 -- unlike other tables, LMS Idents must only be unique within qualification, allowing orphans to be handled independently
  deriving Generic Show