{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
module Kubernetes.Client.Auth.TokenFile where

import           Control.Concurrent.STM
import           Data.Function                  ( (&) )
import           Data.Monoid                    ( (<>) )
import           Data.Text                      ( Text )
import           Data.Time.Clock
import           Kubernetes.Client.Auth.Internal.Types
import           Kubernetes.OpenAPI.Core
import           Kubernetes.Client.KubeConfig
                                         hiding ( token )
import qualified Data.Text                     as T
import qualified Data.Text.IO                  as T
import qualified Lens.Micro                    as L

data TokenFileAuth = TokenFileAuth { token :: TVar(Maybe Text)
                                   , expiry :: TVar(Maybe UTCTime)
                                   , file :: FilePath
                                   , period :: NominalDiffTime
                                   }

instance AuthMethod TokenFileAuth where
  applyAuthMethod _ tokenFile req = do
    t <- getToken tokenFile
    pure
      $           req
      `setHeader` toHeader ("authorization", "Bearer " <> t)
      &           L.set rAuthTypesL []

-- |Detects if token-file is specified in AuthConfig.
tokenFileAuth :: DetectAuth
tokenFileAuth auth (tlsParams, cfg) = do
  file <- tokenFile auth
  return $ do
    c <- setTokenFileAuth file cfg
    return (tlsParams, c)

-- |Configures the 'KubernetesClientConfig' to use TokenFile authentication.
setTokenFileAuth
  :: FilePath -> KubernetesClientConfig -> IO KubernetesClientConfig
setTokenFileAuth f kcfg = atomically $ do
  t <- newTVar (Nothing :: Maybe Text)
  e <- newTVar (Nothing :: Maybe UTCTime)
  return kcfg
    { configAuthMethods =
      [ AnyAuthMethod
          (TokenFileAuth { token = t, expiry = e, file = f, period = 60 })
      ]
    }

getToken :: TokenFileAuth -> IO Text
getToken auth = getCurrentToken auth >>= maybe (reloadToken auth) return

getCurrentToken :: TokenFileAuth -> IO (Maybe Text)
getCurrentToken TokenFileAuth { token, expiry } = do
  now         <- getCurrentTime
  maybeExpiry <- readTVarIO expiry
  maybeToken  <- readTVarIO token
  return $ do
    e <- maybeExpiry
    if e > now then maybeToken else Nothing

reloadToken :: TokenFileAuth -> IO Text
reloadToken TokenFileAuth { token, expiry, file, period } = do
  content <- T.readFile file
  let t = T.strip content
  now <- getCurrentTime
  atomically $ do
    writeTVar token  (Just t)
    writeTVar expiry (Just (addUTCTime period now))
  return t