{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module Database.Persist.TH.JsonEncodingSpec where

import TemplateTestImports

import Data.Aeson
import Data.Text (Text)
import Test.Hspec.QuickCheck
import Test.QuickCheck
import Test.QuickCheck.Instances ()

import Database.Persist.EntityDef
import Database.Persist.ImplicitIdDef
import Database.Persist.ImplicitIdDef.Internal (fieldTypeFromTypeable)
import Database.Persist.Types

mkPersist sqlSettings [persistLowerCase|
JsonEncoding json
    name Text
    age  Int
    Primary name
    deriving Show Eq

JsonEncoding2 json
    name Text
    age Int
    blood Text
    Primary name blood
    deriving Show Eq

JsonEncMigrationOnly json
    name Text
    age  Int
    foo  Text MigrationOnly
|]

instance Arbitrary JsonEncoding where
    arbitrary = JsonEncoding <$> arbitrary <*> arbitrary

instance Arbitrary JsonEncoding2 where
    arbitrary = JsonEncoding2 <$> arbitrary <*> arbitrary <*> arbitrary

pass :: IO ()
pass = pure ()

asIO :: IO a -> IO a
asIO = id

spec :: Spec
spec = describe "JsonEncodingSpec" $ do
    let
        subject =
            JsonEncoding "Bob" 32
        subjectEntity =
            Entity (JsonEncodingKey (jsonEncodingName subject)) subject

    it "encodes without an ID field" $ do
        toJSON subjectEntity
            `shouldBe`
                object
                    [ ("name", String "Bob")
                    , ("age", toJSON (32 :: Int))
                    , ("id", String "Bob")
                    ]

    it "decodes without an ID field" $ do
        let
            json_ = encode . object $
                [ ("name", String "Bob")
                , ("age", toJSON (32 :: Int))
                ]
        eitherDecode json_
            `shouldBe`
                Right subjectEntity

    it "has informative decoder errors" $ do
        let
            json_ = encode Null
        (eitherDecode json_ :: Either String JsonEncoding)
            `shouldBe`
                Left "Error in $: parsing JsonEncoding failed, expected Object, but encountered Null"

    prop "works with a Primary" $ \jsonEncoding -> do
        let
            ent =
                Entity (JsonEncodingKey (jsonEncodingName jsonEncoding)) jsonEncoding
        decode (encode ent)
            `shouldBe`
                Just ent

    prop "excuse me what" $ \j@JsonEncoding{..} -> do
        let
            ent =
                Entity (JsonEncodingKey jsonEncodingName) j
        toJSON ent
            `shouldBe`
                object
                    [ ("name", toJSON jsonEncodingName)
                    , ("age", toJSON jsonEncodingAge)
                    , ("id", toJSON jsonEncodingName)
                    ]

    prop "round trip works with composite key" $ \j@JsonEncoding2{..} -> do
        let
            key = JsonEncoding2Key jsonEncoding2Name jsonEncoding2Blood
            ent =
              Entity key j
        decode (encode ent)
            `shouldBe`
                Just ent

    prop "works with a composite key" $ \j@JsonEncoding2{..} -> do
        let
            key = JsonEncoding2Key jsonEncoding2Name jsonEncoding2Blood
            ent =
                Entity key j
        toJSON ent
            `shouldBe`
                object
                  [ ("name", toJSON jsonEncoding2Name)
                  , ("age", toJSON jsonEncoding2Age)
                  , ("blood", toJSON jsonEncoding2Blood)
                  , ("id", toJSON key)
                  ]
