package vulndb

// VulnSource is the CVE-metadata lookup the enricher needs. *Store satisfies it.
type VulnSource interface {
	Vulnerability(cveID string) (VulnInfo, error)
}

// MetaCache enriches match results with the CVE's own metadata.
//
// This is not an optimisation, it is where severity comes from. Debian, Ubuntu
// and Alpine advisories carry no severity of their own — trivy-db leaves
// Advisory.Severity zero for those buckets and keeps the rating in the
// vulnerability bucket's VendorSeverity map instead. Taking the advisory's
// value alone reported an entire fleet as "unknown".
//
// One cache per tick, shared across servers: a CVE affects every host running
// the package, and the bolt read is the same read every time.
type MetaCache struct {
	src  VulnSource
	seen map[string]VulnInfo
}

func NewMetaCache(src VulnSource) *MetaCache {
	return &MetaCache{src: src, seen: make(map[string]VulnInfo)}
}

// Enrich fills severity, title, score and references in place.
//
// The advisory's severity is kept when the vulnerability bucket has nothing
// better to say — RHEL does publish it per advisory — so this can only raise
// the quality of the answer, never lower it.
func (m *MetaCache) Enrich(results []Result) {
	if m == nil {
		return
	}
	for i := range results {
		info, ok := m.lookup(results[i].CVEID)
		if !ok {
			continue
		}
		if info.Severity != "" && info.Severity != "unknown" {
			results[i].Severity = info.Severity
		}
		results[i].Title = info.Title
		results[i].CVSSScore = info.CVSSScore
		results[i].References = info.References
	}
}

func (m *MetaCache) lookup(cveID string) (VulnInfo, bool) {
	if info, ok := m.seen[cveID]; ok {
		return info, true
	}
	info, err := m.src.Vulnerability(cveID)
	if err != nil {
		// A CVE with an advisory but no vulnerability document is a real state
		// in trivy-db, not a fault. Cache the miss so it is asked once.
		Debugf("no vulnerability record for %s: %v", cveID, err)
		m.seen[cveID] = VulnInfo{}
		return VulnInfo{}, false
	}
	m.seen[cveID] = info
	return info, true
}
