package vulnerability

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

	"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"
)

type Client struct {
	dbc db.Operations
}

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

func (c Client) FillAndFilter(vulns []types.DetectedVulnerability, severities []dbTypes.Severity,
	ignoreUnfixed bool, ignoreFile string, light bool) []types.DetectedVulnerability {
	var err error
	var severity dbTypes.Severity

	ignoredIDs := getIgnoredIDs(ignoreFile)
	var vulnerabilities []types.DetectedVulnerability
	for _, vuln := range vulns {
		var vulnerability dbTypes.Vulnerability
		if light {
			severity, err = c.dbc.GetSeverity(vuln.VulnerabilityID)
			vulnerability.Severity = severity.String()
		} else {
			vulnerability, err = c.dbc.GetVulnerability(vuln.VulnerabilityID)
		}
		if err != nil {
			log.Logger.Warn(err)
			continue
		}

		// Filter vulnerabilities by severity
		for _, s := range severities {
			if s.String() == vulnerability.Severity {
				vuln.Vulnerability = vulnerability

				// 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
}
