package flag

import (
	"github.com/samber/lo"

	dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
	"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
	"github.com/aquasecurity/trivy/pkg/log"
	"github.com/aquasecurity/trivy/pkg/vex"
	xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
)

var (
	IgnoreUnfixedFlag = Flag[bool]{
		Name:          "ignore-unfixed",
		ConfigName:    "vulnerability.ignore-unfixed",
		Usage:         "display only fixed vulnerabilities",
		TelemetrySafe: true,
	}
	IgnoreStatusFlag = Flag[[]string]{
		Name:          "ignore-status",
		ConfigName:    "vulnerability.ignore-status",
		Values:        dbTypes.Statuses,
		Usage:         "comma-separated list of vulnerability status to ignore",
		TelemetrySafe: true,
	}
	VEXFlag = Flag[[]string]{
		Name:       "vex",
		ConfigName: "vulnerability.vex",
		Usage:      `[EXPERIMENTAL] VEX sources ("repo", "oci" or file path)`,
	}
	SkipVEXRepoUpdateFlag = Flag[bool]{
		Name:          "skip-vex-repo-update",
		ConfigName:    "vulnerability.skip-vex-repo-update",
		Usage:         `[EXPERIMENTAL] Skip VEX Repository update`,
		TelemetrySafe: true,
	}
	VulnSeveritySourceFlag = Flag[[]string]{
		Name:       "vuln-severity-source",
		ConfigName: "vulnerability.severity-source",
		Default: []string{
			"auto",
		},
		Values:        append(xstrings.ToStringSlice(vulnerability.AllSourceIDs), "auto"),
		Usage:         "order of data sources for selecting vulnerability severity level",
		TelemetrySafe: true,
	}
)

type VulnerabilityFlagGroup struct {
	IgnoreUnfixed      *Flag[bool]
	IgnoreStatus       *Flag[[]string]
	VEX                *Flag[[]string]
	SkipVEXRepoUpdate  *Flag[bool]
	VulnSeveritySource *Flag[[]string]
}

type VulnerabilityOptions struct {
	IgnoreStatuses      []dbTypes.Status
	VEXSources          []vex.Source
	SkipVEXRepoUpdate   bool
	VulnSeveritySources []dbTypes.SourceID
}

func NewVulnerabilityFlagGroup() *VulnerabilityFlagGroup {
	return &VulnerabilityFlagGroup{
		IgnoreUnfixed:      IgnoreUnfixedFlag.Clone(),
		IgnoreStatus:       IgnoreStatusFlag.Clone(),
		VEX:                VEXFlag.Clone(),
		SkipVEXRepoUpdate:  SkipVEXRepoUpdateFlag.Clone(),
		VulnSeveritySource: VulnSeveritySourceFlag.Clone(),
	}
}

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

func (f *VulnerabilityFlagGroup) Flags() []Flagger {
	return []Flagger{
		f.IgnoreUnfixed,
		f.IgnoreStatus,
		f.VEX,
		f.SkipVEXRepoUpdate,
		f.VulnSeveritySource,
	}
}

func (f *VulnerabilityFlagGroup) ToOptions(opts *Options) error {
	// 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.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.Debug("Ignore statuses", log.Any("statuses", ignoreStatuses))

	opts.VulnerabilityOptions = VulnerabilityOptions{
		IgnoreStatuses: ignoreStatuses,
		VEXSources: lo.Map(f.VEX.Value(), func(s string, _ int) vex.Source {
			return vex.NewSource(s)
		}),
		SkipVEXRepoUpdate:   f.SkipVEXRepoUpdate.Value(),
		VulnSeveritySources: xstrings.ToTSlice[dbTypes.SourceID](f.VulnSeveritySource.Value()),
	}
	return nil
}
