package vulnerability

import (
	"bufio"
	"os"
	"sort"
	"strings"

	"github.com/google/wire"

	"github.com/aquasecurity/trivy-db/pkg/db"

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

const (
	DefaultIgnoreFile = ".trivyignore"
)

var SuperSet = wire.NewSet(
	wire.Struct(new(db.Config)),
	NewClient,
	wire.Bind(new(Operation), new(Client)),
)

type Operation interface {
	FillInfo(vulns []types.DetectedVulnerability, light bool)
	Filter(vulns []types.DetectedVulnerability, severities []dbTypes.Severity,
		ignoreUnfixed bool, ignoreFile string) []types.DetectedVulnerability
}

type Client struct {
	dbc db.Operation
}

func NewClient(dbc db.Config) Client {
	return Client{dbc: dbc}
}

func (c Client) FillInfo(vulns []types.DetectedVulnerability, light bool) {
	var err error
	var severity dbTypes.Severity

	for i := range vulns {
		if light {
			severity, err = c.dbc.GetSeverity(vulns[i].VulnerabilityID)
			vulns[i].Vulnerability.Severity = severity.String()
		} else {
			vulns[i].Vulnerability, err = c.dbc.GetVulnerability(vulns[i].VulnerabilityID)
		}
		if err != nil {
			log.Logger.Warnf("Error while getting vulnerability details: %s\n", err)
			continue
		}
	}
}

func (c Client) Filter(vulns []types.DetectedVulnerability, severities []dbTypes.Severity,
	ignoreUnfixed bool, ignoreFile string) []types.DetectedVulnerability {
	ignoredIDs := getIgnoredIDs(ignoreFile)
	var vulnerabilities []types.DetectedVulnerability
	for _, vuln := range vulns {
		// Filter vulnerabilities by severity
		for _, s := range severities {
			if s.String() == vuln.Severity {
				// Ignore unfixed vulnerabilities
				if ignoreUnfixed && vuln.FixedVersion == "" {
					continue
				} else if utils.StringInSlice(vuln.VulnerabilityID, ignoredIDs) {
					continue
				}
				vulnerabilities = append(vulnerabilities, vuln)
				break
			}
		}
	}
	sort.Slice(vulnerabilities, func(i, j int) bool {
		if vulnerabilities[i].PkgName != vulnerabilities[j].PkgName {
			return vulnerabilities[i].PkgName < vulnerabilities[j].PkgName
		}
		ret := dbTypes.CompareSeverityString(
			vulnerabilities[j].Severity, vulnerabilities[i].Severity,
		)
		if ret != 0 {
			return ret > 0
		}
		return vulnerabilities[i].VulnerabilityID < vulnerabilities[j].VulnerabilityID
	})
	return vulnerabilities
}

func getIgnoredIDs(ignoreFile string) []string {
	f, err := os.Open(ignoreFile)
	if err != nil {
		// trivy must work even if no .trivyignore exist
		return nil
	}

	var ignoredIDs []string
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "#") || line == "" {
			continue
		}
		ignoredIDs = append(ignoredIDs, line)
	}
	return ignoredIDs
}
