{-# LANGUAGE DisambiguateRecordFields #-}

-- | Integration with the Casa server.

module Pantry.Casa where

import           Database.Persist.Sql ( SqlBackend )
import qualified Casa.Client as Casa
import qualified Casa.Types as Casa
import           Conduit
                   ( ConduitT, ResourceT, (.|), await, mapMC, runConduitRes )
import qualified Data.HashMap.Strict as HM
import qualified Pantry.SHA256 as SHA256
import           Pantry.Storage ( storeBlob, withStorage )
import           Pantry.Types as P
                   ( BlobKey (..), FileSize (..), HasPantryConfig (..)
                   , PantryConfig (..), PantryException (..), Tree, TreeKey (..)
                   , parseTreeM
                   )
import           RIO
import qualified RIO.ByteString as B

-- | Lookup a tree.
casaLookupTree ::
     (HasPantryConfig env, HasLogFunc env)
  => TreeKey
  -> RIO env (Maybe (TreeKey, P.Tree))
casaLookupTree (P.TreeKey key) =
  handleAny (const (pure Nothing))
    (withStorage
      (runConduitRes (casaBlobSource (Identity key) .| mapMC parseTreeM .| await)))

-- | Lookup a single blob. If possible, prefer 'casaBlobSource', and query a
-- group of keys at once, rather than one at a time. This will have better
-- network performance.
casaLookupKey ::
     (HasPantryConfig env, HasLogFunc env)
  => BlobKey
  -> RIO env (Maybe ByteString)
casaLookupKey key =
  handleAny (const (pure Nothing))
  (fmap
    (fmap snd)
    (withStorage (runConduitRes (casaBlobSource (Identity key) .| await))))

-- | A source of blobs given a set of keys. All blobs are automatically stored
-- in the local pantry database.
casaBlobSource ::
     (Foldable f, HasPantryConfig env, HasLogFunc env)
  => f BlobKey
  -> ConduitT
       i
       (BlobKey, ByteString)
       (ResourceT (ReaderT SqlBackend (RIO env)))
       ()
casaBlobSource keys = source .| convert .| store
 where
  source = do
    mCasaConfig <- lift $ lift $ lift $ view $ pantryConfigL . to pcCasaConfig
    case mCasaConfig of
      Just (pullUrl, maxPerRequest) -> do
        Casa.blobsSource
          ( Casa.SourceConfig
              { sourceConfigUrl = pullUrl
              , sourceConfigBlobs = toBlobKeyMap keys
              , sourceConfigMaxBlobsPerRequest = maxPerRequest
              }
          )
      Nothing -> throwM NoCasaConfig
   where
    toBlobKeyMap :: Foldable f => f BlobKey -> HashMap Casa.BlobKey Int
    toBlobKeyMap = HM.fromList . map unpackBlobKey . toList
    unpackBlobKey (P.BlobKey sha256 (FileSize fileSize)) =
      (Casa.BlobKey (SHA256.toRaw sha256), fromIntegral fileSize)
  convert = mapMC toBlobKeyAndBlob
   where
    toBlobKeyAndBlob ::
         MonadThrow m
      => (Casa.BlobKey, ByteString)
      -> m (BlobKey, ByteString)
    toBlobKeyAndBlob (Casa.BlobKey keyBytes, blob) = do
      sha256 <-
        case SHA256.fromRaw keyBytes of
          Left e -> throwM e
          Right sha -> pure sha
      pure (BlobKey sha256 (FileSize (fromIntegral (B.length blob))), blob)
  store = mapMC insertBlob
   where
    insertBlob original@(_key, binary) = do
      _ <- lift (storeBlob binary)
      pure original
