package config

import (
	"os"
	"strings"
	"time"

	"github.com/genuinetools/reg/registry"
	"github.com/urfave/cli"
	"go.uber.org/zap"
	"golang.org/x/xerrors"

	dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
	"github.com/aquasecurity/trivy/pkg/log"
)

type Config struct {
	context *cli.Context
	logger  *zap.SugaredLogger

	Quiet      bool
	NoProgress bool
	Debug      bool

	CacheDir       string
	Reset          bool
	DownloadDBOnly bool
	SkipUpdate     bool
	ClearCache     bool

	Input    string
	output   string
	Format   string
	Template string

	Timeout       time.Duration
	vulnType      string
	Light         bool
	severities    string
	IgnoreFile    string
	IgnoreUnfixed bool
	ExitCode      int

	// these variables are generated by Init()
	ImageName  string
	VulnType   []string
	Output     *os.File
	Severities []dbTypes.Severity
	AppVersion string

	// deprecated
	onlyUpdate string
	// deprecated
	refresh bool
	// deprecated
	autoRefresh bool
}

func New(c *cli.Context) (Config, error) {
	debug := c.Bool("debug")
	quiet := c.Bool("quiet")
	logger, err := log.NewLogger(debug, quiet)
	if err != nil {
		return Config{}, xerrors.New("failed to create a logger")
	}
	return Config{
		context: c,
		logger:  logger,

		Quiet:      quiet,
		NoProgress: c.Bool("no-progress"),
		Debug:      debug,

		CacheDir:       c.String("cache-dir"),
		Reset:          c.Bool("reset"),
		DownloadDBOnly: c.Bool("download-db-only"),
		SkipUpdate:     c.Bool("skip-update"),
		ClearCache:     c.Bool("clear-cache"),

		Input:    c.String("input"),
		output:   c.String("output"),
		Format:   c.String("format"),
		Template: c.String("template"),

		Timeout:       c.Duration("timeout"),
		vulnType:      c.String("vuln-type"),
		Light:         c.Bool("light"),
		severities:    c.String("severity"),
		IgnoreFile:    c.String("ignorefile"),
		IgnoreUnfixed: c.Bool("ignore-unfixed"),
		ExitCode:      c.Int("exit-code"),

		onlyUpdate:  c.String("only-update"),
		refresh:     c.Bool("refresh"),
		autoRefresh: c.Bool("auto-refresh"),
	}, nil
}

func (c *Config) Init() (err error) {
	if c.onlyUpdate != "" || c.refresh || c.autoRefresh {
		c.logger.Warn("--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.")
	}
	if c.SkipUpdate && c.DownloadDBOnly {
		return xerrors.New("The --skip-update and --download-db-only option can not be specified both")
	}

	c.Severities = c.splitSeverity(c.severities)
	c.VulnType = strings.Split(c.vulnType, ",")
	c.AppVersion = c.context.App.Version

	// --clear-cache, --download-db-only and --reset don't conduct the scan
	if c.ClearCache || c.DownloadDBOnly || c.Reset {
		return nil
	}

	args := c.context.Args()
	if c.Input == "" && len(args) == 0 {
		c.logger.Error(`trivy requires at least 1 argument or --input option`)
		cli.ShowAppHelp(c.context)
		return xerrors.New("arguments error")
	}

	c.Output = os.Stdout
	if c.output != "" {
		if c.Output, err = os.Create(c.output); err != nil {
			return xerrors.Errorf("failed to create an output file: %w", err)
		}
	}

	if c.Input == "" {
		c.ImageName = args[0]
	}

	// Check whether 'latest' tag is used
	if c.ImageName != "" {
		image, err := registry.ParseImage(c.ImageName)
		if err != nil {
			return xerrors.Errorf("invalid image: %w", err)
		}
		if image.Tag == "latest" {
			c.logger.Warn("You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed")
		}
	}

	return nil
}

func (c *Config) splitSeverity(severity string) []dbTypes.Severity {
	c.logger.Debugf("Severities: %s", severity)
	var severities []dbTypes.Severity
	for _, s := range strings.Split(severity, ",") {
		severity, err := dbTypes.NewSeverity(s)
		if err != nil {
			c.logger.Warnf("unknown severity option: %s", err)
		}
		severities = append(severities, severity)
	}
	return severities
}
