package v5

import (
	"reflect"

	"github.com/anchore/grype/grype/vulnerability"
)

// VulnerabilityMetadata represents all vulnerability data that is not necessary to perform package-to-vulnerability matching.
type VulnerabilityMetadata struct {
	ID           string   `json:"id"`            // The identifier of the vulnerability or advisory
	Namespace    string   `json:"namespace"`     // Where this entry is valid within
	DataSource   string   `json:"data_source"`   // A URL where the data was sourced from
	RecordSource string   `json:"record_source"` // The source of the vulnerability information (relative to the immediate upstream in the enterprise feedgroup)
	Severity     string   `json:"severity"`      // How severe the vulnerability is (valid values are defined by upstream sources currently)
	URLs         []string `json:"urls"`          // URLs to get more information about the vulnerability or advisory
	Description  string   `json:"description"`   // Description of the vulnerability
	Cvss         []Cvss   `json:"cvss"`          // Common Vulnerability Scoring System values
}

// Cvss contains select Common Vulnerability Scoring System fields for a vulnerability.
type Cvss struct {
	// VendorMetadata captures non-standard CVSS fields that vendors can sometimes
	// include when providing CVSS information.  This vendor-specific metadata type
	// allows to capture that data for persisting into the database
	VendorMetadata interface{} `json:"vendor_metadata"`
	Metrics        CvssMetrics `json:"metrics"`
	Vector         string      `json:"vector"`  // A textual representation of the metric values used to determine the score
	Version        string      `json:"version"` // The version of the CVSS spec, for example 2.0, 3.0, or 3.1
	Source         string      `json:"source"`  // Identifies the organization that provided the score
	Type           string      `json:"type"`    // Whether the source is a `primary` or `secondary` source
}

// CvssMetrics are the quantitative values that make up a CVSS score.
type CvssMetrics struct {
	// BaseScore ranges from 0 - 10 and defines qualities intrinsic to the severity of a vulnerability.
	BaseScore float64 `json:"base_score"`
	// ExploitabilityScore is a pointer to avoid having a 0 value by default.
	// It is an indicator of how easy it may be for an attacker to exploit
	// a vulnerability
	ExploitabilityScore *float64 `json:"exploitability_score"`
	// ImpactScore represents the effects of an exploited vulnerability
	// relative to compromise in confidentiality, integrity, and availability.
	// It is an optional parameter, so that is why it is a pointer instead of
	// a regular field
	ImpactScore *float64 `json:"impact_score"`
}

func NewCvssMetrics(baseScore, exploitabilityScore, impactScore float64) CvssMetrics {
	return CvssMetrics{
		BaseScore:           baseScore,
		ExploitabilityScore: &exploitabilityScore,
		ImpactScore:         &impactScore,
	}
}

func (v *VulnerabilityMetadata) Equal(vv VulnerabilityMetadata) bool {
	equal := v.ID == vv.ID &&
		v.Namespace == vv.Namespace &&
		v.DataSource == vv.DataSource &&
		v.RecordSource == vv.RecordSource &&
		v.Severity == vv.Severity &&
		v.Description == vv.Description &&
		len(v.URLs) == len(vv.URLs) &&
		len(v.Cvss) == len(vv.Cvss)

	if !equal {
		return false
	}
	for idx, cpe := range v.URLs {
		if cpe != vv.URLs[idx] {
			return false
		}
	}
	for idx, item := range v.Cvss {
		if !reflect.DeepEqual(item, vv.Cvss[idx]) {
			return false
		}
	}

	return true
}

func NewMetadata(m *VulnerabilityMetadata) (*vulnerability.Metadata, error) {
	if m == nil {
		return nil, nil
	}
	return &vulnerability.Metadata{
		ID:          m.ID,
		DataSource:  m.DataSource,
		Namespace:   m.Namespace,
		Severity:    m.Severity,
		URLs:        m.URLs,
		Description: m.Description,
		Cvss:        NewCvss(m.Cvss),
	}, nil
}
