{-# LANGUAGE OverloadedStrings #-}

module CoreTests.CryptoFileTests (cryptoFileTests) where

import AgentTests.FunctionalAPITests (runRight_)
import Control.Concurrent.STM
import Control.Monad.Except
import Control.Monad.IO.Class
import Crypto.Random (ChaChaDRG)
import qualified Data.ByteString.Lazy as LB
import GHC.IO.IOMode (IOMode (..))
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFile (..), FTCryptoError (..))
import qualified Simplex.Messaging.Crypto.File as CF
import System.Directory (getFileSize)
import Test.Hspec hiding (fit, it)
import Util

cryptoFileTests :: Spec
cryptoFileTests = do
  it "should write/read file" testWriteReadFile
  it "should put/get file" testPutGetFile
  it "should write/get file" testWriteGetFile
  it "should put/read file" testPutReadFile
  it "should fail reading empty or small file" testSmallFile

testFilePath :: FilePath
testFilePath = "tests/tmp/testcryptofile"

testWriteReadFile :: IO ()
testWriteReadFile = do
  g <- C.newRandom
  s <- atomically $ LB.fromStrict <$> C.randomBytes 100000 g
  file <- atomically $ mkCryptoFile g
  runRight_ $ do
    CF.writeFile file s
    liftIO $ CF.getFileContentsSize file `shouldReturn` 100000
    liftIO $ getFileSize testFilePath `shouldReturn` 100000 + fromIntegral C.authTagSize
    s' <- CF.readFile file
    liftIO $ s `shouldBe` s'

testPutGetFile :: IO ()
testPutGetFile = do
  g <- C.newRandom
  s <- atomically $ LB.fromStrict <$> C.randomBytes 50000 g
  s' <- atomically $ LB.fromStrict <$> C.randomBytes 50000 g
  file <- atomically $ mkCryptoFile g
  runRight_ $ do
    CF.withFile file WriteMode $ \h -> liftIO $ do
      CF.hPut h s
      CF.hPut h s'
      CF.hPutTag h
    liftIO $ CF.getFileContentsSize file `shouldReturn` 100000
    liftIO $ getFileSize testFilePath `shouldReturn` 100000 + fromIntegral C.authTagSize
    CF.withFile file ReadMode $ \h -> do
      s1 <- liftIO $ CF.hGet h 30000
      s2 <- liftIO $ CF.hGet h 40000
      s3 <- liftIO $ CF.hGet h 30000
      CF.hGetTag h
      liftIO $ (s <> s') `shouldBe` LB.fromStrict (s1 <> s2 <> s3)

testWriteGetFile :: IO ()
testWriteGetFile = do
  g <- C.newRandom
  s <- atomically $ LB.fromStrict <$> C.randomBytes 100000 g
  file <- atomically $ mkCryptoFile g
  runRight_ $ do
    CF.writeFile file s
    CF.withFile file ReadMode $ \h -> do
      s' <- liftIO $ CF.hGet h 50000
      s'' <- liftIO $ CF.hGet h 50000
      CF.hGetTag h
      liftIO $ runExceptT (CF.hGetTag h) `shouldReturn` Left FTCEInvalidAuthTag
      liftIO $ s `shouldBe` LB.fromStrict (s' <> s'')

testPutReadFile :: IO ()
testPutReadFile = do
  g <- C.newRandom
  s <- atomically $ LB.fromStrict <$> C.randomBytes 50000 g
  s' <- atomically $ LB.fromStrict <$> C.randomBytes 50000 g
  file <- atomically $ mkCryptoFile g
  runRight_ $ do
    CF.withFile file WriteMode $ \h -> liftIO $ do
      CF.hPut h s
      CF.hPut h s'
  runExceptT (CF.readFile file) `shouldReturn` Left FTCEInvalidAuthTag
  runRight_ $ do
    CF.withFile file WriteMode $ \h -> liftIO $ do
      CF.hPut h s
      CF.hPut h s'
      CF.hPutTag h
    s'' <- CF.readFile file
    liftIO $ (s <> s') `shouldBe` s''

testSmallFile :: IO ()
testSmallFile = do
  g <- C.newRandom
  file <- atomically $ mkCryptoFile g
  LB.writeFile testFilePath ""
  runExceptT (CF.readFile file) `shouldReturn` Left FTCEInvalidFileSize
  LB.writeFile testFilePath "123"
  runExceptT (CF.readFile file) `shouldReturn` Left FTCEInvalidFileSize

mkCryptoFile :: TVar ChaChaDRG -> STM CryptoFile
mkCryptoFile g = CryptoFile testFilePath . Just <$> CF.randomArgs g
