{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}

module Hercules.Agent.Cachix.Init where

#if MIN_VERSION_cachix(0,7,0)
import Cachix.Client.Version (cachixVersion)
#else
import Cachix.Client.Env (cachixVersion)
#endif

#if MIN_VERSION_cachix(1,4,0) && ! MIN_VERSION_cachix(1,5,0)
import Cachix.Client.Store qualified as Cachix
import Control.Monad.IO.Unlift (UnliftIO (UnliftIO), askUnliftIO)
import Hercules.CNix.Settings qualified as CNix

#else
import Hercules.CNix.Store (openStore)

#endif
import Cachix.Client.Push qualified as Cachix.Push
import Cachix.Client.Secrets qualified as Cachix.Secrets
import Cachix.Client.URI (defaultCachixBaseUrl)
import Control.Monad.IO.Unlift (MonadUnliftIO)
import Data.Map qualified as M
import Hercules.Agent.Cachix.Env (PushCache (PushCache))
import Hercules.Agent.Cachix.Env qualified as Env
import Hercules.Agent.Config qualified as Config
import Hercules.Error
import Hercules.Formats.CachixCache qualified as CachixCache
import Katip (KatipContext)
import Katip qualified as K
import Network.HTTP.Client (ManagerSettings (managerModifyRequest, managerResponseTimeout), responseTimeoutNone)
import Network.HTTP.Client.TLS (newTlsManagerWith, tlsManagerSettings)
import Network.HTTP.Simple (setRequestHeader)
import Protolude
import Servant.Auth.Client qualified
import Servant.Client (mkClientEnv)

-- TODO use from lib after cachix >0.3.5 + https://github.com/cachix/cachix/pull/274
customManagerSettings :: ManagerSettings
customManagerSettings =
  tlsManagerSettings
    { managerResponseTimeout = responseTimeoutNone,
      -- managerModifyRequest :: Request -> IO Request
      managerModifyRequest = return . setRequestHeader "User-Agent" [encodeUtf8 cachixVersion]
    }

withEnv :: (MonadUnliftIO m, KatipContext m) => Config.FinalConfig -> Map Text CachixCache.CachixCache -> (Env.Env -> m a) -> m a
withEnv _config cks continue = do
  -- FIXME: sl doesn't work??
  K.katipAddContext (K.sl "caches" (M.keys cks)) $
    K.logLocM K.DebugS ("Cachix init " <> K.logStr (show (M.keys cks) :: Text))
  pcs <- liftIO $ toPushCaches cks
  httpManager <- newTlsManagerWith customManagerSettings

#if MIN_VERSION_cachix(1,4,0) && ! MIN_VERSION_cachix(1,5,0)
  UnliftIO unlift <- askUnliftIO
  useWAL <- liftIO CNix.getUseSQLiteWAL

  liftIO do
    Cachix.withLocalStore
      Cachix.LocalStoreOptions
        { storePrefix = "/nix",
          useSqliteWAL = useWAL
        }
      \store -> unlift do
        continue
          Env.Env
            { pushCaches = pcs,
              netrcLines = toNetrcLines cks,
              cacheKeys = cks,
              store = store,
              clientEnv = mkClientEnv httpManager defaultCachixBaseUrl
            }
#else
  env <- do
    store <- liftIO openStore
    pure
      Env.Env
        { pushCaches = pcs,
          netrcLines = toNetrcLines cks,
          cacheKeys = cks,
          store = store,
          clientEnv = mkClientEnv httpManager defaultCachixBaseUrl
        }
  continue env
#endif

toNetrcLines :: Map Text CachixCache.CachixCache -> [Text]
toNetrcLines = concatMap toNetrcLine . M.toList
  where
    toNetrcLine (name, keys) = do
      pt <- toList $ CachixCache.authToken keys
      pure $ "machine " <> name <> ".cachix.org" <> " login authtoken password " <> pt

toPushCaches :: Map Text CachixCache.CachixCache -> IO (Map Text PushCache)
toPushCaches = sequenceA . M.mapMaybeWithKey toPushCaches'
  where
    toPushCaches' name keys =
      let t = fromMaybe "" (CachixCache.authToken keys)
       in do
            sk <- head $ CachixCache.signingKeys keys
            Just $
              escalateAs FatalError $ do
                k' <- Cachix.Secrets.parseSigningKeyLenient sk
                pure
                  PushCache
                    { pushCacheName = name,
                      pushCacheSecret =
                        Cachix.Push.PushSigningKey
                          (Servant.Auth.Client.Token $ encodeUtf8 t)
                          k'
                    }
            <|> do
              token <- head $ CachixCache.authToken keys
              Just $
                pure
                  PushCache
                    { pushCacheName = name,
                      pushCacheSecret =
                        Cachix.Push.PushToken (Servant.Auth.Client.Token $ encodeUtf8 token)
                    }
