{-# LANGUAGE UndecidableInstances #-}

module Model.Types.WorkflowSpec where

import TestImport hiding (NonEmpty)
import TestInstances ()

import Data.Scientific (Scientific)
import Data.List.NonEmpty (NonEmpty)

import Utils.I18nSpec ()
import Model.Types.FileSpec ()

import qualified Data.Map as Map
import qualified Data.Set as Set

import qualified Data.Aeson as Aeson

import Utils.Lens

import Utils.I18n

import qualified Data.CaseInsensitive as CI

import Data.Time.LocalTime (TimeOfDay)


instance Arbitrary WorkflowPayloadLabel where
  arbitrary = WorkflowPayloadLabel . CI.mk . pack <$> (fmap getPrintableString arbitrary `suchThat` (not . null))
  shrink = genericShrink
instance CoArbitrary WorkflowPayloadLabel
instance Function WorkflowPayloadLabel

instance (Arbitrary fileid, Arbitrary userid, Typeable fileid, Typeable userid, Ord fileid, Arbitrary (FileField fileid)) => Arbitrary (WorkflowPayloadSpec fileid userid) where
  arbitrary = oneof
    [ WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid Text)
    , WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid Scientific)
    , WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid Bool)
    , WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid Day)
    , WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid WorkflowPayloadTimeCapture)
    , WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid (Set fileid))
    , WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid userid)
    , WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid WorkflowPayloadFieldReference)
    , WorkflowPayloadSpec <$> arbitrary @(WorkflowPayloadField fileid userid (NonEmpty (WorkflowFieldPayloadW fileid userid)))
    ]

instance Arbitrary WorkflowPayloadTextPreset where
  arbitrary = genericArbitrary
  shrink = genericShrink
instance Arbitrary (WorkflowPayloadField fileid userid Text) where
  arbitrary = WorkflowPayloadFieldText
    <$> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
instance Arbitrary (WorkflowPayloadField fileid userid Scientific) where
  arbitrary = WorkflowPayloadFieldNumber
    <$> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
instance Arbitrary (WorkflowPayloadField fileid userid Bool) where
  arbitrary = WorkflowPayloadFieldBool
    <$> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
instance Arbitrary (WorkflowPayloadField fileid userid Day) where
  arbitrary = WorkflowPayloadFieldDay
    <$> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
instance (Arbitrary (FileField fileid)) => Arbitrary (WorkflowPayloadField fileid userid (Set fileid)) where
  arbitrary = WorkflowPayloadFieldFile
    <$> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
instance Arbitrary userid => Arbitrary (WorkflowPayloadField fileid userid userid) where
  arbitrary = oneof
    [ WorkflowPayloadFieldUser
        <$> scale (`div` 2) arbitrary
        <*> scale (`div` 2) arbitrary
        <*> scale (`div` 2) arbitrary
        <*> scale (`div` 2) arbitrary
    , pure WorkflowPayloadFieldCaptureUser
    ]
instance Arbitrary (WorkflowPayloadField fileid userid WorkflowPayloadFieldReference) where
  arbitrary = WorkflowPayloadFieldReference
    <$> scale (`div` 2) arbitrary
instance (Arbitrary fileid, Arbitrary userid, Typeable fileid, Typeable userid, Ord fileid, Arbitrary (FileField fileid)) => Arbitrary (WorkflowPayloadField fileid userid (NonEmpty (WorkflowFieldPayloadW fileid userid))) where
  arbitrary = WorkflowPayloadFieldMultiple
    <$> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
instance Arbitrary (WorkflowPayloadField fileid userid WorkflowPayloadTimeCapture) where
  arbitrary = WorkflowPayloadFieldCaptureDateTime
    <$> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary
    <*> scale (`div` 2) arbitrary

instance Arbitrary WorkflowPayloadTimeCapturePrecision where
  arbitrary = genericArbitrary
  shrink = genericShrink
 
instance Arbitrary WorkflowGraphEdgeFormOrder where
  arbitrary = genericArbitrary
  shrink = genericShrink

instance (Arbitrary fileid, Arbitrary userid, Typeable fileid, Typeable userid, Ord fileid, Ord userid, Ord (FileField fileid), Arbitrary (FileField fileid)) => Arbitrary (WorkflowGraphEdgeForm fileid userid) where
  arbitrary = WorkflowGraphEdgeForm . Map.fromList . mapMaybe (\(l, s) -> (l, ) <$> fromNullable (Set.fromList . mapMaybe fromNullable $ map Map.fromList s)) <$> listOf ((,) <$> scale (`div` 2) arbitrary <*> scale (`div` 2) (listOf . scale (`div` 2) . listOf $ (,) <$> scale (`div` 2) arbitrary <*> scale (`div` 2) arbitrary))
  shrink = genericShrink

