
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE QuasiQuotes #-}

module Test.Offline.JSON (tests) where

import Data.Aeson
import Data.ByteString.Lazy.Char8 qualified as C8
import Data.ByteString qualified as B
import Data.Either
import Gargantext.API.Errors
import Gargantext.API.Node.Corpus.Types
import Gargantext.API.Node.Types
import Gargantext.API.Routes.Named.Publish (PublishRequest)
import Gargantext.API.Routes.Named.Remote
import Gargantext.API.Viz.Types
import Gargantext.Core.Types.Phylo
import Gargantext.Core.Viz.Phylo qualified as VizPhylo
import Gargantext.Database.Admin.Types.Node
import Paths_gargantext
import Prelude
import Test.Instances (genFrontendErr)
import Test.Hspec
import Test.HUnit
import Test.QuickCheck
import Text.RawString.QQ

jsonRoundtrip :: (Show a, FromJSON a, ToJSON a, Eq a) => a -> Property
jsonRoundtrip a =
  counterexample ("Parsed JSON: " <> C8.unpack (encode a)) $ eitherDecode (encode a) === Right a

class (Show a, FromJSON a, ToJSON a, Eq a, Enum a, Bounded a) => EnumBoundedJSON a
instance EnumBoundedJSON BackendErrorCode
instance EnumBoundedJSON NodeType
instance EnumBoundedJSON NodePublishPolicy

jsonEnumRoundtrip :: forall a. Dict EnumBoundedJSON a -> Property
jsonEnumRoundtrip d = case d of
  Dict -> conjoin $ map (prop Dict) [minBound .. maxBound]
  where
    prop :: Dict EnumBoundedJSON a -> a -> Property
    prop Dict a = counterexample ("Parsed JSON: " <> C8.unpack (encode a)) $ eitherDecode (encode a) === Right a

-- | Tests /all/ the 'BackendErrorCode' and their associated 'FrontendError' payloads.
jsonFrontendErrorRoundtrip :: Property
jsonFrontendErrorRoundtrip = conjoin $ map mk_prop [minBound .. maxBound]
  where
    mk_prop :: BackendErrorCode -> Property
    mk_prop code = forAll (genFrontendErr code) $ \a ->
      counterexample ("Parsed JSON: " <> C8.unpack (encode a)) $ eitherDecode (encode a) === Right a

tests :: Spec
tests = describe "JSON" $ do
  it "NodeId roundtrips"         (property $ jsonRoundtrip @NodeId)
  it "RootId roundtrips"         (property $ jsonRoundtrip @RootId)
  it "Datafield roundtrips"      (property $ jsonRoundtrip @Datafield)
  it "WithQuery roundtrips"      (property $ jsonRoundtrip @WithQuery)
  it "PublishRequest roundtrips" (property $ jsonRoundtrip @PublishRequest)
  it "RemoteExportRequest roundtrips" (property $ jsonRoundtrip @RemoteExportRequest)
  it "FrontendError roundtrips" jsonFrontendErrorRoundtrip
  it "BackendErrorCode roundtrips" (jsonEnumRoundtrip (Dict @_ @BackendErrorCode))
  it "NodeType roundtrips"      (jsonEnumRoundtrip (Dict @_ @NodeType))
  it "NodePublishPolicy roundtrips" (jsonEnumRoundtrip (Dict @_ @NodePublishPolicy))
  it "WithQuery frontend compliance" testWithQueryFrontend
  it "WithQuery frontend compliance (PubMed)" testWithQueryFrontendPubMed
  it "WithQuery frontend compliance (EPO)" testWithQueryFrontendEPO
  describe "Phylo" $ do
    it "PeriodToNode"  (property $ jsonRoundtrip @PeriodToNodeData)
    it "GraphData"     (property $ jsonRoundtrip @GraphData)
    it "GraphDataData" (property $ jsonRoundtrip @GraphDataData)
    it "ObjectData"    (property $ jsonRoundtrip @ObjectData)
    it "PhyloData"     (property $ jsonRoundtrip @PhyloData)
    it "ComputeTimeHistory" (property $ jsonRoundtrip @VizPhylo.ComputeTimeHistory)
    it "Phylo"         (property $ jsonRoundtrip @VizPhylo.Phylo)
    it "LayerData"     (property $ jsonRoundtrip @LayerData)
    it "can parse bpa_phylo_test.json" testParseBpaPhylo
    it "can parse open_science.json" testOpenSciencePhylo

testWithQueryFrontend :: Assertion
testWithQueryFrontend = do
  case  eitherDecode @WithQuery (C8.pack cannedWithQueryPayload) of
   Left err -> fail $ "JSON instance will break frontend!: JSON decoding returned: " <> err
   Right _ -> pure ()

testWithQueryFrontendPubMed :: Assertion
testWithQueryFrontendPubMed = do
  case  eitherDecode @WithQuery (C8.pack cannedWithQueryPayloadPubMed) of
   Left err -> fail $ "JSON instance will break frontend (PubMed)!: JSON decoding returned: " <> err
   Right _ -> pure ()


testWithQueryFrontendEPO :: Assertion
testWithQueryFrontendEPO = do
  case  eitherDecode @WithQuery (C8.pack cannedWithQueryPayloadEPO) of
   Left err -> fail $ "JSON instance will break frontend (EPO)!: JSON decoding returned: " <> err
   Right _ -> pure ()

-- The aim of this type is to catch regressions in the frontend serialisation; this
-- is what the frontend currently expects, and therefore if we were to change the JSON
-- instances, this test would fail, and we will be notified.
cannedWithQueryPayload :: String
cannedWithQueryPayload = [r| {"query":"Haskell","node_id":138,"lang":"EN","flowListWith":{"type":"MyListsFirst"},"datafield": { "External": {"db": "Arxiv"}}} |]

  -- The aim of this type is to catch regressions in the frontend serialisation; this
-- is what the frontend currently expects, and therefore if we were to change the JSON
-- instances, this test would fail, and we will be notified.
cannedWithQueryPayloadPubMed :: String
cannedWithQueryPayloadPubMed = [r| {"query":"Haskell","node_id":138,"lang":"EN","flowListWith":{"type":"MyListsFirst"},"datafield": { "External": {"db": "PubMed", "api_key": "x"}}} |]

  
-- The aim of this type is to catch regressions in the frontend serialisation; this
-- is what the frontend currently expects, and therefore if we were to change the JSON
-- instances, this test would fail, and we will be notified.
cannedWithQueryPayloadEPO :: String
cannedWithQueryPayloadEPO = [r| {"query":"Haskell","node_id":138,"lang":"EN","flowListWith":{"type":"MyListsFirst"},"datafield": { "External": {"db": "EPO", "api_user": "user", "api_token": "token"}}} |]

testParseBpaPhylo :: Assertion
testParseBpaPhylo = do
  pth      <- getDataFileName "test-data/phylo/bpa_phylo_test.json"
  jsonBlob <- B.readFile pth
  case eitherDecodeStrict' @GraphData jsonBlob of
    Left err    -> error err
    Right _     -> pure ()

testOpenSciencePhylo :: Assertion
testOpenSciencePhylo = do
  pth      <- getDataFileName "test-data/phylo/open_science.json"
  jsonBlob <- B.readFile pth
  case eitherDecodeStrict' @PhyloData jsonBlob of
    Left err    -> error err
    Right _     -> pure ()
