package plugin

import (
	"context"
	"fmt"
	"os"

	"github.com/urfave/cli/v2"
	"golang.org/x/xerrors"

	"github.com/aquasecurity/trivy/pkg/commands/option"
	"github.com/aquasecurity/trivy/pkg/log"
	"github.com/aquasecurity/trivy/pkg/plugin"
)

// Install installs a plugin
func Install(c *cli.Context) error {
	if c.NArg() != 1 {
		cli.ShowSubcommandHelpAndExit(c, 1)
	}

	if err := initLogger(c); err != nil {
		return xerrors.Errorf("initialize error: %w", err)
	}

	url := c.Args().First()
	if _, err := plugin.Install(c.Context, url, true); err != nil {
		return xerrors.Errorf("plugin install error: %w", err)
	}

	return nil
}

// Uninstall uninstalls the plugin
func Uninstall(c *cli.Context) error {
	if c.NArg() != 1 {
		cli.ShowSubcommandHelpAndExit(c, 1)
	}

	if err := initLogger(c); err != nil {
		return xerrors.Errorf("initialize error: %w", err)
	}

	pluginName := c.Args().First()
	if err := plugin.Uninstall(pluginName); err != nil {
		return xerrors.Errorf("plugin uninstall error: %w", err)
	}

	return nil
}

// Information displays information about the plugin
func Information(c *cli.Context) error {
	if c.NArg() != 1 {
		cli.ShowSubcommandHelpAndExit(c, 1)
	}

	if err := initLogger(c); err != nil {
		return xerrors.Errorf("initialize logger error: %w", err)
	}

	pluginName := c.Args().First()
	info, err := plugin.Information(pluginName)
	if err != nil {
		return xerrors.Errorf("plugin information display error: %w", err)
	}

	if _, err = fmt.Fprintf(os.Stdout, info); err != nil {
		return xerrors.Errorf("print error: %w", err)
	}

	return nil
}

// List displays a list of all of installed plugins
func List(c *cli.Context) error {
	if err := initLogger(c); err != nil {
		return xerrors.Errorf("initialize error: %w", err)
	}

	info, err := plugin.List()
	if err != nil {
		return xerrors.Errorf("plugin list display error: %w", err)
	}

	if _, err = fmt.Fprintf(os.Stdout, info); err != nil {
		return xerrors.Errorf("print error: %w", err)
	}

	return nil
}

// Update updates an existing plugin
func Update(c *cli.Context) error {
	if c.NArg() != 1 {
		cli.ShowSubcommandHelpAndExit(c, 1)
	}

	if err := initLogger(c); err != nil {
		return xerrors.Errorf("initialize error: %w", err)
	}

	pluginName := c.Args().First()
	if err := plugin.Update(pluginName); err != nil {
		return xerrors.Errorf("plugin update error: %w", err)
	}

	return nil
}

// Run runs the plugin
func Run(c *cli.Context) error {
	if c.NArg() < 1 {
		cli.ShowSubcommandHelpAndExit(c, 1)
	}

	if err := initLogger(c); err != nil {
		return xerrors.Errorf("initialize error: %w", err)
	}

	url := c.Args().First()
	args := c.Args().Tail()
	return RunWithArgs(c.Context, url, args)
}

// RunWithArgs runs the plugin with arguments
func RunWithArgs(ctx context.Context, url string, args []string) error {
	pl, err := plugin.Install(ctx, url, false)
	if err != nil {
		return xerrors.Errorf("plugin install error: %w", err)
	}

	if err = pl.Run(ctx, args); err != nil {
		return xerrors.Errorf("unable to run %s plugin: %w", pl.Name, err)
	}
	return nil
}

// LoadCommands loads plugins as subcommands
func LoadCommands() cli.Commands {
	var commands cli.Commands
	plugins, err := plugin.LoadAll()
	if err != nil {
		log.Logger.Debugf("no plugins were loaded")
		return nil
	}
	for _, p := range plugins {
		p := p
		cmd := &cli.Command{
			Name:  p.Name,
			Usage: p.Usage,
			Action: func(c *cli.Context) error {
				if err := initLogger(c); err != nil {
					return xerrors.Errorf("initialize error: %w", err)
				}

				if err := p.Run(c.Context, c.Args().Slice()); err != nil {
					return xerrors.Errorf("plugin error: %w", err)
				}
				return nil
			},
			SkipFlagParsing: true,
		}
		commands = append(commands, cmd)
	}
	return commands
}

func initLogger(ctx *cli.Context) error {
	conf, err := option.NewGlobalOption(ctx)
	if err != nil {
		return xerrors.Errorf("config error: %w", err)
	}

	if err = log.InitLogger(conf.Debug, conf.Quiet); err != nil {
		return xerrors.Errorf("failed to initialize a logger: %w", err)
	}
	return nil
}
