{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}

module Main (main) where

import Data.ByteString.Lazy qualified as BL
import Optics
import Relude
import System.Process
import Test.Tasty qualified as Tasty
import Test.Tasty.HUnit qualified as Tasty
import TestContainers.Tasty qualified as TC

data TestConfig = TestConfig
  { redisHost :: String,
    redisPort :: Int,
    postgreSQLHost :: String,
    postgreSQLPort :: Int
  }
  deriving (Eq, Show, Generic)

makeFieldLabelsNoPrefix ''TestConfig

-- | Sets up and runs the containers required for this test suite.
setupContainers :: (TC.MonadDocker m) => m TestConfig
setupContainers = do
  redisContainer <-
    TC.run $
      TC.containerRequest (TC.fromTag "redis:6.2-alpine")
        -- Expose the port 6379 from within the container. The respective port
        -- on the host machine can be looked up using `containerPort` (see below).
        TC.& TC.setExpose [6379]
        -- Wait until the container is ready to accept requests. `run` blocks until
        -- readiness can be established.
        TC.& TC.setWaitingFor (TC.waitUntilMappedPortReachable 6379)
  postgreSQLContainer <-
    TC.run $
      TC.containerRequest (TC.fromTag "postgres:15.5")
        TC.& TC.setExpose [5432]
        TC.& TC.setWaitingFor (TC.waitUntilMappedPortReachable 5432)

  pure $
    TestConfig
      { redisHost = "0.0.0.0",
        redisPort =
          -- Look up the corresponding port on the host machine for the exposed
          -- port 6379.
          TC.containerPort redisContainer 6379,
        postgreSQLHost = "0.0.0.0",
        postgreSQLPort = TC.containerPort postgreSQLContainer 5432
      }

main :: IO ()
main =
  Tasty.defaultMain $
    -- Use `withContainers` to make the containers available in the closed over
    -- tests. Due to how Tasty handles resources `withContainers` passes down
    -- an IO action `start` to actually start up the containers. `start` can be
    -- invoked multiple times, Tasty makes sure to only start up the containrs
    -- once.
    --
    -- `withContainers` ensures the started containers are shut down correctly
    -- once execution leaves its scope.
    TC.withContainers setupContainers $
      \start ->
        Tasty.testGroup
          "Some test group"
          [ Tasty.testCase "Redis + PostgreSQL test" $ do
              -- Actually start the containers!!
              TestConfig {..} <- start
              -- ... assert some properties
              pure (),
            Tasty.testCase "Another Redis + PostgreSQL test" $ do
              -- Invoking `start` twice gives the same Endpoints!
              TestConfig {..} <- start
              (code, stdout, stderr) <- readProcess "ls -l /home" -- >>= BL.putStr
              _ <- BL.putStr stdout
              -- ... assert some properties
              pure (),
            Tasty.testCase "Another Redis + PostgreSQL test 2" $ do
              -- Invoking `start` twice gives the same Endpoints!
              TestConfig {..} <- start
              (code, stdout, stderr) <- readProcess "timeout 20s make dev" -- >>= BL.putStr
              _ <- BL.putStr stdout
              -- ... assert some properties
              pure ()
          ]
