{-# LANGUAGE OverloadedStrings #-}
module Data.CQRS.PostgreSQL.Internal.Migration
    ( applyMigrations
    , uuid
    ) where

import           Control.Monad (forM_, (>=>))
import           Data.ByteString (ByteString)
import           Data.CQRS.PostgreSQL.Internal.Utils (badQueryResultMsg, execSql, ioQuery, withTransaction, SqlValue(..))
import           Data.UUID.Types (UUID)
import qualified Data.UUID.Types as U
import           Data.Text.Encoding (decodeUtf8)
import           Database.PostgreSQL.LibPQ (Connection)
import qualified System.IO.Streams as Streams
import qualified System.IO.Streams.Combinators as SC

-- | Convert valid UUID string to a value.
uuid :: ByteString -> UUID
uuid s =
  case U.fromASCIIBytes s of
    Nothing -> error $ "Invalid UUID string: " ++ show s
    Just u -> u

-- | Apply all necessary migrations to event store database.
-- We use a special table to track which migrations have been
-- applied.
applyMigrations :: Connection -> [(UUID, ByteString)] -> IO ()
applyMigrations c migrations = do
  -- Must always create the change log if necessary
  withTransaction c $ execSql c sqlCreateChangeSetTbl [ ]
  -- Apply all the migrations.
  forM_ migrations $ \(changeSetId, sql) -> do
    let changeSetIdSql = SqlUUID $ Just changeSetId
    let sqlText = decodeUtf8 sql
    withTransaction c $ do
      -- Check if change set has already been applied
      existingChangeSet <- ioQuery c sqlFindChangeSet [ changeSetIdSql ] $
                             SC.map (unpackChangeSet changeSetId) >=> Streams.read
      case existingChangeSet of
        Just (_, sqlText') | sqlText == sqlText' ->
          return () -- Already applied, do nothing
        Just _ ->
          -- Applied, but SQL doesn't match. That's a huge problem, so we'll error out.
          error $ "Migration error: Changeset SQL modified: UUID " ++ show changeSetId
        Nothing -> do
          execSql c sqlInsertChangeSet [ changeSetIdSql, SqlText $ Just sqlText ]
          execSql c sql [ ]

  where
    unpackChangeSet _ [ SqlUUID (Just changeSetId), SqlText (Just sqlText) ] = (changeSetId, sqlText)
    unpackChangeSet changeSetId columns = error $ badQueryResultMsg [show changeSetId] columns

    -- Migrations support SQL:
    sqlCreateChangeSetTbl =
        "CREATE TABLE IF NOT EXISTS changeset ( \
         \  changeset_id UUID PRIMARY KEY, \
         \  sql TEXT NOT NULL \
         \)"
    sqlFindChangeSet =
        "SELECT changeset_id, sql FROM changeset \
        \ WHERE changeset_id = $1"
    sqlInsertChangeSet =
        "INSERT INTO changeset (changeset_id, sql) \
        \ VALUES ($1, $2)"