content: {-# LANGUAGE ExistentialQuantification #-}
  
  module Sample.Stake.Create (
    StakeDatumWrapper (..),
    Parameters (..),
    create,
    mkTestCase,
    ownerIsPubKeyTotallyValid,
    ownerIsScriptTotallyValid,
    createMoreThanOneStake,
    spendStake,
    unexpectedStakedAmount,
    noStakeDatum,
    malformedStakeDatum,
    notAuthorizedByOwner,
    setDelegatee,
    alreadyHasLocks,
  ) where
  
  import Agora.Governor (Governor (gtClassRef))
  import Agora.Proposal (ProposalId (ProposalId))
  import Agora.SafeMoney (GTTag)
  import Agora.Stake (ProposalAction (Created), ProposalLock (ProposalLock), StakeDatum (..))
  import Data.Semigroup (stimesMonoid)
  import Data.Tagged (Tagged)
  import Plutarch.Context (
    input,
    mint,
    normalizeValue,
    output,
    pubKey,
    script,
    signedWith,
    withDatum,
    withValue,
   )
  import Plutarch.Extra.AssetClass (assetClassValue)
  import Plutarch.Extra.ScriptContext (scriptHashToTokenName)
  import Plutarch.Lift (PUnsafeLiftDecl (PLifted))
  import PlutusLedgerApi.V1.Value qualified as Value
  import PlutusLedgerApi.V2 (
    Credential (
      PubKeyCredential,
      ScriptCredential
    ),
   )
  import Sample.Shared (
    governor,
    signer,
    signer2,
    stakePolicy,
    stakeScriptHash,
    stakeSymbol,
   )
  import Test.Specification (SpecificationTree, testPolicy)
  import Test.Util (CombinableBuilder, mkMinting, validatorHashes)
  
  data StakeDatumWrapper
    = forall (b :: Type) (p :: S -> Type).
      (PUnsafeLiftDecl p, PLifted p ~ b, PIsData p) =>
      StakeDatumWrapper b
  
  data Parameters = Parameters
    { numSSTMinted :: Integer
    , invalidSSTName :: Bool
    , stakeAtInput :: Bool
    , numGTsInValue :: Tagged GTTag Integer
    , stakeDatum :: Maybe StakeDatumWrapper
    , authorizedBy :: Maybe Credential
    }
  
  create :: forall b. CombinableBuilder b => Parameters -> b
  create ps@Parameters {stakeDatum} =
    let perStakeGTs =
          assetClassValue
            governor.gtClassRef
            ps.numGTsInValue
  
        gtValue =
          stimesMonoid ps.numSSTMinted perStakeGTs
  
        gtInputBuilder =
          mconcat
            [ input $
                mconcat
                  [ pubKey signer
                  , withValue $ normalizeValue gtValue
                  ]
            ]
  
        ---
  
        sstName =
          if ps.invalidSSTName
            then "114514"
            else scriptHashToTokenName stakeScriptHash
  
        sst = Value.singleton stakeSymbol sstName 1
  
        withStakeDatum =
          maybe
            mempty
            (\(StakeDatumWrapper stakeDatum) -> withDatum stakeDatum)
            stakeDatum
  
        stakeBuilder =
          mconcat
            [ script stakeScriptHash
            , withValue $ normalizeValue $ sst <> perStakeGTs
            , withStakeDatum
            ]
  
        stakeInputBuilder =
          if ps.stakeAtInput
            then input stakeBuilder
            else mempty
  
        stakeOutputBuilder =
          stimesMonoid ps.numSSTMinted $
            output stakeBuilder
  
        ---
  
        withAuthorization =
          maybe
            mempty
            ( \case
                PubKeyCredential pkh -> signedWith pkh
                ScriptCredential val -> input $ script val
            )
            ps.authorizedBy
  
        ---
  
        mintSSTs = mint $ stimesMonoid ps.numSSTMinted sst
     in mconcat
          [ gtInputBuilder
          , stakeInputBuilder
          , stakeOutputBuilder
          , withAuthorization
          , mintSSTs
          ]
  
  mkTestCase :: String -> Parameters -> Bool -> SpecificationTree
  mkTestCase name ps val = stake
    where
      mint = mkMinting create ps
  
      stake =
        testPolicy
          val
          name
          stakePolicy
          ()
          (mint stakeSymbol)
  
  mkTotallyValid :: Integer -> Credential -> Parameters
  mkTotallyValid gts owner =
    Parameters
      { numSSTMinted = 1
      , invalidSSTName = False
      , numGTsInValue = fromInteger gts
      , stakeAtInput = False
      , stakeDatum =
          Just $
            StakeDatumWrapper $
              StakeDatum
                { stakedAmount = fromInteger gts
                , owner = owner
                , delegatedTo = Nothing
                , lockedBy = []
                }
      , authorizedBy = Just owner
      }
  
  ownerIsPubKeyTotallyValid :: Parameters
  ownerIsPubKeyTotallyValid = mkTotallyValid 114514 (PubKeyCredential signer)
  
  ownerIsScriptTotallyValid :: Parameters
  ownerIsScriptTotallyValid =
    mkTotallyValid
      114514
      ( ScriptCredential $
          head validatorHashes
      )
  
  createMoreThanOneStake :: Parameters
  createMoreThanOneStake =
    ownerIsPubKeyTotallyValid
      { numSSTMinted = 5
      }
  
  spendStake :: Parameters
  spendStake =
    ownerIsPubKeyTotallyValid
      { stakeAtInput = True
      }
  
  unexpectedStakedAmount :: Parameters
  unexpectedStakedAmount =
    ownerIsPubKeyTotallyValid
      { numGTsInValue = 114514
      , stakeDatum =
          Just $
            StakeDatumWrapper $
              StakeDatum
                { stakedAmount = 1919810
                , owner = PubKeyCredential signer
                , delegatedTo = Nothing
                , lockedBy = []
                }
      }
  
  noStakeDatum :: Parameters
  noStakeDatum =
    ownerIsPubKeyTotallyValid
      { stakeDatum = Nothing
      }
  
  malformedStakeDatum :: Parameters
  malformedStakeDatum =
    ownerIsPubKeyTotallyValid
      { stakeDatum = Just $ StakeDatumWrapper (1 :: Integer)
      }
  
  notAuthorizedByOwner :: Parameters
  notAuthorizedByOwner =
    ownerIsPubKeyTotallyValid
      { authorizedBy = Nothing
      }
  
  setDelegatee :: Parameters
  setDelegatee =
    ownerIsPubKeyTotallyValid
      { numGTsInValue = 114514
      , stakeDatum =
          Just $
            StakeDatumWrapper $
              StakeDatum
                { stakedAmount = 114514
                , owner = PubKeyCredential signer
                , delegatedTo = Just $ PubKeyCredential signer2
                , lockedBy = []
                }
      }
  
  alreadyHasLocks :: Parameters
  alreadyHasLocks =
    ownerIsPubKeyTotallyValid
      { numGTsInValue = 114514
      , stakeDatum =
          Just $
            StakeDatumWrapper $
              StakeDatum
                { stakedAmount = 114514
                , owner = PubKeyCredential signer
                , delegatedTo = Nothing
                , lockedBy = [ProposalLock (ProposalId 0) Created]
                }
      }
  