module PostgREST.QueryBuilder.Procedure where

import           Data.Maybe
import           Data.Text                      (intercalate, unwords)
import qualified Hasql.Decoders                 as HD
import qualified Hasql.Encoders                 as HE
import qualified Hasql.Statement                as H
import           PostgREST.QueryBuilder.Private
import           PostgREST.Types
import           Protolude                      hiding (cast,
                                                 intercalate, replace)
import           Text.InterpolatedString.Perl6  (qc)

type ProcResults = (Maybe Int64, Int64, ByteString, ByteString)
callProc :: QualifiedIdentifier -> [PgArg] -> Bool -> SqlQuery -> SqlQuery -> Bool ->
            Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion ->
            H.Statement ByteString (Maybe ProcResults)
callProc qi pgArgs returnsScalar selectQuery countQuery countTotal isSingle paramsAsSingleObject asCsv asBinary binaryField pgVer =
  unicodeStatement sql (param HE.unknown) decodeProc True
  where
    sql =[qc|
      WITH
      {argsRecord},
      {sourceCTEName} AS (
        {sourceBody}
      )
      SELECT
        {countResultF} AS total_result_set,
        pg_catalog.count(_postgrest_t) AS page_total,
        {bodyF} AS body,
        {responseHeaders} AS response_headers
      FROM ({selectQuery}) _postgrest_t;|]

    (argsRecord, args)
      | paramsAsSingleObject = ("_args_record AS (SELECT NULL)", "$1::json")
      | null pgArgs = (ignoredBody, "")
      | otherwise = (
          unwords [
            normalizedBody <> ",",
            "_args_record AS (",
              "SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <>
                intercalate ", " ((\a -> pgFmtIdent (pgaName a) <> " " <> pgaType a) <$> pgArgs) <> ")",
            ")"]
         , intercalate ", " ((\a -> pgFmtIdent (pgaName a) <> " := _args_record." <> pgFmtIdent (pgaName a)) <$> pgArgs))

    sourceBody :: SqlFragment
    sourceBody
      | paramsAsSingleObject || null pgArgs =
          if returnsScalar
            then [qc| SELECT {fromQi qi}({args}) |]
            else [qc| SELECT * FROM {fromQi qi}({args}) |]
      | otherwise =
          if returnsScalar
            then [qc| SELECT {fromQi qi}({args}) FROM _args_record |]
            else [qc| SELECT _.*
                      FROM _args_record,
                      LATERAL ( SELECT * FROM {fromQi qi}({args}) ) _ |]

    bodyF
     | returnsScalar = scalarBodyF
     | isSingle = asJsonSingleF
     | asCsv = asCsvF
     | isJust binaryField = asBinaryF $ fromJust binaryField
     | otherwise = asJsonF

    scalarBodyF
     | asBinary = asBinaryF _procName
     | otherwise = unwords [
        "CASE",
          "WHEN pg_catalog.count(_postgrest_t) = 1",
            "THEN (json_agg(_postgrest_t." <> pgFmtIdent _procName <> ")->0)::character varying",
            "ELSE (json_agg(_postgrest_t." <> pgFmtIdent _procName <> "))::character varying",
        "END"]

    countResultF = if countTotal then "( "<> countQuery <> ")" else "null::bigint" :: Text
    _procName = qiName qi
    responseHeaders =
      if pgVer >= pgVersion96
        then "coalesce(nullif(current_setting('response.headers', true), ''), '[]')" :: Text -- nullif is used because of https://gist.github.com/steve-chavez/8d7033ea5655096903f3b52f8ed09a15
        else "'[]'" :: Text

    decodeProc = HD.rowMaybe procRow
    procRow = (,,,) <$> nullableColumn HD.int8 <*> column HD.int8
                    <*> column HD.bytea <*> column HD.bytea