{-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}

-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module CargoHold.Options where

import Amazonka (S3AddressingStyle (..))
import qualified CargoHold.CloudFront as CF
import Control.Lens hiding (Level)
import Data.Aeson (FromJSON (..), withText)
import Data.Aeson.TH
import Data.Domain
import Imports
import System.Logger.Extended (Level, LogFormat)
import Util.Options
import Util.SuffixNamer
import Wire.API.Routes.Version

-- | AWS CloudFront settings.
data CloudFrontOpts = CloudFrontOpts
  { -- | Domain
    domain :: CF.Domain,
    -- | Keypair ID
    keyPairId :: CF.KeyPairId,
    -- | Path to private key
    privateKey :: FilePath
  }
  deriving (Show, Generic)

deriveFromJSON defaultOptions ''CloudFrontOpts

newtype OptS3AddressingStyle = OptS3AddressingStyle
  { unwrapS3AddressingStyle :: S3AddressingStyle
  }
  deriving (Show)

instance FromJSON OptS3AddressingStyle where
  parseJSON =
    withText "S3AddressingStyle" $
      fmap OptS3AddressingStyle . \case
        "auto" -> pure S3AddressingStyleAuto
        "path" -> pure S3AddressingStylePath
        "virtual" -> pure S3AddressingStyleVirtual
        other -> fail $ "invalid S3AddressingStyle: " <> show other

data AWSOpts = AWSOpts
  { s3Endpoint :: !AWSEndpoint,
    -- | S3 can either by addressed in path style, i.e.
    -- https://<s3-endpoint>/<bucket-name>/<object>, or vhost style, i.e.
    -- https://<bucket-name>.<s3-endpoint>/<object>. AWS's S3 offering has
    -- deprecated path style addressing for S3 and completely disabled it for
    -- buckets created after 30 Sep 2020:
    -- https://aws.amazon.com/blogs/aws/amazon-s3-path-deprecation-plan-the-rest-of-the-story/
    --
    -- However other object storage providers (specially self-deployed ones like
    -- MinIO) may not support vhost style addressing yet (or ever?). Users of
    -- such buckets should configure this option to "path".
    --
    -- Installations using S3 service provided by AWS, should use "auto", this
    -- option will ensure that vhost style is only used when it is possible to
    -- construct a valid hostname from the bucket name and the bucket name
    -- doesn't contain a '.'. Having a '.' in the bucket name causes TLS
    -- validation to fail, hence it is not used by default.
    --
    -- Using "virtual" as an option is only useful in situations where vhost
    -- style addressing must be used even if it is not possible to construct a
    -- valid hostname from the bucket name or the S3 service provider can ensure
    -- correct certificate is issued for bucket which contain one or more '.'s
    -- in the name.
    --
    -- When this option is unspecified, we default to path style addressing to
    -- ensure smooth transition for older deployments.
    s3AddressingStyle :: !(Maybe OptS3AddressingStyle),
    -- | S3 endpoint for generating download links. Useful if Cargohold is configured to use
    -- an S3 replacement running inside the internal network (in which case internally we
    -- would use one hostname for S3, and when generating an asset link for a client app, we
    -- would use another hostname).
    s3DownloadEndpoint :: !(Maybe AWSEndpoint),
    -- | S3 bucket name
    s3Bucket :: !Text,
    -- | Enable this option for compatibility with specific S3 backends.
    s3Compatibility :: !(Maybe S3Compatibility),
    -- | AWS CloudFront options
    cloudFront :: !(Maybe CloudFrontOpts),
    -- | @Z-Host@ header to s3 download endpoint `Map`
    --
    -- This logic is: If the @Z-Host@ header is provided and found in this map,
    -- the map's values is taken as s3 download endpoint to redirect to;
    -- otherwise a 404 is retuned. This option is only useful
    -- in the context of multi-ingress setups where one backend / deployment is
    -- reachable under several domains.
    multiIngress :: !(Maybe (Map Domain AWSEndpoint))
  }
  deriving (Show, Generic)

data S3Compatibility
  = -- | Scality RING, might also work for Zenko CloudServer
    -- <https://www.scality.com/products/ring/>
    S3CompatibilityScalityRing
  deriving (Eq, Show)

instance FromJSON S3Compatibility where
  parseJSON = withText "S3Compatibility" $ \case
    "scality-ring" -> pure S3CompatibilityScalityRing
    other -> fail $ "invalid S3Compatibility: " <> show other

deriveFromJSON defaultOptions ''AWSOpts

makeLensesFor
  [ ("multiIngress", "multiIngressLens"),
    ("s3DownloadEndpoint", "s3DownloadEndpointLens"),
    ("cloudFront", "cloudFrontLens")
  ]
  ''AWSOpts

data Settings = Settings
  { -- | Maximum allowed size for uploads, in bytes (applies to team users)
    maxTotalBytes :: !Int,
    -- | Maximum allowed size for uploads, in bytes (more restrictive setting, applies to non-team users)
    maxTotalBytesStrict :: !Int,
    -- | TTL for download links, in seconds
    downloadLinkTTL :: !Word,
    -- | Enable audit logging for asset uploads/downloads.
    -- When enabled, the backend will collect and log asset metadata
    -- as part of the asset audit log feature.
    assetAuditLogEnabled :: !Bool,
    -- | FederationDomain is required, even when not wanting to federate with other backends
    -- (in that case the 'allowedDomains' can be set to empty in Federator)
    -- Federation domain is used to qualify local IDs and handles,
    -- e.g. 0c4d8944-70fa-480e-a8b7-9d929862d18c@wire.com and somehandle@wire.com.
    -- It should also match the SRV DNS records under which other wire-server installations can find this backend:
    --    _wire-server-federator._tcp.<federationDomain>
    -- Once set, DO NOT change it: if you do, existing users may have a broken experience and/or stop working
    -- Remember to keep it the same in all services.
    -- This is referred to as the 'backend domain' in the public documentation; See
    -- https://docs.wire.com/how-to/install/configure-federation.html#choose-a-backend-domain-name
    federationDomain :: !Domain,
    disabledAPIVersions :: !(Set VersionExp)
  }
  deriving (Show, Generic)

deriveFromJSON defaultOptions ''Settings

-- | Options consist of information the server needs to operate, and 'Settings'
-- modify the behavior.
data Opts = Opts
  { -- | Hostname and port to bind to
    cargohold :: !Endpoint,
    aws :: !AWSOpts,
    settings :: !Settings,
    -- | Federator endpoint
    federator :: Maybe Endpoint,
    -- | Brig endpoint
    brig :: !Endpoint,
    -- Logging

    -- | Log level (Debug, Info, etc)
    logLevel :: !Level,
    -- | Use netstrings encoding:
    --   <http://cr.yp.to/proto/netstrings.txt>
    logNetStrings :: !(Maybe (Last Bool)),
    logFormat :: !(Maybe (Last LogFormat)) --- ^ Log format
  }
  deriving (Show, Generic)

deriveFromJSON defaultOptions ''Opts

makeLensesWith (lensRules & lensField .~ suffixNamer) ''Opts
