package flag

import (
	"github.com/samber/lo"

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

var (
	VulnTypeFlag = Flag[[]string]{
		Name:       "vuln-type",
		ConfigName: "vulnerability.type",
		Default: []string{
			types.VulnTypeOS,
			types.VulnTypeLibrary,
		},
		Values: []string{
			types.VulnTypeOS,
			types.VulnTypeLibrary,
		},
		Usage: "comma-separated list of vulnerability types",
	}
	IgnoreUnfixedFlag = Flag[bool]{
		Name:       "ignore-unfixed",
		ConfigName: "vulnerability.ignore-unfixed",
		Usage:      "display only fixed vulnerabilities",
	}
	IgnoreStatusFlag = Flag[[]string]{
		Name:       "ignore-status",
		ConfigName: "vulnerability.ignore-status",
		Values:     dbTypes.Statuses,
		Usage:      "comma-separated list of vulnerability status to ignore",
	}
	VEXFlag = Flag[string]{
		Name:       "vex",
		ConfigName: "vulnerability.vex",
		Default:    "",
		Usage:      "[EXPERIMENTAL] file path to VEX",
	}
)

type VulnerabilityFlagGroup struct {
	VulnType      *Flag[[]string]
	IgnoreUnfixed *Flag[bool]
	IgnoreStatus  *Flag[[]string]
	VEXPath       *Flag[string]
}

type VulnerabilityOptions struct {
	VulnType       []string
	IgnoreStatuses []dbTypes.Status
	VEXPath        string
}

func NewVulnerabilityFlagGroup() *VulnerabilityFlagGroup {
	return &VulnerabilityFlagGroup{
		VulnType:      VulnTypeFlag.Clone(),
		IgnoreUnfixed: IgnoreUnfixedFlag.Clone(),
		IgnoreStatus:  IgnoreStatusFlag.Clone(),
		VEXPath:       VEXFlag.Clone(),
	}
}

func (f *VulnerabilityFlagGroup) Name() string {
	return "Vulnerability"
}

func (f *VulnerabilityFlagGroup) Flags() []Flagger {
	return []Flagger{
		f.VulnType,
		f.IgnoreUnfixed,
		f.IgnoreStatus,
		f.VEXPath,
	}
}

func (f *VulnerabilityFlagGroup) ToOptions() (VulnerabilityOptions, error) {
	if err := parseFlags(f); err != nil {
		return VulnerabilityOptions{}, err
	}

	// Just convert string to dbTypes.Status as the validated values are passed here.
	ignoreStatuses := lo.Map(f.IgnoreStatus.Value(), func(s string, _ int) dbTypes.Status {
		return dbTypes.NewStatus(s)
	})
	ignoreUnfixed := f.IgnoreUnfixed.Value()

	switch {
	case ignoreUnfixed && len(ignoreStatuses) > 0:
		log.Logger.Warn("'--ignore-unfixed' is ignored because '--ignore-status' is specified")
	case ignoreUnfixed:
		// '--ignore-unfixed' is a shorthand of '--ignore-status'.
		ignoreStatuses = lo.FilterMap(dbTypes.Statuses, func(s string, _ int) (dbTypes.Status, bool) {
			fixed := dbTypes.StatusFixed
			if s == fixed.String() {
				return 0, false
			}
			return dbTypes.NewStatus(s), true
		})
	case len(ignoreStatuses) == 0:
		ignoreStatuses = nil
	}
	log.Logger.Debugw("Ignore statuses", "statuses", ignoreStatuses)

	return VulnerabilityOptions{
		VulnType:       f.VulnType.Value(),
		IgnoreStatuses: ignoreStatuses,
		VEXPath:        f.VEXPath.Value(),
	}, nil
}
