{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}

module Utils.Backends
  ( withPostgres
  ) where

import Control.Concurrent (threadDelay)
import Control.Monad.Logger (runNoLoggingT)
import qualified Data.ByteString.Char8 as ByteString
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid ((<>))
#endif
import Data.Pool (Pool, destroyAllResources)
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import Database.Persist.Postgresql (createPostgresqlPool)
import Database.Persist.Sql (SqlBackend)
import System.Exit (ExitCode(..), exitWith)
import System.IO (hPutStrLn, stderr)
import System.Process (readProcessWithExitCode)
import Test.Tasty (TestTree, withResource)

-- | Run a function with the PostgreSQL backend.
withPostgres :: FilePath -> (IO (Pool SqlBackend) -> TestTree) -> TestTree
withPostgres dir = withResource startPostgres stopPostgres
  where
    dir' = dir ++ "/postgresql/"
    -- running postgres
    startPostgres = do
      -- initialize local postgres server
      callProcess' "pg_ctl" ["-D", dir', "init"]
      -- modify configuration
      let confFile = dir' ++ "postgresql.conf"
      conf <- Text.readFile confFile
      Text.writeFile confFile . Text.unlines . modifyConf . Text.lines $ conf
      -- start postgres server
      callProcess' "pg_ctl"
        [ "-D", dir'
        , "-l", dir' ++ "postgres.log"
        , "-o", "-h '' -k '" ++ dir' ++ "'"
        , "start"
        ]
      threadDelay 1000000
      callProcess' "createdb" ["-h", dir', "test_db"]
      -- create a connection Pool
      let connString = ByteString.pack $ "postgresql:///test_db?host=" ++ dir'
      runNoLoggingT $ createPostgresqlPool connString 4
    stopPostgres pool = do
      callProcess' "pg_ctl" ["-D", dir', "stop"]
      destroyAllResources pool
    -- utilities
    callProcess' cmd args = do
      (code, out, err) <- readProcessWithExitCode cmd args ""
      case code of
        ExitSuccess -> return ()
        ExitFailure _ -> do
          hPutStrLn stderr out
          hPutStrLn stderr err
          exitWith code
    modifyConf conf = foldl modifyConf' conf
      [ ("client_min_messages", "warning")
      , ("log_min_messages", "info")
      , ("log_statement", "all")
      ]
    modifyConf' conf (k, v) =
      let setting = Text.unwords [k, "=", v]
      in case conf of
        [] -> [setting]
        line:conf' -> if ("#" <> k) `Text.isPrefixOf` line
          then setting : conf'
          else modifyConf' conf' (k, v)
