-- | Functions for fetching and updating @'Metadata' in the catalog.
module Hasura.RQL.DDL.Schema.Catalog
  ( fetchMetadataFromCatalog
  , fetchMetadataAndResourceVersionFromCatalog
  , fetchMetadataResourceVersionFromCatalog
  , fetchMetadataNotificationsFromCatalog
  , insertMetadataInCatalog
  , setMetadataInCatalog
  , bumpMetadataVersionInCatalog
  ) where

import           Hasura.Prelude

import qualified Database.PG.Query                   as Q

import           Hasura.Backends.Postgres.Connection
import           Hasura.Base.Error
import           Hasura.RQL.Types.Metadata
import           Hasura.RQL.Types.SchemaCache        (MetadataResourceVersion (..),
                                                      initialResourceVersion)
import           Hasura.RQL.Types.SchemaCache.Build  (CacheInvalidations)
import           Hasura.Server.Types                 (InstanceId (..))

import           Data.Bifunctor                      (bimap)

fetchMetadataFromCatalog :: Q.TxE QErr Metadata
fetchMetadataFromCatalog = do
  rows <- Q.withQE defaultTxErrorHandler
    [Q.sql|
       SELECT metadata from hdb_catalog.hdb_metadata
    |] () True
  case rows of
    []                           -> pure emptyMetadata
    [Identity (Q.AltJ metadata)] -> pure metadata
    _                            -> throw500 "multiple rows in hdb_metadata table"

fetchMetadataAndResourceVersionFromCatalog :: Q.TxE QErr (Metadata, MetadataResourceVersion)
fetchMetadataAndResourceVersionFromCatalog = do
  rows <- Q.withQE defaultTxErrorHandler
    [Q.sql|
       SELECT metadata, resource_version from hdb_catalog.hdb_metadata
    |] () True
  case rows of
    []                                   -> pure (emptyMetadata, initialResourceVersion)
    [(Q.AltJ metadata, resourceVersion)] -> pure (metadata, MetadataResourceVersion resourceVersion)
    _                                    -> throw500 "multiple rows in hdb_metadata table"

fetchMetadataResourceVersionFromCatalog :: Q.TxE QErr MetadataResourceVersion
fetchMetadataResourceVersionFromCatalog = do
  rows <- Q.withQE defaultTxErrorHandler
    [Q.sql|
       SELECT resource_version from hdb_catalog.hdb_metadata
    |] () True
  case rows of
    []                         -> pure initialResourceVersion
    [Identity resourceVersion] -> pure (MetadataResourceVersion resourceVersion)
    _                          -> throw500 "multiple rows in hdb_metadata table"

fetchMetadataNotificationsFromCatalog :: MetadataResourceVersion -> InstanceId -> Q.TxE QErr [(MetadataResourceVersion, CacheInvalidations)]
fetchMetadataNotificationsFromCatalog (MetadataResourceVersion resourceVersion) instanceId = do
  fmap (bimap MetadataResourceVersion Q.getAltJ) <$>
    Q.withQE defaultTxErrorHandler
      [Q.sql|
         SELECT resource_version, notification
         FROM hdb_catalog.hdb_schema_notifications
         WHERE resource_version > $1 AND instance_id != ($2::uuid)
      |] (resourceVersion, instanceId) True

-- Used to increment metadata version when no other changes are required
bumpMetadataVersionInCatalog :: Q.TxE QErr  ()
bumpMetadataVersionInCatalog = do
    Q.unitQE defaultTxErrorHandler
      [Q.sql|
      UPDATE hdb_catalog.hdb_metadata
      SET resource_version = hdb_catalog.hdb_metadata.resource_version + 1
      |] () True

insertMetadataInCatalog :: Metadata -> Q.TxE QErr ()
insertMetadataInCatalog metadata =
  Q.unitQE defaultTxErrorHandler
    [Q.sql|
    INSERT INTO hdb_catalog.hdb_metadata(id, metadata)
    VALUES (1, $1::json)
    |] (Identity $ Q.AltJ metadata) True

setMetadataInCatalog :: Maybe MetadataResourceVersion -> Metadata -> Q.TxE QErr MetadataResourceVersion
setMetadataInCatalog mResourceVersion metadata = case mResourceVersion of
  -- If a resource version isn't specified update the metadata and bump the version
  Nothing -> do
    rows <- Q.withQE defaultTxErrorHandler
      [Q.sql|
      INSERT INTO hdb_catalog.hdb_metadata(id, metadata)
      VALUES (1, $1::json)
      ON CONFLICT (id) DO UPDATE SET
        metadata = $1::json,
        resource_version = hdb_catalog.hdb_metadata.resource_version + 1
      RETURNING resource_version
      |] (Identity $ Q.AltJ metadata) True

    case rows of
      [Identity newResourceVersion] -> pure $ MetadataResourceVersion newResourceVersion
      _                             -> throw500 "error writing to hdb_metadata table"

  -- If a resource version is specified, check that it matches and...
  --   If so: Update the metadata and bump the version
  --   If not: Throw a 409 error
  Just resourceVersion -> do
    rows <- Q.withQE defaultTxErrorHandler
      [Q.sql|
      INSERT INTO hdb_catalog.hdb_metadata(id, metadata)
      VALUES (1, $1::json)
      ON CONFLICT (id) DO UPDATE SET
        metadata = $1::json,
        resource_version = hdb_catalog.hdb_metadata.resource_version + 1
        WHERE hdb_catalog.hdb_metadata.resource_version = $2
      RETURNING resource_version
      |] (Q.AltJ metadata, getMetadataResourceVersion resourceVersion) True
    case rows of
      []                    -> throw409 $ "metadata resource version referenced (" <>  tshow (getMetadataResourceVersion resourceVersion) <> ") did not match current version"
      [Identity newResourceVersion] -> pure $ MetadataResourceVersion newResourceVersion
      _                     -> throw500 "multiple rows in hdb_metadata table"