content: {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE OverloadedLabels #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE TypeApplications #-}
  {-# LANGUAGE TypeOperators #-}
  {-# LANGUAGE GADTs #-}
  {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE DeriveAnyClass                #-}
  {-# LANGUAGE DerivingStrategies                #-}
  {-# LANGUAGE ScopedTypeVariables                #-}
  
  module Gauge.DBSetup where
  
  import           Data.ByteString                ( ByteString )
  import qualified Data.ByteString.Char8         as C
  import           Control.Monad                  ( void )
  import           GHC.Generics
  import           Squeal.PostgreSQL
  -- Project imports
  import           Gauge.Schema                   ( Schemas
                                                  , DeviceOS
                                                  , IPLocation
                                                  )
  
  
  -- First create enums as they're needed in the Schema
  setup :: Definition (Public '[]) Schemas
  setup =
    createTypeEnumFrom @DeviceOS #device_os
      >>> createTypeCompositeFrom @IPLocation #ip_location
      >>> createTable
            #users
            (    serial8
            `as` #id
            :*   (text & notNullable)
            `as` #email
            :*   (text & notNullable)
            `as` #password
            :*   (text & nullable)
            `as` #first_name
            :*   (int2 & nullable)
            `as` #birthyear
            )
            (primaryKey #id `as` #pk_users :* unique #email `as` #email)
      >>> createTable
            #user_devices
            (    serial8
            `as` #id
            :*   notNullable int8
            `as` #user_id
            :*   (text & notNullable)
            `as` #token
            :*   (typedef #device_os & notNullable)
            `as` #os
            )
            (    primaryKey #id
            `as` #pk_user_devices
            :*   foreignKey #user_id #users #id (OnDelete Cascade) (OnUpdate Cascade)
            `as` #fk_user_id
            :*   unique #token
            `as` #token
            )
  
  -- Drop types last because tables depend on them
  teardown :: Definition Schemas (Public '[])
  teardown =
    dropTableCascade #user_devices
      >>> dropTableCascade #users
      >>> dropType #ip_location
      >>> dropType #device_os
  
  -- With env vars, we could use the commented keys
  data PGConfig = PGConfig
    { pgHost     :: String -- "PG_HOST"
    , pgPort     :: Int    -- "PG_PORT"
    , pgDbname   :: String -- "PG_DBNAME" 
    , pgUser     :: String -- "PG_USER"
    , pgPassword :: String -- "PG_PASSWORD" 
    }
    deriving (Generic, Show)
  
  -- | Helper: unused now, but primarily for testing locally
  makeConnStr :: PGConfig -> ByteString
  makeConnStr PGConfig { pgHost = host, pgPort = portNumber, pgDbname = dbName, pgUser = user, pgPassword = pw }
    = C.pack
      $  "host="
      <> host
      <> " dbname="
      <> dbName
      <> " user="
      <> user
      <> " password="
      <> pw
      <> " port="
      <> show portNumber
  
  connectionString :: ByteString
  connectionString = "host=localhost port=5432 dbname=exampledb user=postgres password=postgres"
  
  performDBAction :: Definition a b -> String -> IO ()
  performDBAction action message = do
    void
      $ withConnection connectionString
      $ manipulate_ (UnsafeManipulation "SET client_min_messages TO WARNING;")
      & pqThen (define action)
    putStrLn message
  
  initDB :: IO ()
  initDB =
    performDBAction setup "Initialized Schema & corresponding tables for Database"
  
  teardownDB :: IO ()
  teardownDB = performDBAction teardown "Dropped all database tables"
  
  dbSchema :: Definition '["public" ::: '[]] (Drop "public" '["public" ::: '[]])
  dbSchema = dropSchemaCascade #public
  
  dropDBSchema :: IO ()
  dropDBSchema = performDBAction dbSchema "Dropped Public schema from database"
  
  -- | Concatenate two `ByteString`s with a space between.
  (<+>) :: ByteString -> ByteString -> ByteString
  infixr 7 <+>
  str1 <+> str2 = str1 <> " " <> str2
  
  -- | Drop table custom SQL statement with 'cascade'
  dropTableCascade
    :: (Has sch schemas schema, Has tab schema ( 'Table table))
    => QualifiedAlias sch tab -- ^ table to remove
    -> Definition schemas (Alter sch (Drop tab schema) schemas)
  dropTableCascade tab =
    UnsafeDefinition $ "DROP TABLE" <+> renderSQL tab <> " cascade;"
  