package cli

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"coopcloud.tech/abra/cli/app"
	"coopcloud.tech/abra/cli/catalogue"
	"coopcloud.tech/abra/cli/internal"
	"coopcloud.tech/abra/cli/recipe"
	"coopcloud.tech/abra/cli/server"
	"coopcloud.tech/abra/pkg/config"
	"coopcloud.tech/abra/pkg/formatter"
	"coopcloud.tech/abra/pkg/i18n"
	"coopcloud.tech/abra/pkg/log"
	charmLog "github.com/charmbracelet/log"
	"github.com/spf13/cobra"
	"github.com/spf13/cobra/doc"
)

var (
	// translators: `abra` usage template. please translate only words like
	// "Aliases" and "Example" and nothing inside the {{ ... }}
	usageTemplate = i18n.G(`Usage:{{if .Runnable}}
  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
  {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}

Aliases:
  {{.NameAndAliases}}{{end}}{{if .HasExample}}

Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}

Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}

Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}

Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}

Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}

Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`)

	helpCmd = &cobra.Command{
		Use: i18n.G("help [command]"),
		// translators: Short description for `help` command
		Short: i18n.G("Help about any command"),
		Long: i18n.G(`Help provides help for any command in the application.
Simply type abra help [path to command] for full details.`),
		Run: func(c *cobra.Command, args []string) {
			cmd, _, e := c.Root().Find(args)
			if cmd == nil || e != nil {
				c.Print(i18n.G("unknown help topic %#q\n", args))
				if err := c.Root().Usage(); err != nil {
					log.Fatal(err)
				}
			} else {
				cmd.InitDefaultHelpFlag()
				cmd.InitDefaultVersionFlag()
				if err := cmd.Help(); err != nil {
					log.Fatal(err)
				}
			}
		},
	}
)

