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

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

import Utils.Form

import Data.CaseInsensitive (CI)
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
                   | MsgPWHashPassword
  deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)


hashForm :: ( RenderMessage site FormMessage
            , RenderMessage site PWHashMessage
            , Button site ButtonSubmit
            ) => AForm (HandlerT site IO) HashLogin
hashForm = HashLogin
  <$> areq ciField (fslpI MsgPWHashIdent "Identifikation") Nothing
  <*> areq passwordField (fslpI MsgPWHashPassword "Passwort") Nothing


hashLogin :: ( YesodAuth site
             , YesodPersist site
             , SqlBackendCanRead (YesodPersistBackend site)
             , RenderMessage site FormMessage
             , RenderMessage site PWHashMessage
             , Button site ButtonSubmit
             ) => PWHashAlgorithm -> AuthPlugin site
hashLogin pwHashAlgo = AuthPlugin{..}
  where
    apName = "PWHash"
    apDispatch "POST" [] = do
      ((loginRes, _), _) <- lift . runFormPost $ renderAForm FormStandard hashForm
      case loginRes of
        FormFailure errs -> do
          forM_ errs $ addMessage Error . toHtml
          redirect LoginR
        FormMissing -> redirect LoginR
        FormSuccess HashLogin{..} -> do
          user <- lift . runDB . getBy $ UniqueAuthentication hashIdent
          case user of
            Just (Entity _ User{ userAuthentication = AuthPWHash{..}, userIdent = CI.original -> userIdent })
              | verifyPasswordWith pwHashAlgo (2^) (encodeUtf8 hashPassword) (encodeUtf8 authPWHash) -> -- (2^) is magic.
                  lift . setCredsRedirect $ Creds apName userIdent []
            other -> do
              $logDebugS "PWHash" $ tshow other
              loginErrorMessageI LoginR Msg.InvalidLogin
    -- apDispatch "GET" [] = do
    --   authData <- lookupBasicAuth
    --   pwdata <- liftIO $ Yaml.decodeFileEither fp

    --   addHeader "WWW-Authenticate" [st|Basic realm="uni2work maintenance auth" charset="UTF-8"|]

    --   case pwdata of
    --     Left err -> $logDebugS "Auth" $ tshow err
    --     Right pws -> $logDebugS "Auth" $ tshow (length pws) <> " pw entries"

    --   case (authData, pwdata) of
    --     (Nothing, _) -> do
    --       notAuthenticated
    --     (Just (usr, (Text.encodeUtf8 -> pw)), Right pwdata')
    --       | [ PWEntry{ pwUser = pwUser@(User{..}), pwHash = (Text.encodeUtf8 -> pwHash) } ]
    --           <- [ pwe | pwe@PWEntry{..} <- pwdata'
    --                    , let User{..} = pwUser
    --                    , userIdent  == usr
    --                    , userPlugin == apName
    --              ]
    --       , verifyPassword pw pwHash
    --         -> lift $ do
    --           runDB . void $ insertUnique pwUser
    --           setCredsRedirect $ Creds apName userIdent []
    --     _ -> permissionDenied "Invalid auth"
    apDispatch _ _ = notFound
    apLogin toMaster = do
      (login, loginEnctype) <- handlerToWidget . generateFormPost $ renderAForm FormStandard hashForm
      let loginForm = wrapForm login FormSettings
            { formMethod = POST
            , formAction = Just . SomeRoute . toMaster $ PluginR "PWHash" []
            , formEncoding = loginEnctype
            , formAttrs = []
            , formSubmit = FormSubmit
            , formAnchor = Just "login--hash" :: Maybe Text
            }
      $(widgetFile "widgets/hash-login-form/hash-login-form")