instance (Arbitrary fileid, Arbitrary userid, Ord fileid, Typeable userid, Typeable fileid) => Arbitrary (WorkflowFieldPayloadW fileid userid) where
  arbitrary = oneof
    [ WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid Text)
    , WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid Scientific)
    , WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid Bool)
    , WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid Day)
    , WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid TimeOfDay)
    , WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid UTCTime)
    , WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid fileid)
    , WorkflowFieldPayloadW <$> arbitrary @(WorkflowFieldPayload fileid userid userid)
    ]

instance (Arbitrary payload, IsWorkflowFieldPayload' fileid userid payload) => Arbitrary (WorkflowFieldPayload fileid userid payload) where
  arbitrary = review _WorkflowFieldPayload <$> arbitrary

instance (Arbitrary termid, Arbitrary courseid) => Arbitrary (WorkflowScope termid SchoolShorthand courseid) where
  arbitrary = oneof
    [ pure WSGlobal
    , WSTerm <$> arbitrary
    , WSSchool <$> arbitrarySchoolShorthand
    , WSTermSchool <$> arbitrary <*> arbitrarySchoolShorthand
    , WSCourse <$> arbitrary
    ]
    where arbitrarySchoolShorthand = CI.mk . pack <$> (fmap getPrintableString arbitrary `suchThat` (not . null))
instance (Arbitrary termid, Arbitrary courseid) => Arbitrary (WorkflowScope termid SchoolId courseid) where
  arbitrary = over _wisSchool SchoolKey <$> arbitrary
instance (CoArbitrary termid, CoArbitrary schoolid, CoArbitrary courseid) => CoArbitrary (WorkflowScope termid schoolid courseid)
instance (Function termid, Function schoolid, Function courseid) => Function (WorkflowScope termid schoolid courseid)

instance Arbitrary WorkflowScope' where
  arbitrary = genericArbitrary
  shrink = genericShrink


spec :: Spec
spec = do
  describe "WorkflowPayloadSpec" $ do
    it "json-roundtrips some examples" $ do
      let roundtrip val = Aeson.eitherDecode (Aeson.encode val) `shouldBe` Right val

      -- Generated tests that failed previously
      roundtrip $ WorkflowPayloadSpec @FileReference @SqlBackendKey (WorkflowPayloadFieldNumber {wpfnLabel = I18n {i18nFallback = "\368366\901557\714616k", i18nFallbackLang = Nothing, i18nTranslations = Map.fromList [("",""),("Jak8","\125553E")]}, wpfnPlaceholder = Just (I18n {i18nFallback = "\303706\543092", i18nFallbackLang = Nothing, i18nTranslations = Map.fromList []}), wpfnTooltip = Nothing, wpfnDefault = Nothing, wpfnMin = Nothing, wpfnMax = Just 0.1, wpfnStep = Nothing, wpfnOptional = False})
  
  describe "WorkflowGraphEdgeForm" $ do
    it "json-decodes some examples" $ do
      let decodes bs = Aeson.decode bs `shouldSatisfy` is (_Just @(WorkflowGraphEdgeForm FileReference SqlBackendKey))
      
      decodes "{\"\": [{\"tag\": \"capture-user\"}]}"
      decodes "{\"\": [{\"_\": {\"tag\": \"capture-user\"}}]}"
      decodes "{\"\": [{\"1\": {\"tag\": \"capture-user\"}}]}"
      decodes "{\"\": [{\"-1\": {\"tag\": \"capture-user\"}}]}"
      decodes "{\"\": [{\"tag\": \"capture-user\"}, {\"_\": {\"tag\": \"capture-user\"}}]}"
      decodes "{\"\": [{\"tag\": \"capture-user\"}, {\"1\": {\"tag\": \"capture-user\"}}]}"
      decodes "{\"\": [{\"_\": {\"tag\": \"capture-user\"}}, {\"1\": {\"tag\": \"capture-user\"}}]}"
      decodes "{\"\": [{\"0.1\":{\"tag\": \"capture-user\"}}, {\"-0.1\":{\"tag\": \"capture-user\"}}]}"

  parallel $ do
    lawsCheckHspec (Proxy @WorkflowGraphEdgeFormOrder)
      [ eqLaws, ordLaws, semigroupLaws, monoidLaws, semigroupMonoidLaws, commutativeSemigroupLaws, idempotentSemigroupLaws, showLaws, showReadLaws, jsonLaws, jsonKeyLaws ]
    lawsCheckHspec (Proxy @(WorkflowPayloadSpec FileReference SqlBackendKey))
      [ eqLaws, ordLaws, jsonLaws ]
    modifyMaxSize (`div` 4) $ lawsCheckHspec (Proxy @(WorkflowGraphEdgeForm FileReference SqlBackendKey))
      [ eqLaws, ordLaws, jsonLaws ]
    lawsCheckHspec (Proxy @WorkflowScope')
      [ eqLaws, ordLaws, boundedEnumLaws, showLaws, showReadLaws, universeLaws, finiteLaws, pathPieceLaws, jsonLaws, persistFieldLaws, binaryLaws ]
    lawsCheckHspec (Proxy @(WorkflowFieldPayloadW FileReference SqlBackendKey))
      [ eqLaws, ordLaws, showLaws, jsonLaws, binaryLaws ]
