// Copyright © 2019 The Knative Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package plugin

import (
	"fmt"
	"os"
	"strings"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"knative.dev/client/pkg/kn/commands"
	"knative.dev/client/pkg/kn/config"
	"knative.dev/client/pkg/kn/plugin"
)

// ValidPluginFilenamePrefixes controls the prefix for all kn plugins
var ValidPluginFilenamePrefixes = []string{"kn"}

// pluginListFlags contains all plugin commands flags
type pluginListFlags struct {
	verbose bool
}

// NewPluginListCommand creates a new `kn plugin list` command
func NewPluginListCommand(p *commands.KnParams) *cobra.Command {

	plFlags := pluginListFlags{}
	pluginListCommand := &cobra.Command{
		Use:     "list",
		Short:   "List plugins",
		Aliases: []string{"ls"},
		Long: `List all installed plugins.

Available plugins are those that are:
- executable
- begin with "kn-"
- Kn's plugin directory
- Anywhere in the execution $PATH (if plugins.path-lookup configuration variable is enabled)`,
		RunE: func(cmd *cobra.Command, args []string) error {
			return listPlugins(cmd, plFlags)
		},
	}

	// Plugin flags
	pluginListCommand.Flags().BoolVar(&plFlags.verbose, "verbose", false, "verbose output")

	return pluginListCommand
}

// List plugins by looking up in plugin directory and path
func listPlugins(cmd *cobra.Command, flags pluginListFlags) error {
	factory := plugin.NewManager(config.GlobalConfig.PluginsDir(), config.GlobalConfig.LookupPluginsInPath())

	pluginsFound, err := factory.ListPlugins()
	if err != nil {
		return errors.Wrap(err, fmt.Sprintf("cannot list plugins in %s (lookup plugins in $PATH: %t)", factory.PluginsDir(), factory.LookupInPath()))
	}

	out := cmd.OutOrStdout()
	if flags.verbose {
		fmt.Fprintf(out, "The following plugins are available, using options:\n")
		fmt.Fprintf(out, "  plugins dir: '%s'%s\n", factory.PluginsDir(), extraLabelIfPathNotExists(factory.PluginsDir()))
		fmt.Fprintf(out, "  lookup plugins in $PATH: %t\n\n", factory.LookupInPath())
	}

	if len(pluginsFound) == 0 {
		if flags.verbose {
			fmt.Fprintf(out, "No plugins found in path '%s'.\n", factory.PluginsDir())
		} else {
			fmt.Fprintln(out, "No plugins found.")
		}
		return nil
	}

	eaw := factory.Verify()
	eaw = addErrorIfOverwritingExistingCommand(eaw, cmd.Root(), pluginsFound)

	for _, pl := range pluginsFound {
		desc, _ := pl.Description()
		if desc != "" {
			fmt.Fprintf(out, "- %s : %s", pl.Name(), desc)
		} else {
			fmt.Fprintf(out, "- %s", pl.Name())
		}
		if flags.verbose {
			fmt.Fprintf(out, "  (%s)\n", pl.Path())
		} else {
			fmt.Fprintln(out, "")
		}
	}
	if !eaw.IsEmpty() {
		fmt.Fprintln(out, "")
		eaw.PrintWarningsAndErrors(out)
	}
	if eaw.HasErrors() {
		return errors.Errorf("plugin validation errors")
	}
	return nil
}

// create an info label which can be appended to an verbose output
func extraLabelIfPathNotExists(path string) string {
	_, err := os.Stat(path)
	if err == nil {
		return ""
	}
	if os.IsNotExist(err) {
		return " (does not exist)"
	}
	return ""
}

func addErrorIfOverwritingExistingCommand(eaw plugin.VerificationErrorsAndWarnings, rootCmd *cobra.Command, plugins []plugin.Plugin) plugin.VerificationErrorsAndWarnings {

	for _, plugin := range plugins {
		cmd, args, err := rootCmd.Find(plugin.CommandParts())
		if err == nil {
			if !cmd.HasSubCommands() || // a leaf command can't be overridden
				cmd.HasSubCommands() && len(args) == 0 { // a group can't be overridden either
				eaw.AddError("%s overwrites existing built-in command: '%s'", plugin.Path(), strings.Join(plugin.CommandParts(), " "))
			}
		}
	}
	return eaw
}
