{-# LANGUAGE LambdaCase         #-}
{-# LANGUAGE OverloadedStrings  #-}

module Main where

import           Control.Applicative
import           Control.Lens
import           Data.Maybe                     (fromJust)
import           Data.Semigroup
import           Data.Text                      (Text)
import qualified Data.Text as T
import           Options.Applicative            (Parser, ReadM)
import qualified Options.Applicative as O
import qualified Options.Applicative.Types as O
import           Options.Generic

import           Dampf
import           Dampf.Postgres
import           Dampf.Types
import           Dampf.Monitor (runMonitor)
import           Dampf.Test (test)
import           Dampf.Browse (browse, Backend(..))
import           Dampf.Docker
import           Dampf.Provision


-- Running Dampfs


main :: IO ()
main = O.execParser parser >>= run
  where
    parser = O.info
        (O.helper <*> parseOptions)
        (O.header "dampf - Declarative DevOps for Busy Developers")


run :: Options -> IO ()
run (Options af cf p (Provision pt)) =
    goProvision pt
run (Options af cf p cmd) = do
    a   <- loadAppFile af
    c   <- loadConfigFile cf >>= \case
        Left ps   -> return (ps ^. profiles . at p . to fromJust)
        Right cfg -> return cfg

    runDampfT a c $ case cmd of
        Backup db           -> backupDB db
        Restore fp mdb      -> restoreDB fp mdb
        Build               -> goBuild
        Deploy              -> goDeploy
        Dump                -> dump
        Run img mcmd        -> runDocker img mcmd
        NewMigration db mig -> newMigrationCmd db mig
        RunMigrations db    -> runMigrations db
        SetupDatabase       -> setupDB
        Monitor tests       -> runMonitor tests
        Test tests          -> test tests
        Provision pt        -> goProvision pt
        Env cmds            -> envCmd cmds
        Browse b            -> browse b
        LsEnv               -> lsEnvCmd

-- Command Line Options

data Options = Options
    { appFile   :: Maybe FilePath
    , cfgFile   :: Maybe FilePath
    , profile   :: Text
    , command   :: Command
    } deriving (Show)


data Command
    = Backup (Maybe Text)
    | Restore FilePath (Maybe Text)
    | Build
    | Deploy
    | Run Text (Maybe Text)
    | Dump
    | NewMigration Text FilePath
    | RunMigrations (Maybe Text)
    | Env [Text]
    | LsEnv
    | SetupDatabase
    | Monitor [Text]
    | Test [Text]
    | Provision ProvisionType
    | Browse Backend
    deriving (Show)


parseOptions :: Parser Options
parseOptions = Options
    <$> optional (O.strOption $
           O.short 'a'
        <> O.long "appFile"
        <> O.metavar "FILE"
        <> O.help "The file which specifies the application")

    <*> optional (O.strOption $
           O.short 'c'
        <> O.long "configFile"
        <> O.metavar "FILE"
        <> O.help "The file which specifies the configuration")

    <*> O.option readerText
           (O.short 'p'
        <> O.long "profile"
        <> O.metavar "PROFILE"
        <> O.help "The configuration profile (default: default)"
        <> O.value "default")

    <*> parseCommand


parseCommand :: Parser Command
parseCommand = O.subparser $
       O.command "backup"
           (O.info
               (O.helper <*> parseBackup)
               (O.progDesc "Backup the specified databases"))
    <> O.command "restore"
            (O.info
                (O.helper <*> parseRestore)
                (O.progDesc "Restore database"))

    <> O.command "build"
            (O.info
                (O.helper <*> pure Build)
                (O.progDesc "Build the application"))

    <> O.command "deploy"
            (O.info
                (O.helper <*> pure Deploy)
                (O.progDesc "Deploy the application"))

    <> O.command "dump"
            (O.info
                (O.helper <*> pure Dump)
                (O.progDesc "Show the dampf context"))

    <> O.command "newmigration"
            (O.info
                (O.helper <*> parseNewMigration)
                (O.progDesc "Create a new database migration"))

    <> O.command "runmigrations"
            (O.info
                (O.helper <*> parseRunMigrations)
                (O.progDesc "Run unapplied database migrations"))
    <> O.command "run"
            (O.info
                (O.helper <*> parseRun)
                (O.progDesc "Run container"))
    <> O.command "setupdb"
            (O.info
                (O.helper <*> pure SetupDatabase)
                (O.progDesc "Setup the databases"))

    <> O.command "monitor"
            (O.info
                (O.helper <*> parseMonitor)
                (O.progDesc "Run specified test (if not specified, run all tests) against live production environment"))

    <> O.command "browse"
            (O.info
                (O.helper <*> parseBrowse)
                (O.progDesc "Run a browser to interact with proxied conatainers"))

    <> O.command "env"
            (O.info
                (O.helper <*> parseEnv)
                (O.progDesc "Run command locally in an environment that can access database"))
    <> O.command "lsenv"
            (O.info
                (O.helper <*> pure LsEnv)
                (O.progDesc "list variables for an environment that can access database"))
    <> O.command "provision"
            (O.info
                (O.helper <*> parseProvision)
                (O.progDesc "Provision a server"))

    <> O.command "test"
            (O.info
                (O.helper <*> parseTest)
                (O.progDesc "Run specified test (if not specified, all tests)"))

instance ParseField ProvisionType
instance ParseField Backend

parseProvision :: Parser Command
parseProvision = Provision
    <$> parseField (Just "SingleServer | Development | CI") Nothing Nothing

parseBackup :: Parser Command
parseBackup = Backup
    <$> optional (O.argument readerText (O.metavar "DATABASE"))

parseRestore :: Parser Command
parseRestore = Restore
        <$> O.argument O.readerAsk (O.metavar "FILE")
        <*> optional (O.argument readerText (O.metavar "DATABASE"))


parseBrowse :: Parser Command
parseBrowse = Browse
    <$> parseField (Just "VNC | X11 ") Nothing Nothing

parseRun :: Parser Command
parseRun = Run
    <$> O.argument readerText (O.metavar "IMAGE")
    <*> optional (O.argument readerText (O.metavar "COMMAND"))

parseNewMigration :: Parser Command
parseNewMigration = NewMigration
    <$> O.argument readerText (O.metavar "DATABASE")
    <*> O.strArgument (O.metavar "NAME")


parseRunMigrations :: Parser Command
parseRunMigrations = RunMigrations
    <$> optional (O.argument readerText (O.metavar "DATABASE"))


parseTest :: Parser Command
parseTest = Test
    <$> many (O.argument readerText (O.metavar "TESTS"))

parseMonitor :: Parser Command
parseMonitor = Monitor
    <$> many (O.argument readerText (O.metavar "TESTS"))

parseEnv :: Parser Command
parseEnv = Env
    <$> many (O.argument readerText (O.metavar "CMDS"))

readerText :: ReadM Text
readerText = T.pack <$> O.readerAsk

