package cmd

import (
	"github.com/heroku/color"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"github.com/buildpacks/pack/buildpackage"
	builderwriter "github.com/buildpacks/pack/internal/builder/writer"
	"github.com/buildpacks/pack/internal/commands"
	"github.com/buildpacks/pack/internal/config"
	imagewriter "github.com/buildpacks/pack/internal/inspectimage/writer"
	"github.com/buildpacks/pack/internal/term"
	"github.com/buildpacks/pack/pkg/client"
	"github.com/buildpacks/pack/pkg/logging"
)

// ConfigurableLogger defines behavior required by the PackCommand
type ConfigurableLogger interface {
	logging.Logger
	WantTime(f bool)
	WantQuiet(f bool)
	WantVerbose(f bool)
}

// NewPackCommand generates a Pack command
//
//nolint:staticcheck
func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) {
	cobra.EnableCommandSorting = false
	cfg, cfgPath, err := initConfig()
	if err != nil {
		return nil, err
	}

	packClient, err := initClient(logger, cfg)
	if err != nil {
		return nil, err
	}

	rootCmd := &cobra.Command{
		Use:   "pack",
		Short: "CLI for building apps using Cloud Native Buildpacks",
		PersistentPreRun: func(cmd *cobra.Command, args []string) {
			if fs := cmd.Flags(); fs != nil {
				if forceColor, err := fs.GetBool("force-color"); err == nil && !forceColor {
					if flag, err := fs.GetBool("no-color"); err == nil && flag {
						color.Disable(flag)
					}

					_, canDisplayColor := term.IsTerminal(logging.GetWriterForLevel(logger, logging.InfoLevel))
					if !canDisplayColor {
						color.Disable(true)
					}
				}
				if flag, err := fs.GetBool("quiet"); err == nil {
					logger.WantQuiet(flag)
				}
				if flag, err := fs.GetBool("verbose"); err == nil {
					logger.WantVerbose(flag)
				}
				if flag, err := fs.GetBool("timestamps"); err == nil {
					logger.WantTime(flag)
				}
			}
		},
	}

	rootCmd.PersistentFlags().Bool("no-color", false, "Disable color output")
	rootCmd.PersistentFlags().Bool("force-color", false, "Force color output")
	rootCmd.PersistentFlags().Bool("timestamps", false, "Enable timestamps in output")
	rootCmd.PersistentFlags().BoolP("quiet", "q", false, "Show less output")
	rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Show more output")
	rootCmd.Flags().Bool("version", false, "Show current 'pack' version")

	commands.AddHelpFlag(rootCmd, "pack")

	rootCmd.AddCommand(commands.Build(logger, cfg, packClient))
	rootCmd.AddCommand(commands.NewBuilderCommand(logger, cfg, packClient))
	rootCmd.AddCommand(commands.NewBuildpackCommand(logger, cfg, packClient, buildpackage.NewConfigReader()))
	rootCmd.AddCommand(commands.NewExtensionCommand(logger, cfg, packClient, buildpackage.NewConfigReader()))
	rootCmd.AddCommand(commands.NewConfigCommand(logger, cfg, cfgPath, packClient))
	rootCmd.AddCommand(commands.InspectImage(logger, imagewriter.NewFactory(), cfg, packClient))
	rootCmd.AddCommand(commands.NewStackCommand(logger))
	rootCmd.AddCommand(commands.Rebase(logger, cfg, packClient))
	rootCmd.AddCommand(commands.NewSBOMCommand(logger, cfg, packClient))

	rootCmd.AddCommand(commands.InspectBuildpack(logger, cfg, packClient))
	rootCmd.AddCommand(commands.InspectBuilder(logger, cfg, packClient, builderwriter.NewFactory()))

	rootCmd.AddCommand(commands.SetDefaultBuilder(logger, cfg, cfgPath, packClient))
	rootCmd.AddCommand(commands.SetRunImagesMirrors(logger, cfg, cfgPath))
	rootCmd.AddCommand(commands.SuggestBuilders(logger, packClient))
	rootCmd.AddCommand(commands.TrustBuilder(logger, cfg, cfgPath))
	rootCmd.AddCommand(commands.UntrustBuilder(logger, cfg, cfgPath))
	rootCmd.AddCommand(commands.ListTrustedBuilders(logger, cfg))
	rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, packClient))
	rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, packClient, buildpackage.NewConfigReader()))

	if cfg.Experimental {
		rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath))
		rootCmd.AddCommand(commands.ListBuildpackRegistries(logger, cfg))
		rootCmd.AddCommand(commands.RegisterBuildpack(logger, cfg, packClient))
		rootCmd.AddCommand(commands.SetDefaultRegistry(logger, cfg, cfgPath))
		rootCmd.AddCommand(commands.RemoveRegistry(logger, cfg, cfgPath))
		rootCmd.AddCommand(commands.YankBuildpack(logger, cfg, packClient))
		rootCmd.AddCommand(commands.NewManifestCommand(logger, packClient))
	}

	packHome, err := config.PackHome()
	if err != nil {
		return nil, err
	}

	rootCmd.AddCommand(commands.CompletionCommand(logger, packHome))
	rootCmd.AddCommand(commands.Report(logger, packClient.Version(), cfgPath))
	rootCmd.AddCommand(commands.Version(logger, packClient.Version()))

	rootCmd.Version = packClient.Version()
	rootCmd.SetVersionTemplate(`{{.Version}}{{"\n"}}`)
	rootCmd.SetOut(logging.GetWriterForLevel(logger, logging.InfoLevel))
	rootCmd.SetErr(logging.GetWriterForLevel(logger, logging.ErrorLevel))

	return rootCmd, nil
}

func initConfig() (config.Config, string, error) {
	path, err := config.DefaultConfigPath()
	if err != nil {
		return config.Config{}, "", errors.Wrap(err, "getting config path")
	}

	cfg, err := config.Read(path)
	if err != nil {
		return config.Config{}, "", errors.Wrap(err, "reading pack config")
	}
	return cfg, path, nil
}

func initClient(logger logging.Logger, cfg config.Config) (*client.Client, error) {
	if err := client.ProcessDockerContext(logger); err != nil {
		return nil, err
	}

	dc, err := tryInitSSHDockerClient()
	if err != nil {
		return nil, err
	}

	// If we got a docker client from SSH, use it directly
	if dc != nil {
		return client.NewClient(client.WithLogger(logger), client.WithExperimental(cfg.Experimental), client.WithRegistryMirrors(cfg.RegistryMirrors), client.WithDockerClient(dc))
	}

	return client.NewClient(client.WithLogger(logger), client.WithExperimental(cfg.Experimental), client.WithRegistryMirrors(cfg.RegistryMirrors))
}
