{-# LANGUAGE CPP #-}
module Database.Beam.Postgres.Test where

#if MIN_VERSION_base(4,12,0)
import           Prelude hiding (fail)
#endif

import qualified Database.PostgreSQL.Simple as Pg

import           Control.Exception (SomeException(..), bracket, catch)
import           Control.Concurrent (threadDelay)

import           Control.Monad (void)
#if MIN_VERSION_base(4,12,0)
import           Control.Monad.Fail (MonadFail(..))
#endif

import           Data.ByteString (ByteString)
import           Data.Semigroup
import           Data.String

import qualified Hedgehog

import           System.IO.Temp
import           System.Process
import           System.Exit
import           System.FilePath
import           System.Directory

withTestPostgres :: String -> IO ByteString -> (Pg.Connection -> IO a) -> IO a
withTestPostgres dbName getConnStr action = do
  connStr <- getConnStr

  let connStrTemplate1 = connStr <> " dbname=template1"
      connStrDb = connStr <> " dbname=" <> fromString dbName

      withTemplate1 :: (Pg.Connection -> IO b) -> IO b
      withTemplate1 = bracket (Pg.connectPostgreSQL connStrTemplate1) Pg.close

      createDatabase = withTemplate1 $ \c -> do
                         Pg.execute_ c (fromString ("CREATE DATABASE " <> dbName))

                         Pg.connectPostgreSQL connStrDb
      dropDatabase c = do
        Pg.close c
        withTemplate1 $ \c' -> do
            Pg.execute_ c' (fromString ("DROP DATABASE " <> dbName))
            pure ()

  bracket createDatabase dropDatabase action

startTempPostgres :: IO (ByteString, IO ())
startTempPostgres = do
  tmpDir <- getCanonicalTemporaryDirectory
  pgDataDir <- createTempDirectory tmpDir "postgres-data"

  callProcess "pg_ctl" [ "init", "-D", pgDataDir ]

  -- Use 'D' because otherwise, the path is too long on OS X
  pgHdl <- spawnProcess "postgres"
                        [ "-D", pgDataDir
                        , "-k", pgDataDir, "-h", "" ]

  putStrLn ("Using " ++ pgDataDir ++ " as postgres host")

  let waitForPort 10 = fail "Could not connect to postgres"
      waitForPort n = do
        (code, stdout, stderr) <- readProcessWithExitCode "pg_ctl" [ "status", "-D", pgDataDir ] ""
        case code of
          ExitSuccess -> waitForSocket 0
          ExitFailure _ -> threadDelay 1000000 >> waitForPort (n + 1)

      waitForSocket 10 = fail "Could not connect to postgres (waitForSocket)"
      waitForSocket n = do
        skExists <- doesFileExist (pgDataDir </> ".s.PGSQL.5432")
        if skExists then pure () else threadDelay 1000000 >> waitForSocket (n + 1)

  waitForPort 0
  putStrLn "Completed waiting for postgres"

  pure ( fromString ("host=" ++ pgDataDir)
       , void (callProcess "pg_ctl" [ "stop", "-D", pgDataDir ]))

#if MIN_VERSION_base(4,12,0)
-- TODO orphan instances are bad
instance Monad m => MonadFail (Hedgehog.PropertyT m) where
    fail _ = Hedgehog.failure
#endif