module Auth.PWHash
  ( apHash
  , hashLogin
  , PWHashMessage(..)
  ) where

import Import.NoFoundation
import Database.Persist.Sql (SqlBackendCanRead)

import Utils.Metrics
import Utils.Form

import qualified Data.CaseInsensitive as CI

import Yesod.Auth.Util.PasswordStore (verifyPasswordWith)

import qualified Yesod.Auth.Message as Msg


data HashLogin = HashLogin
  { hashIdent :: CI Text
  , hashPassword :: Text
  } deriving (Generic, Typeable)

data PWHashMessage = MsgPWHashIdent
                   | MsgPWHashIdentPlaceholder
                   | MsgPWHashPassword
                   | MsgPWHashPasswordPlaceholder
  deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)


hashForm :: ( RenderMessage (HandlerSite m) FormMessage
            , RenderMessage (HandlerSite m) (ValueRequired (HandlerSite m))
            , RenderMessage (HandlerSite m) PWHashMessage
            , MonadHandler m
            ) => WForm m (FormResult HashLogin)
hashForm = do
  MsgRenderer mr <- getMsgRenderer
  aFormToWForm $ HashLogin
    <$> areq ciField (fslpI MsgPWHashIdent (mr MsgPWHashIdentPlaceholder)) Nothing
    <*> areq passwordField (fslpI MsgPWHashPassword (mr MsgPWHashPasswordPlaceholder)) Nothing

apHash :: Text
apHash = "PWHash"

hashLogin :: forall site.
             ( YesodAuth site
             , YesodPersist site
             , SqlBackendCanRead (YesodPersistBackend site)
             , PersistRecordBackend User (YesodPersistBackend site)
             , RenderMessage site PWHashMessage
             , RenderMessage site AFormMessage
             , RenderMessage site (ValueRequired site)
             , Button site ButtonSubmit
             ) => PWHashAlgorithm -> AuthPlugin site
hashLogin pwHashAlgo = AuthPlugin{..}
  where
    apName :: Text
    apName = apHash

    apDispatch :: forall m. MonadAuthHandler site m => Text -> [Text] -> m TypedContent
    apDispatch method [] | encodeUtf8 method == methodPost = liftSubHandler $ do
      ((loginRes, _), _) <- runFormPost $ renderWForm FormStandard hashForm
      tp <- getRouteToParent

      resp <- formResultMaybe loginRes $ \HashLogin{..} -> Just <$> do
        user <- liftHandler . runDB . getBy $ UniqueAuthentication hashIdent
        case user of
          Just (Entity _ User{ userAuthentication = AuthPWHash{..}, userIdent = CI.original -> userIdent })
            | verifyPasswordWith pwHashAlgo (2^) (encodeUtf8 hashPassword) (encodeUtf8 authPWHash) -> do -- (2^) is magic.
                observeLoginOutcome apName LoginSuccessful
                setCredsRedirect $ Creds apName userIdent []
          other -> do
            $logDebugS apName $ tshow other
            observeLoginOutcome apName LoginInvalidCredentials
            loginErrorMessageI LoginR Msg.InvalidLogin

      maybe (redirect $ tp LoginR) return resp
    apDispatch _ [] = badMethod
    apDispatch _ _ = notFound

    apLogin :: (Route Auth -> Route site) -> WidgetFor site ()
    apLogin toMaster = do
      (login, loginEnctype) <- handlerToWidget . generateFormPost $ renderWForm FormStandard hashForm
      let loginForm = wrapForm login FormSettings
            { formMethod = POST
            , formAction = Just . SomeRoute . toMaster $ PluginR apName []
            , formEncoding = loginEnctype
            , formAttrs = [("uw-no-navigate-away-prompt","")]
            , formSubmit = FormSubmit
            , formAnchor = Just "login--hash" :: Maybe Text
            }
      $(widgetFile "widgets/hash-login-form/hash-login-form")