func Run(version, commit string) {
	rootCmd := &cobra.Command{
		// translators: `abra` binary name
		Use: i18n.G("abra [cmd] [args] [flags]"),
		// translators: Short description for `abra` binary
		Short: i18n.G("The Co-op Cloud command-line utility belt 🎩🐇"),
		// translators: Long description for `abra` binary. This needs to be
		// translated in the same way as the Short description so that everything
		// matches up
		Long: i18n.G(`The Co-op Cloud command-line utility belt 🎩🐇

Config:
  $ABRA_DIR: %s`, config.ABRA_DIR),
		Version: fmt.Sprintf("%s-%s", version, commit[:7]),
		ValidArgs: []string{
			// translators: `abra app` command for autocompletion
			i18n.G("app"),
			// translators: `abra autocomplete` command for autocompletion
			i18n.G("autocomplete"),
			// translators: `abra catalogue` command for autocompletion
			i18n.G("catalogue"),
			// translators: `abra man` command for autocompletion
			i18n.G("man"),
			// translators: `abra recipe` command for autocompletion
			i18n.G("recipe"),
			// translators: `abra server` command for autocompletion
			i18n.G("server"),
			// translators: `abra upgrade` command for autocompletion
			i18n.G("upgrade"),
		},
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			dirs := []map[string]os.FileMode{
				{config.ABRA_DIR: 0764},
				{config.SERVERS_DIR: 0700},
				{config.RECIPES_DIR: 0764},
				{config.LOGS_DIR: 0764},
			}

			for _, dir := range dirs {
				for path, perm := range dir {
					if err := os.Mkdir(path, perm); err != nil {
						if !os.IsExist(err) {
							return errors.New(i18n.G("unable to create %s: %s", path, err))
						}
						continue
					}
				}
			}

			log.Logger.SetStyles(charmLog.DefaultStyles())
			charmLog.SetDefault(log.Logger)

			if internal.MachineReadable {
				log.SetOutput(os.Stderr)
			}

			if internal.Debug {
				log.SetLevel(log.DebugLevel)
				log.SetOutput(os.Stderr)
				log.SetReportCaller(true)
			}

			log.Debug(i18n.G(
				"abra version: %s, commit: %s, lang: %s",
				version, formatter.SmallSHA(commit), i18n.Locale,
			))

			return nil
		},
	}

	rootCmd.CompletionOptions.DisableDefaultCmd = true
	rootCmd.SetUsageTemplate(usageTemplate)
	rootCmd.SetHelpCommand(helpCmd)

	// translators: `abra man` aliases. use a comma separated list of aliases
	// with no spaces in between
	manAliases := i18n.G("m")

	manCommand := &cobra.Command{
		// translators: `man` command
		Use:     i18n.G("man [flags]"),
		Aliases: strings.Split(manAliases, ","),
		// translators: Short description for `man` command
		Short: i18n.G("Generate manpage"),
		Example: i18n.G(`  # generate the man pages into /usr/local/share/man/man1
  abra_path=$(which abra)  # pass abra absolute path to sudo below
  sudo $abra_path man
  sudo mandb

  # read the man pages
  man abra
  man abra-app-deploy`),
		Run: func(cmd *cobra.Command, args []string) {
			header := &doc.GenManHeader{
				Title:   "ABRA",
				Section: "1",
			}

			manDir := "/usr/local/share/man/man1"
			if _, err := os.Stat(manDir); os.IsNotExist(err) {
				log.Fatal(i18n.G("unable to proceed, %s does not exist?", manDir))
			}

			err := doc.GenManTree(rootCmd, header, manDir)
			if err != nil {
				log.Fatal(err)
			}

			log.Info(i18n.G("don't forget to run 'sudo mandb'"))
		},
	}

	rootCmd.PersistentFlags().BoolVarP(
		&internal.Debug,
		i18n.G("debug"),
		i18n.G("d"),
		false,
		i18n.G("show debug messages"),
	)

	rootCmd.PersistentFlags().BoolVarP(
		&internal.NoInput,
		i18n.G("no-input"),
		i18n.G("n"),
		false,
		i18n.G("toggle non-interactive mode"),
	)

	rootCmd.PersistentFlags().BoolVarP(
		&internal.Help,
		i18n.G("help"),
		i18n.G("h"),
		false,
		i18n.G("help for abra"),
	)

	rootCmd.PersistentFlags().BoolVarP(
		&internal.Offline,
		i18n.G("offline"),
		i18n.G("o"),
		false,
		i18n.G("prefer offline & filesystem access"),
	)

	rootCmd.Flags().BoolVarP(
		&internal.Version,
		i18n.G("version"),
		i18n.G("v"),
		false,
		i18n.G("version for abra"),
	)

	catalogue.CatalogueCommand.AddCommand(
		catalogue.CatalogueGenerateCommand,
		catalogue.CatalogueSyncCommand,
	)

	server.ServerCommand.AddCommand(
		server.ServerAddCommand,
		server.ServerListCommand,
		server.ServerPruneCommand,
		server.ServerRemoveCommand,
	)

	recipe.RecipeCommand.AddCommand(
		recipe.RecipeDiffCommand,
		recipe.RecipeFetchCommand,
		recipe.RecipeLintCommand,
		recipe.RecipeListCommand,
		recipe.RecipeNewCommand,
		recipe.RecipeReleaseCommand,
		recipe.RecipeResetCommand,
		recipe.RecipeUpgradeCommand,
		recipe.RecipeVersionCommand,
	)

	rootCmd.AddCommand(
		UpgradeCommand,
		AutocompleteCommand,
		manCommand,
		app.AppCommand,
		catalogue.CatalogueCommand,
		server.ServerCommand,
		recipe.RecipeCommand,
	)

	app.AppCmdCommand.AddCommand(
		app.AppCmdListCommand,
	)

	app.AppSecretCommand.AddCommand(
		app.AppSecretGenerateCommand,
		app.AppSecretInsertCommand,
		app.AppSecretRmCommand,
		app.AppSecretLsCommand,
	)

	app.AppVolumeCommand.AddCommand(
		app.AppVolumeListCommand,
		app.AppVolumeRemoveCommand,
	)

	app.AppBackupCommand.AddCommand(
		app.AppBackupListCommand,
		app.AppBackupDownloadCommand,
		app.AppBackupCreateCommand,
		app.AppBackupSnapshotsCommand,
	)

	app.AppEnvCommand.AddCommand(
		app.AppEnvListCommand,
		app.AppEnvPullCommand,
	)

	app.AppCommand.AddCommand(
		app.AppBackupCommand,
		app.AppCheckCommand,
		app.AppCmdCommand,
		app.AppConfigCommand,
		app.AppCpCommand,
		app.AppDeployCommand,
		app.AppListCommand,
		app.AppLogsCommand,
		app.AppNewCommand,
		app.AppPsCommand,
		app.AppRemoveCommand,
		app.AppRestartCommand,
		app.AppRestoreCommand,
		app.AppRollbackCommand,
		app.AppMoveCommand,
		app.AppRunCommand,
		app.AppSecretCommand,
		app.AppServicesCommand,
		app.AppUndeployCommand,
		app.AppUpgradeCommand,
		app.AppVolumeCommand,
		app.AppLabelsCommand,
		app.AppEnvCommand,
	)

	if err := rootCmd.Execute(); err != nil {
		os.Exit(1)
	}
}
