{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}

import Control.Monad.Logger
import Control.Monad.Trans.Resource
import Control.Monad.Reader
import Data.List.NonEmpty (NonEmpty(..))
import Data.Text (Text)
import System.Log.FastLogger
import Test.Hspec
import Test.HUnit ((@?=))
import UnliftIO

import Database.Persist.Sql
import Database.Persist.Sql.Raw.QQ
import Database.Persist.Sqlite
import PersistTestPetType
import PersistentTestModels

main :: IO ()
main = hspec specs

_debugOn :: Bool
_debugOn = False

sqlite_database_file :: Text
sqlite_database_file = "testdb.sqlite3"
sqlite_database :: SqliteConnectionInfo
sqlite_database = mkSqliteConnectionInfo sqlite_database_file

runConn :: MonadUnliftIO m => SqlPersistT (LoggingT m) t -> m ()
runConn f = do
  let debugPrint = _debugOn
  let printDebug = if debugPrint then print . fromLogStr else void . return
  flip runLoggingT (\_ _ _ s -> printDebug s) $ do
    _ <- withSqlitePoolInfo sqlite_database 1 $ runSqlPool f
    return ()

db :: SqlPersistT (LoggingT (ResourceT IO)) () -> IO ()
db actions = do
  runResourceT $ runConn $ do
      runMigration testMigrate
      actions
      transactionUndo

specs :: Spec
specs = describe "persistent-qq" $ do
    it "sqlQQ/?-?" $ db $ do
        ret <- [sqlQQ| SELECT #{2 :: Int}+#{2 :: Int} |]
        liftIO $ ret @?= [Single (4::Int)]

    it "sqlQQ/?-?" $ db $ do
        ret <- [sqlQQ| SELECT #{5 :: Int}-#{3 :: Int} |]
        liftIO $ ret @?= [Single (2::Int)]

    it "sqlQQ/NULL" $ db $ do
        ret <- [sqlQQ| SELECT NULL |]
        liftIO $ ret @?= [Nothing :: Maybe (Single Int)]

    it "sqlQQ/entity" $ db $ do
        let insert'
              :: PersistStore backend
              => PersistEntity val
              => PersistEntityBackend val ~ BaseBackend backend
              => MonadIO m
              => val
              -> ReaderT backend m (Key val, val)
            insert' v = insert v >>= \k -> return (k, v)
        (p1k, p1) <- insert' $ Person "Mathias"   23 Nothing
        (p2k, p2) <- insert' $ Person "Norbert"   44 Nothing
        (p3k, _ ) <- insert' $ Person "Cassandra" 19 Nothing
        (_  , _ ) <- insert' $ Person "Thiago"    19 Nothing
        (a1k, a1) <- insert' $ Pet p1k "Rodolfo" Cat
        (a2k, a2) <- insert' $ Pet p1k "Zeno"    Cat
        (a3k, a3) <- insert' $ Pet p2k "Lhama"   Dog
        (_  , _ ) <- insert' $ Pet p3k "Abacate" Cat

        let runQuery
              :: (RawSql a, Functor m, MonadIO m)
              => Int
              -> ReaderT SqlBackend m [a]
            runQuery age =
              [sqlQQ|
                  SELECT ??, ??
                  FROM
                    ^{Person},
                    ^{Pet}
                  WHERE ^{Person}.@{PersonAge} >= #{age}
                      AND ^{Pet}.@{PetOwnerId} = ^{Person}.@{PersonId}
                      ORDER BY ^{Person}.@{PersonName}
              |]

        ret <- runQuery 20
        liftIO $ ret @?= [ (Entity p1k p1, Entity a1k a1)
                         , (Entity p1k p1, Entity a2k a2)
                         , (Entity p2k p2, Entity a3k a3) ]
        ret2 <- runQuery 20
        liftIO $ ret2 @?= [ (Just (Entity p1k p1), Just (Entity a1k a1))
                          , (Just (Entity p1k p1), Just (Entity a2k a2))
                          , (Just (Entity p2k p2), Just (Entity a3k a3)) ]
        ret3 <- runQuery 20
        liftIO $ ret3 @?= [ Just (Entity p1k p1, Entity a1k a1)
                          , Just (Entity p1k p1, Entity a2k a2)
                          , Just (Entity p2k p2, Entity a3k a3) ]

    it "sqlQQ/order-proof" $ db $ do
        let p1 = Person "Zacarias" 93 Nothing
        p1k <- insert p1

        let runQuery
              :: (RawSql a, Functor m, MonadIO m)
              => ReaderT SqlBackend m [a]
            runQuery = [sqlQQ| SELECT ?? FROM ^{Person} |]
        ret1 <- runQuery
        ret2 <- runQuery :: (MonadIO m) => SqlPersistT m [Entity (ReverseFieldOrder Person)]
        liftIO $ ret1 @?= [Entity p1k p1]
        liftIO $ ret2 @?= [Entity (RFOKey $ unPersonKey p1k) (RFO p1)]

    it "sqlQQ/OUTER JOIN" $ db $ do
        let insert' :: (PersistStore backend, PersistEntity val, PersistEntityBackend val ~ BaseBackend backend, MonadIO m)
                    => val -> ReaderT backend m (Key val, val)
            insert' v = insert v >>= \k -> return (k, v)
        (p1k, p1) <- insert' $ Person "Mathias"   23 Nothing
        (p2k, p2) <- insert' $ Person "Norbert"   44 Nothing
        (a1k, a1) <- insert' $ Pet p1k "Rodolfo" Cat
        (a2k, a2) <- insert' $ Pet p1k "Zeno"    Cat
        ret <- [sqlQQ|
          SELECT ??, ??
          FROM ^{Person}
          LEFT OUTER JOIN ^{Pet}
              ON ^{Person}.@{PersonId} = ^{Pet}.@{PetOwnerId}
          ORDER BY ^{Person}.@{PersonName}
        |]
        liftIO $ ret @?= [ (Entity p1k p1, Just (Entity a1k a1))
                         , (Entity p1k p1, Just (Entity a2k a2))
                         , (Entity p2k p2, Nothing) ]

    it "sqlQQ/values syntax" $ db $ do
        let insert' :: (PersistStore backend, PersistEntity val, PersistEntityBackend val ~ BaseBackend backend, MonadIO m)
                    => val -> ReaderT backend m (Key val, val)
            insert' v = insert v >>= \k -> return (k, v)
        (p1k, p1) <- insert' $ Person "Mathias"   23 (Just "red")
        (_  , _ ) <- insert' $ Person "Norbert"   44 (Just "green")
        (p3k, p3) <- insert' $ Person "Cassandra" 19 (Just "blue")
        (_  , _ ) <- insert' $ Person "Thiago"    19 (Just "yellow")
        let
          colors = Just "blue" :| Just "red" : [] :: NonEmpty (Maybe Text)
        ret <- [sqlQQ|
          SELECT ??
          FROM ^{Person}
          WHERE ^{Person}.@{PersonColor} IN %{colors}
        |]
        liftIO $ ret @?= [ (Entity p1k p1)
                         , (Entity p3k p3) ]
