module Handler.Admin.Tokens
  ( getAdminTokensR, postAdminTokensR
  ) where

import Import
import Handler.Utils

import qualified Data.HashSet as HashSet
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Set as Set

import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Encode.Pretty as Aeson

import Data.Map ((!), (!?))

import qualified Data.Text as Text


data BearerTokenForm = BearerTokenForm
  { btfAuthority :: HashSet (Either UserGroupName UserId)
  , btfRoutes    :: Maybe (HashSet (Route UniWorX))
  , btfRestrict  :: HashMap (Route UniWorX) Value
  , btfAddAuth   :: Maybe AuthDNF
  , btfExpiresAt :: Maybe (Maybe UTCTime)
  , btfStartsAt  :: Maybe UTCTime
  }

bearerTokenForm :: WForm Handler (FormResult BearerTokenForm)
bearerTokenForm = do
  muid <- maybeAuthId
  mr <- getMessageRender

  btfAuthorityGroups <- aFormToWForm $ HashSet.fromList . map Left <$> massInputListA pathPieceField (const "") MsgBearerTokenAuthorityGroupMissing (\p -> Just . SomeRoute $ AdminTokensR :#: p) ("token-groups" :: Text) (fslI MsgBearerTokenAuthorityGroups & setTooltip MsgBearerTokenAuthorityGroupsTip) False Nothing
  btfAuthorityUsers <- fmap (fmap . ofoldMap $ HashSet.singleton . Right) <$> wopt (checkMap (foldMapM $ fmap Set.singleton . left MsgBearerTokenAuthorityUnknownUser) (Set.map Right) $ multiUserField False Nothing) (fslpI MsgBearerTokenAuthorityUsers (mr MsgLdapIdentificationOrEmail) & setTooltip MsgBearerTokenAuthorityUsersTip) (Just $ Set.singleton <$> muid)
  let btfAuthority' :: FormResult (HashSet (Either UserGroupName UserId))
      btfAuthority'
        = (<>) <$> btfAuthorityGroups <*> ((hoistMaybe =<< btfAuthorityUsers) <|> pure HashSet.empty)

  let btfRoutesForm = HashSet.fromList <$> massInputListA routeField (const "") MsgBearerTokenRouteMissing (\p -> Just . SomeRoute $ AdminTokensR :#: p) ("token-routes" :: Text) (fslI MsgBearerTokenRoutes & setTooltip MsgBearerTokenRoutesTip) True Nothing
  btfRoutes' <- optionalActionW btfRoutesForm (fslI MsgBearerTokenRestrictRoutes) (Just True)

  let btfRestrictForm = massInputAccumEditW miAdd' miCell' (\p -> Just . SomeRoute $ AdminTokensR :#: p) miLayout' ("token-restrictions" :: Text) (fslI MsgBearerTokenRestrictions) False Nothing
        where miAdd' nudge = fmap (over (mapped . _1) tweakRes) . miForm nudge . Left
                where tweakRes res = res <&> \(newRoute, newRestr) oldRestrs -> pure (bool [(newRoute, newRestr)] [] $ newRoute `HashMap.member` HashMap.fromList oldRestrs)
              miCell' nudge = miForm nudge . Right
              miForm :: (Text -> Text)
                     -> Either (FieldView UniWorX) (Route UniWorX, Value)
                     -> Form (Route UniWorX, Value)
              miForm nudge mode csrf = do
                (routeRes, routeView) <- mpreq routeField (fslI MsgBearerTokenRestrictRoute & addName (nudge "route")) (mode ^? _Right . _1)
                (restrRes, restrView) <- mpreq (checkMap (left Text.pack . Aeson.eitherDecode . encodeUtf8 . fromStrict . unTextarea) (Textarea . toStrict . decodeUtf8 . Aeson.encodePretty) textareaField) (fslI MsgBearerTokenRestrictValue & addName (nudge "restr")) (mode ^? _Right . _2)

                return ((,) <$> routeRes <*> restrRes, case mode of
                           Left btn -> $(widgetFile "widgets/massinput/token-restrictions/add")
                           Right _  -> $(widgetFile "widgets/massinput/token-restrictions/cell")
                       )

              miLayout' :: MassInputLayout ListLength (Route UniWorX, Value) (Route UniWorX, Value)
              miLayout' lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/token-restrictions/layout")

  btfRestrict' <- fmap HashMap.fromList <$> btfRestrictForm

  btfAddAuth' <- fmap (assertM $ not . Set.null . dnfTerms) <$> wopt pathPieceField (fslI MsgBearerTokenAdditionalAuth & setTooltip MsgBearerTokenAdditionalAuthTip) Nothing

  btfExpiresAt' <- optionalActionW (aopt utcTimeField (fslI MsgBearerTokenExpires & setTooltip MsgBearerTokenExpiresTip) Nothing) (fslI MsgBearerTokenOverrideExpiration) (Just False)
  btfStartsAt' <- wopt utcTimeField (fslI MsgBearerTokenOverrideStart & setTooltip MsgBearerTokenOverrideStartTip) Nothing

  return $ BearerTokenForm
    <$> btfAuthority'
    <*> btfRoutes'
    <*> btfRestrict'
    <*> btfAddAuth'
    <*> btfExpiresAt'
    <*> btfStartsAt'


getAdminTokensR, postAdminTokensR :: Handler Html
getAdminTokensR = postAdminTokensR
postAdminTokensR = do
  ((bearerReq, bearerView), bearerEnc) <- runFormPost $ renderWForm FormStandard bearerTokenForm

  mjwt <- formResultMaybe bearerReq $ \BearerTokenForm{..} -> do
    uid <- requireAuthId
    let btfAuthority' = btfAuthority
          & HashSet.insert (Right uid)
          & HashSet.map (left toJSON)

    fmap Just . encodeBearer . set _bearerRestrictions btfRestrict =<< bearerToken btfAuthority' Nothing (maybe HashMap.empty (HashMap.singleton BearerTokenRouteEval) btfRoutes) btfAddAuth btfExpiresAt btfStartsAt

  siteLayoutMsg MsgMenuAdminTokens $ do
    setTitleI MsgMenuAdminTokens

    let bearerForm = wrapForm bearerView def
          { formMethod = POST
          , formAction = Just $ SomeRoute AdminTokensR
          , formEncoding = bearerEnc
          }

    warning <- notification NotificationBroad <$> messageI Warning MsgBearerTokenUsageWarning

    $(widgetFile "admin-tokens")
