{-# LANGUAGE ScopedTypeVariables #-}
module Lib.SystemPaths where

import           Startlude               hiding ( (<.>)
                                                , Reader
                                                , ask
                                                , runReader
                                                )

import           Control.Effect.Labelled        ( Labelled
                                                , runLabelled
                                                )
import           Control.Effect.Reader.Labelled
import           Data.List
import qualified Data.Text                     as T
import qualified Protolude.Base                as P
                                                ( show )
import           System.IO.Error                ( isDoesNotExistError )
import           System.Directory

import           Lib.Types.Core
import           Settings

strJoin :: Char -> Text -> Text -> Text
strJoin c a b = case (T.unsnoc a, T.uncons b) of
    (Nothing     , Nothing     ) -> ""
    (Nothing     , Just _      ) -> b
    (Just _      , Nothing     ) -> a
    (Just (_, c0), Just (c1, s)) -> case (c0 == c, c1 == c) of
        (True , True ) -> a <> s
        (False, False) -> a <> T.singleton c <> b
        _              -> a <> b

(</>) :: Text -> Text -> Text
(</>) = strJoin '/'

(<.>) :: Text -> Text -> Text
(<.>) = strJoin '.'

-- system paths behave the same as FilePaths mostly except that they can be rebased onto alternative roots so that things
-- can be tested in an isolated way. This uses a church encoding.
newtype SystemPath = SystemPath { relativeTo :: Text -> Text }
instance Eq SystemPath where
    (==) a b = a `relativeTo` "/" == b `relativeTo` "/"
instance Show SystemPath where
    show sp = P.show $ sp `relativeTo` "/"
instance Semigroup SystemPath where
    (SystemPath f) <> (SystemPath g) = SystemPath $ g . f
instance Monoid SystemPath where
    mempty = SystemPath id
instance IsString SystemPath where
    fromString (c : cs) = case c of
        '/' -> relBase . toS $ cs
        _   -> relBase . toS $ c : cs
    fromString [] = mempty

leaf :: SystemPath -> Text
leaf = last . T.splitOn "/" . show

relBase :: Text -> SystemPath
relBase = SystemPath . flip (</>)

type HasFilesystemBase sig m = HasLabelled "filesystemBase" (Reader Text) sig m

injectFilesystemBase :: Monad m => Text -> Labelled "filesystemBase" (ReaderT Text) m a -> m a
injectFilesystemBase fsbase = flip runReaderT fsbase . runLabelled @"filesystemBase"

injectFilesystemBaseFromContext :: Monad m => AppSettings -> Labelled "filesystemBase" (ReaderT Text) m a -> m a
injectFilesystemBaseFromContext = injectFilesystemBase . appFilesystemBase

getAbsoluteLocationFor :: HasFilesystemBase sig m => SystemPath -> m Text
getAbsoluteLocationFor path = do
    base <- ask @"filesystemBase"
    pure $ path `relativeTo` base

readSystemPath :: (HasFilesystemBase sig m, MonadIO m) => SystemPath -> m (Maybe Text)
readSystemPath path = do
    loadPath <- getAbsoluteLocationFor path
    liftIO
        $       (Just <$> readFile (toS loadPath))
        `catch` (\(e :: IOException) -> if isDoesNotExistError e then pure Nothing else throwIO e)

existsSystemPath :: (HasFilesystemBase sig m, MonadIO m) => SystemPath -> m Bool
existsSystemPath path = do
    checkPath <- getAbsoluteLocationFor path
    liftIO . doesPathExist $ toS checkPath

-- like the above, but throws IO error if file not found
readSystemPath' :: (HasFilesystemBase sig m, MonadIO m) => SystemPath -> m Text
readSystemPath' path = do
    loadPath <- getAbsoluteLocationFor path
    liftIO . readFile . toS $ loadPath

writeSystemPath :: (HasFilesystemBase sig m, MonadIO m) => SystemPath -> Text -> m ()
writeSystemPath path contents = do
    loadPath <- getAbsoluteLocationFor path
    liftIO $ writeFile (toS loadPath) contents

deleteSystemPath :: (HasFilesystemBase sig m, MonadIO m) => SystemPath -> m ()
deleteSystemPath path = do
    loadPath <- getAbsoluteLocationFor path
    liftIO $ removePathForcibly (toS loadPath)

dbPath :: (HasFilesystemBase sig m, HasLabelled "sqlDatabase" (Reader Text) sig m) => m Text
dbPath = do
    rt     <- ask @"filesystemBase"
    dbName <- ask @"sqlDatabase"
    pure $ rt </> "root/agent" </> toS dbName

uiPath :: SystemPath
uiPath = "/var/www/html"

agentDataDirectory :: SystemPath
agentDataDirectory = "/root/agent"

agentTmpDirectory :: SystemPath
agentTmpDirectory = "/root/agent/tmp"

bootConfigPath :: SystemPath
bootConfigPath = "/boot/config.txt"

bootConfigTempPath :: SystemPath
bootConfigTempPath = "/boot/config_tmp.txt"

executablePath :: SystemPath
executablePath = "/usr/local/bin"

-- Caches --

iconBasePath :: SystemPath
iconBasePath = "/root/agent/icons"

-- Nginx --

nginxConfig :: SystemPath
nginxConfig = "/etc/nginx/nginx.conf"

journaldConfig :: SystemPath
journaldConfig = "/etc/systemd/journald.conf"

nginxSitesAvailable :: SystemPath -> SystemPath
nginxSitesAvailable = ("/etc/nginx/sites-available" <>)

nginxSitesEnabled :: SystemPath -> SystemPath
nginxSitesEnabled = ("/etc/nginx/sites-enabled" <>)

nginxTorConf :: SystemPath
nginxTorConf = "/start9-ambassador.conf"

nginxSslConf :: SystemPath
nginxSslConf = "/start9-ambassador-ssl.conf"

-- SSH --

sshKeysDirectory :: SystemPath
sshKeysDirectory = "/home/pi/.ssh"

sshKeysFilePath :: SystemPath
sshKeysFilePath = sshKeysDirectory <> "authorized_keys"

-- Zero Conf --

avahiPath :: SystemPath
avahiPath = "/etc/avahi"

avahiServiceFolder :: SystemPath
avahiServiceFolder = avahiPath <> "services"

avahiServicePath :: Text -> SystemPath
avahiServicePath svc = avahiServiceFolder <> relBase (svc <.> "service")

-- Ambassador UI --

ambassadorUiPath :: SystemPath
ambassadorUiPath = uiPath <> "/start9-ambassador"

ambassadorUiManifestPath :: SystemPath
ambassadorUiManifestPath = ambassadorUiPath <> "/client-manifest.yaml"

-- Tor --

agentTorHiddenServiceDirectory :: SystemPath
agentTorHiddenServiceDirectory = "/var/lib/tor/agent"

agentTorHiddenServiceHostnamePath :: SystemPath
agentTorHiddenServiceHostnamePath = agentTorHiddenServiceDirectory <> "/hostname"

agentTorHiddenServicePrivateKeyPath :: SystemPath
agentTorHiddenServicePrivateKeyPath = agentTorHiddenServiceDirectory <> "/hs_ed25519_secret_key"

-- Server Config --

serverNamePath :: SystemPath
serverNamePath = "/root/agent/name.txt"

disableAutoCheckUpdatesPath :: SystemPath
disableAutoCheckUpdatesPath = "/root/agent/.disableAutoCheckUpdates"

altRegistryUrlPath :: SystemPath
altRegistryUrlPath = "/root/agent/alt_registry_url.txt"

-- Session Auth Key --

sessionSigningKeyPath :: SystemPath
sessionSigningKeyPath = "/root/agent/start9.aes"

-- AppMgr --

appMgrRootPath :: SystemPath
appMgrRootPath = "/root/appmgr"

appMgrAppPath :: AppId -> SystemPath
appMgrAppPath = ((appMgrRootPath <> "apps") <>) . relBase . unAppId

lifelineBinaryPath :: SystemPath
lifelineBinaryPath = "/usr/local/bin/lifeline"

-- Open SSL --

rootCaDirectory :: SystemPath
rootCaDirectory = agentDataDirectory <> "/ca"

rootCaKeyPath :: SystemPath
rootCaKeyPath = rootCaDirectory <> "/private/embassy-root-ca.key.pem"

rootCaCertPath :: SystemPath
rootCaCertPath = rootCaDirectory <> "/certs/embassy-root-ca.cert.pem"

rootCaOpenSslConfPath :: SystemPath
rootCaOpenSslConfPath = rootCaDirectory <> "/openssl.conf"

intermediateCaDirectory :: SystemPath
intermediateCaDirectory = rootCaDirectory <> "/intermediate"

intermediateCaKeyPath :: SystemPath
intermediateCaKeyPath = intermediateCaDirectory <> "/private/embassy-int-ca.key.pem"

intermediateCaCertPath :: SystemPath
intermediateCaCertPath = intermediateCaDirectory <> "/certs/embassy-int-ca.crt.pem"

intermediateCaOpenSslConfPath :: SystemPath
intermediateCaOpenSslConfPath = intermediateCaDirectory <> "/openssl.conf"

sslDirectory :: SystemPath
sslDirectory = "/etc/nginx/ssl"

entityKeyPath :: Text -> SystemPath
entityKeyPath hostname = sslDirectory <> relBase ("/" <> hostname <> "-local.key.pem")

entityCertPath :: Text -> SystemPath
entityCertPath hostname = sslDirectory <> relBase ("/" <> hostname <> "-local.crt.pem")

entityConfPath :: Text -> SystemPath
entityConfPath hostname = sslDirectory <> relBase ("/" <> hostname <> "-local.conf")

-- Systemd

agentServicePath :: SystemPath
agentServicePath = "/etc/systemd/system/agent.service"
