-- Verify.hs: OpenPGP (RFC9580) signature verification
-- Copyright © 2012-2026  Clint Adams
-- This software is released under the terms of the Expat license.
-- (See the LICENSE file).

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}

module Data.Conduit.OpenPGP.Verify
  ( VerificationMode(..)
  , VerificationModeW(..)
  , conduitVerify
  , verifyPacketsBatch
  , verifyPacketsWithModeTyped
  , verifyPacketsWithMode
  ) where

import Data.Conduit
import Data.Time.Clock (UTCTime)
import Data.List (foldl')

import Codec.Encryption.OpenPGP.Internal (PktStreamContext(..), emptyPSC)
import Codec.Encryption.OpenPGP.Signatures
  ( VerificationError(..)
  , verifyAgainstKeyring
  , verifySigWith
  )
import Codec.Encryption.OpenPGP.Types
import qualified Data.Conduit.List as CL

data VerificationMode
  = VerificationStreaming
  | VerificationBatch
  deriving (Eq, Show)

data VerificationModeW (mode :: VerificationMode) where
  VerificationStreamingW :: VerificationModeW 'VerificationStreaming
  VerificationBatchW :: VerificationModeW 'VerificationBatch

conduitVerify ::
     Monad m
  => PublicKeyring
  -> Maybe UTCTime
  -> ConduitT Pkt (Either VerificationError Verification) m ()
conduitVerify kr mt =
  CL.concatMapAccum (\pkt state -> pushPacketTyped kr mt pkt state) emptyPSC

verifyPacketsBatch ::
     PublicKeyring
  -> Maybe UTCTime
  -> [Pkt]
  -> [Either VerificationError Verification]
verifyPacketsBatch kr mt =
  verifyPacketsBatchTyped kr mt

verifyPacketsBatchTyped ::
     PublicKeyring
  -> Maybe UTCTime
  -> [Pkt]
  -> [Either VerificationError Verification]
verifyPacketsBatchTyped kr mt =
  reverse . snd . foldl' step (emptyPSC, [])
  where
    step (state, outputs) pkt =
      let (nextState, newOutputs) = pushPacketTyped kr mt pkt state
       in (nextState, reverse newOutputs ++ outputs)

verifyPacketsWithMode ::
     Monad m
  => VerificationMode
  -> PublicKeyring
  -> Maybe UTCTime
  -> ConduitT Pkt (Either VerificationError Verification) m ()
verifyPacketsWithMode VerificationStreaming kr mt =
  verifyPacketsWithModeTyped VerificationStreamingW kr mt
verifyPacketsWithMode VerificationBatch kr mt =
  verifyPacketsWithModeTyped VerificationBatchW kr mt

verifyPacketsWithModeTyped ::
     Monad m
  => VerificationModeW mode
  -> PublicKeyring
  -> Maybe UTCTime
  -> ConduitT Pkt (Either VerificationError Verification) m ()
verifyPacketsWithModeTyped modeW kr mt =
  case modeW of
    VerificationStreamingW -> conduitVerify kr mt
    VerificationBatchW -> CL.consume >>= mapM_ yield . verifyPacketsBatch kr mt

pushPacketTyped ::
     PublicKeyring
  -> Maybe UTCTime
  -> Pkt
  -> PktStreamContext
  -> (PktStreamContext, [Either VerificationError Verification])
pushPacketTyped _ _ ld@LiteralDataPkt {} state = (state {lastLD = ld}, [])
pushPacketTyped _ _ uid@(UserIdPkt _) state = (state {lastUIDorUAt = uid}, [])
pushPacketTyped _ _ uat@(UserAttributePkt _) state = (state {lastUIDorUAt = uat}, [])
pushPacketTyped _ _ pk@(PublicKeyPkt _) state = (state {lastPrimaryKey = pk}, [])
pushPacketTyped _ _ pk@(PublicSubkeyPkt _) state = (state {lastSubkey = pk}, [])
pushPacketTyped _ _ sk@(SecretKeyPkt _ _) state = (state {lastPrimaryKey = sk}, [])
pushPacketTyped _ _ sk@(SecretSubkeyPkt _ _) state = (state {lastSubkey = sk}, [])
pushPacketTyped kr mt sig@(SignaturePkt signature) state =
  case fromSignaturePayloadVerifiableSignatureV signature of
    Just _ ->
      ( state {lastSig = sig}
      , [verifySigWith (verifyAgainstKeyring kr) sig state mt]
      )
    Nothing -> (state, [])
pushPacketTyped _ _ (OtherPacketPkt t _) state | t < 40 =
  (state, [Left (UnknownCriticalPacketInStream t)])
pushPacketTyped _ _ (BrokenPacketPkt err t _) state | t < 40 =
  (state, [Left (BrokenCriticalPacketInStream t err)])
pushPacketTyped _ _ pkt@(OnePassSignaturePkt _) state
  | isOpeningOnePassSignature pkt = (state, [])
pushPacketTyped _ _ _ state = (state, [])

data VerifiableSignatureV where
  VerifiableSignatureV4 :: SignaturePayloadV 'SigPayloadV4 -> VerifiableSignatureV
  VerifiableSignatureV6 :: SignaturePayloadV 'SigPayloadV6 -> VerifiableSignatureV

fromSignaturePayloadVerifiableSignatureV ::
     SignaturePayload -> Maybe VerifiableSignatureV
fromSignaturePayloadVerifiableSignatureV sigPayload =
  case toSomeSignaturePayload sigPayload of
    SomeSignaturePayload (payload@SigPayloadV4Data {}) ->
      Just (VerifiableSignatureV4 payload)
    SomeSignaturePayload (payload@SigPayloadV6Data {}) ->
      Just (VerifiableSignatureV6 payload)
    _ -> Nothing

isOpeningOnePassSignature :: Pkt -> Bool
isOpeningOnePassSignature (OnePassSignaturePkt (OPSPayloadV3Packet (OPSPayloadV3 _ _ _ _ _ False))) =
  True
isOpeningOnePassSignature (OnePassSignaturePkt (OPSPayloadV6Packet (OPSPayloadV6 _ _ _ _ _ False))) =
  True
isOpeningOnePassSignature _ = False
