{-# LANGUAGE OverloadedStrings #-}

module Snap.AtlassianConnect.Connect
  ( initConnectSnaplet
  ) where

-- Data file imports
import qualified Paths_atlassian_connect_core    as PACC

-- Helper Imports
import           Data.ConfigurationHelpers

-- Atlassian Connect Imports
import qualified Data.Connect.Descriptor         as D
import           Snap.AtlassianConnect.Data
import qualified Snap.AtlassianConnect.PageToken as PT
import qualified Snap.AtlassianConnect.Routes    as CR
import qualified Snap.AtlassianConnect.TimeUnits as TU

-- Standard imports
import qualified Control.Monad                   as CM
import qualified Control.Monad.IO.Class          as MI
import qualified Crypto.Cipher.AES               as CCA
import qualified Data.ByteString.Char8           as BSC
import qualified Data.Configurator               as DC
import qualified Data.Configurator.Types         as DCT
import qualified Data.EnvironmentHelpers         as DE
import           Data.Maybe                      (fromMaybe)
import           Data.Text
import           Data.Time.Units
import qualified Network.HostName                as HN
import qualified Network.URI                     as NU
import qualified Snap.Snaplet                    as SS

-- | Initialise the Atlassian Connect part of this application. This gives you an Atlassian Connect configuration that
-- comes with:
--
--  * An Atlassian Connect configuration file.
--  * The definition of what it means to be an Atlassian Connect Tenant.
--  * Default routes for your Atlassian Connect Descriptor. (With automatic configuration injection of Plugin Keys / Names)
--  * Page Token support
--  * Host Whitelist Support
--
-- In short, it is the easy way to get up and running with Atlassian Connect in your Snap applications.
initConnectSnaplet :: D.Plugin -> SS.SnapletInit b Connect
initConnectSnaplet plugin  = SS.makeSnaplet "connect" "Atlassian Connect" (Just dataDir) $ do
  SS.addRoutes CR.connectRoutes
  configurationDirectory <- SS.getSnapletFilePath
  MI.liftIO . fmap (toConnect plugin) $ SS.loadAppConfig "connect.cfg" configurationDirectory >>= loadConnectConfig

dataDir :: IO FilePath
dataDir = CM.liftM (++"/resources") PACC.getDataDir

data ConnectConfig = ConnectConfig
  { ccSecretKey        :: BSC.ByteString
  , ccBaseUrl          :: NU.URI
  , ccPageTokenTimeout :: TU.ConnectSecond
  , ccHostWhiteList    :: [Text]   -- TODO This host whitelist should be loaded from configuration
  }

toConnect :: D.Plugin -> ConnectConfig -> Connect
toConnect plugin conf = Connect
  { connectPlugin = plugin
  , connectAES = CCA.initAES $ ccSecretKey conf
  , connectBaseUrl = ccBaseUrl conf
  , connectPageTokenTimeout = D.Timeout . TU.getSecond . ccPageTokenTimeout $ conf
  , connectHostWhitelist = ccHostWhiteList conf
  }

loadConnectConfig :: DCT.Config -> IO ConnectConfig
loadConnectConfig connectConf = do
  rawBaseUrl <- require connectConf "base_url" "Missing base url in connect configuration file."
  secret <- require connectConf "secret_key" "Missing secret key in connect configuration file."
  hostWhiteListFromConfig <- require connectConf "valid_hosts" "Missing a valid_hosts whitelist."
  hostWhiteList <- addLocalHost hostWhiteListFromConfig
  let keyLength = BSC.length secret
  CM.when (keyLength /= 32) $ fail ("Expected Atlassian Connect secret_key to be 32 Hex Digits long but was actually: " ++ show keyLength)
  envBaseUrl <- DE.getEnv "CONNECT_BASE_URL"
  envSecretKey <- DE.getEnv "CONNECT_SECRET_KEY"
  let baseUrlToParse = fromMaybe rawBaseUrl envBaseUrl
  case NU.parseURI baseUrlToParse of
    Nothing -> fail("Could not parse the baseUrl in the configuration file: " ++ rawBaseUrl)
    Just baseUrl -> do
      pageTokenTimeoutInSeconds <- DC.lookupDefault PT.defaultTimeoutSeconds connectConf "page_token_timeout_seconds"
      return ConnectConfig
        { ccBaseUrl = baseUrl
        , ccSecretKey = maybe secret BSC.pack envSecretKey
        , ccPageTokenTimeout = pageTokenTimeoutInSeconds
        , ccHostWhiteList = hostWhiteList
        }

addLocalHost :: [Text] -> IO [Text]
addLocalHost originalHosts = do
  localhost <- HN.getHostName
  return $ (pack localhost) : originalHosts