{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE QuasiQuotes     #-}
{-# LANGUAGE RecordWildCards #-}
module Handler.Types.Apps where

import           Startlude

import           Data.Aeson
import           Data.Aeson.Flatten
import           Data.Singletons

import qualified Lib.External.AppManifest      as Manifest
import           Lib.TyFam.ConditionalData
import           Lib.Types.Core
import           Lib.Types.Emver
import           Lib.Types.Emver.Orphans        ( )
import           Lib.Types.NetAddress
data AppBase = AppBase
    { appBaseId      :: AppId
    , appBaseTitle   :: Text
    , appBaseIconUrl :: Text
    }
    deriving (Eq, Show)
instance ToJSON AppBase where
    toJSON AppBase {..} = object ["id" .= appBaseId, "title" .= appBaseTitle, "iconURL" .= appBaseIconUrl]

data AppAvailablePreview = AppAvailablePreview
    { appAvailablePreviewBase             :: AppBase
    , appAvailablePreviewVersionLatest    :: Version
    , appAvailablePreviewDescriptionShort :: Text
    , appAvailablePreviewInstallInfo      :: Maybe (Version, AppStatus)
    , appAvailablePreviewTimestamp        :: UTCTime
    }
    deriving (Eq, Show)
instance ToJSON AppAvailablePreview where
    toJSON AppAvailablePreview {..} = mergeTo (toJSON appAvailablePreviewBase) $ object
        [ "versionLatest" .= appAvailablePreviewVersionLatest
        , "descriptionShort" .= appAvailablePreviewDescriptionShort
        , "versionInstalled" .= (fst <$> appAvailablePreviewInstallInfo)
        , "status" .= (snd <$> appAvailablePreviewInstallInfo)
        , "latestVersionTimestamp" .= appAvailablePreviewTimestamp
        ]

data AppInstalledPreview = AppInstalledPreview
    { appInstalledPreviewBase             :: AppBase
    , appInstalledPreviewStatus           :: AppStatus
    , appInstalledPreviewVersionInstalled :: Version
    , appInstalledPreviewTorAddress       :: Maybe TorAddress
    , appInstalledPreviewLanAddress       :: Maybe LanAddress
    , appInstalledPreviewTorUi            :: Bool
    , appInstalledPreviewLanUi            :: Bool
    }
    deriving (Eq, Show)
instance ToJSON AppInstalledPreview where
    toJSON AppInstalledPreview {..} = mergeTo (toJSON appInstalledPreviewBase) $ object
        [ "status" .= appInstalledPreviewStatus
        , "versionInstalled" .= appInstalledPreviewVersionInstalled
        , "torAddress" .= (unTorAddress <$> appInstalledPreviewTorAddress)
        , "lanAddress" .= (unLanAddress <$> appInstalledPreviewLanAddress)
        , "torUi" .= appInstalledPreviewTorUi
        , "lanUi" .= appInstalledPreviewLanUi
        ]

data InstallNewAppReq = InstallNewAppReq
    { installNewAppVersion :: Version
    , installNewAppDryRun  :: Bool
    }
    deriving (Eq, Show)
instance FromJSON InstallNewAppReq where
    parseJSON = withObject "Install New App Request" $ \o -> do
        installNewAppVersion <- o .: "version"
        installNewAppDryRun  <- o .:? "dryRun" .!= False
        pure InstallNewAppReq { .. }

data AppAvailableFull = AppAvailableFull
    { appAvailableFullBase                   :: AppBase
    , appAvailableFullLicenseName            :: Maybe Text
    , appAvailableFullLicenseLink            :: Maybe Text
    , appAvailableFullInstallInfo            :: Maybe (Version, AppStatus)
    , appAvailableFullVersionLatest          :: Version
    , appAvailableFullDescriptionShort       :: Text
    , appAvailableFullDescriptionLong        :: Text
    , appAvailableFullReleaseNotes           :: Text
    , appAvailableFullInstallAlert           :: Maybe Text
    , appAvailableFullDependencyRequirements :: [Full AppDependencyRequirement]
    , appAvailableFullVersions               :: NonEmpty Version
    }
    -- deriving Eq
instance ToJSON AppAvailableFull where
    toJSON AppAvailableFull {..} = mergeTo
        (toJSON appAvailableFullBase)
        (object
            [ "licenseName" .= appAvailableFullLicenseName
            , "licenseLink" .= appAvailableFullLicenseLink
            , "versionInstalled" .= fmap fst appAvailableFullInstallInfo
            , "status" .= fmap snd appAvailableFullInstallInfo
            , "versionLatest" .= appAvailableFullVersionLatest
            , "descriptionShort" .= appAvailableFullDescriptionShort
            , "descriptionLong" .= appAvailableFullDescriptionLong
            , "versions" .= appAvailableFullVersions
            , "releaseNotes" .= appAvailableFullReleaseNotes
            , "serviceRequirements" .= appAvailableFullDependencyRequirements
            , "installAlert" .= appAvailableFullInstallAlert
            ]
        )

type AppDependencyRequirement :: (Type ~> Type) -> Type
data AppDependencyRequirement f = AppDependencyRequirement
    { appDependencyRequirementBase           :: AppBase
    , appDependencyRequirementReasonOptional :: Apply f (Maybe Text)
    , appDependencyRequirementDefault        :: Apply f Bool
    , appDependencyRequirementDescription    :: Maybe Text
    , appDependencyRequirementViolation      :: Maybe ApiDependencyViolation
    , appDependencyRequirementVersionSpec    :: VersionRange
    }
instance ToJSON (AppDependencyRequirement Strip) where
    toJSON AppDependencyRequirement {..} = mergeTo (toJSON appDependencyRequirementBase) $ object
        [ "versionSpec" .= appDependencyRequirementVersionSpec
        , "description" .= appDependencyRequirementDescription
        , "violation" .= appDependencyRequirementViolation
        ]
instance ToJSON (AppDependencyRequirement Keep) where
    toJSON r =
        let stripped = r { appDependencyRequirementReasonOptional = (), appDependencyRequirementDefault = () }
        in
            mergeTo
                (toJSON @(AppDependencyRequirement Strip) stripped)
                (object
                    [ "optional" .= appDependencyRequirementReasonOptional r
                    , "default" .= appDependencyRequirementDefault r
                    ]
                )

-- filter non required dependencies in installed show
-- mute violations downstream of version for installing apps
data AppInstalledFull = AppInstalledFull
    { appInstalledFullBase                   :: AppBase
    , appInstalledFullLicenseName            :: Maybe Text
    , appInstalledFullLicenseLink            :: Maybe Text
    , appInstalledFullStatus                 :: AppStatus
    , appInstalledFullVersionInstalled       :: Version
    , appInstalledFullTorAddress             :: Maybe TorAddress
    , appInstalledFullLanAddress             :: Maybe LanAddress
    , appInstalledFullTorUi                  :: Bool
    , appInstalledFullLanUi                  :: Bool
    , appInstalledFullInstructions           :: Maybe Text
    , appInstalledFullLastBackup             :: Maybe UTCTime
    , appInstalledFullConfiguredRequirements :: [Stripped AppDependencyRequirement]
    , appInstalledFullUninstallAlert         :: Maybe Text
    , appInstalledFullRestoreAlert           :: Maybe Text
    , appInstalledFullStartAlert             :: Maybe Text
    , appInstalledFullActions                :: [Manifest.Action]
    }
instance ToJSON AppInstalledFull where
    toJSON AppInstalledFull {..} = object
        [ "instructions" .= appInstalledFullInstructions
        , "lastBackup" .= appInstalledFullLastBackup
        , "configuredRequirements" .= appInstalledFullConfiguredRequirements
        , "torAddress" .= (unTorAddress <$> appInstalledFullTorAddress)
        , "lanAddress" .= (unLanAddress <$> appInstalledFullLanAddress)
        , "torUi" .= appInstalledFullTorUi
        , "lanUi" .= appInstalledFullLanUi
        , "id" .= appBaseId appInstalledFullBase
        , "title" .= appBaseTitle appInstalledFullBase
        , "licenseName" .= appInstalledFullLicenseName
        , "licenseLink" .= appInstalledFullLicenseLink
        , "iconURL" .= appBaseIconUrl appInstalledFullBase
        , "versionInstalled" .= appInstalledFullVersionInstalled
        , "status" .= appInstalledFullStatus
        , "uninstallAlert" .= appInstalledFullUninstallAlert
        , "restoreAlert" .= appInstalledFullRestoreAlert
        , "startAlert" .= appInstalledFullStartAlert
        , "actions" .= appInstalledFullActions
        ]

data AppVersionInfo = AppVersionInfo
    { appVersionInfoVersion                :: Version
    , appVersionInfoReleaseNotes           :: Text
    , appVersionInfoDependencyRequirements :: [Full AppDependencyRequirement]
    , appVersionInfoInstallAlert           :: Maybe Text
    }
instance ToJSON AppVersionInfo where
    toJSON AppVersionInfo {..} = object
        [ "version" .= appVersionInfoVersion
        , "releaseNotes" .= appVersionInfoReleaseNotes
        , "serviceRequirements" .= appVersionInfoDependencyRequirements
        , "installAlert" .= appVersionInfoInstallAlert
        ]

data ApiDependencyViolation
    = Missing
    | IncompatibleVersion
    | IncompatibleConfig [Text] -- rule violations
    | IncompatibleStatus AppStatus

instance ToJSON ApiDependencyViolation where
    toJSON Missing             = object ["name" .= ("missing" :: Text)]
    toJSON IncompatibleVersion = object ["name" .= ("incompatible-version" :: Text)]
    toJSON (IncompatibleConfig ruleViolations) =
        object ["name" .= ("incompatible-config" :: Text), "ruleViolations" .= ruleViolations]
    toJSON (IncompatibleStatus status) = object ["name" .= ("incompatible-status" :: Text), "status" .= status]

data WithBreakages a = WithBreakages [AppBase] a
instance {-# Overlappable #-} ToJSON a => ToJSON (WithBreakages a) where
    toJSON (WithBreakages breakages thing) = mergeTo (toJSON thing) (object ["breakages" .= breakages])
instance ToJSON (WithBreakages ()) where
    toJSON (WithBreakages breakages _) = object ["breakages" .= breakages]

newtype AutoconfigureChangesRes = AutoconfigureChangesRes
    { autoconfigureChangesConfig :: Maybe Value
    }
instance ToJSON AutoconfigureChangesRes where
    toJSON AutoconfigureChangesRes {..} = object ["config" .= autoconfigureChangesConfig]
