package v5

import (
	"sort"
	"strings"

	"github.com/anchore/grype/grype/db/v5/pkg/qualifier"
)

// Vulnerability represents the minimum data fields necessary to perform package-to-vulnerability matching. This can represent a CVE, 3rd party advisory, or any source that relates back to a CVE.
type Vulnerability struct {
	ID                     string                   `json:"id"`                      // The identifier of the vulnerability or advisory
	PackageName            string                   `json:"package_name"`            // The name of the package that is vulnerable
	Namespace              string                   `json:"namespace"`               // The ecosystem where the package resides
	PackageQualifiers      []qualifier.Qualifier    `json:"package_qualifiers"`      // The qualifiers for determining if a package is vulnerable
	VersionConstraint      string                   `json:"version_constraint"`      // The version range which the given package is vulnerable
	VersionFormat          string                   `json:"version_format"`          // The format which all version fields should be interpreted as
	CPEs                   []string                 `json:"cpes"`                    // The CPEs which are considered vulnerable
	RelatedVulnerabilities []VulnerabilityReference `json:"related_vulnerabilities"` // Other Vulnerabilities that are related to this one (e.g. GHSA relate to CVEs, or how distro CVE relates to NVD record)
	Fix                    Fix                      `json:"fix"`                     // All information about fixed versions
	Advisories             []Advisory               `json:"advisories"`              // Any vendor advisories about fixes or other notifications about this vulnerability
}

type VulnerabilityReference struct {
	ID        string `json:"id"`
	Namespace string `json:"namespace"`
}

//nolint:gocognit
func (v *Vulnerability) Equal(vv Vulnerability) bool {
	equal := v.ID == vv.ID &&
		v.PackageName == vv.PackageName &&
		v.Namespace == vv.Namespace &&
		len(v.PackageQualifiers) == len(vv.PackageQualifiers) &&
		v.VersionConstraint == vv.VersionConstraint &&
		v.VersionFormat == vv.VersionFormat &&
		len(v.CPEs) == len(vv.CPEs) &&
		len(v.RelatedVulnerabilities) == len(vv.RelatedVulnerabilities) &&
		len(v.Advisories) == len(vv.Advisories) &&
		v.Fix.State == vv.Fix.State &&
		len(v.Fix.Versions) == len(vv.Fix.Versions)

	if !equal {
		return false
	}

	sort.Strings(v.CPEs)
	sort.Strings(vv.CPEs)
	for idx, cpe := range v.CPEs {
		if cpe != vv.CPEs[idx] {
			return false
		}
	}

	sortedBaseRelVulns, sortedTargetRelVulns := sortRelatedVulns(v.RelatedVulnerabilities), sortRelatedVulns(vv.RelatedVulnerabilities)
	for idx, item := range sortedBaseRelVulns {
		if item != sortedTargetRelVulns[idx] {
			return false
		}
	}
	sortedBaseAdvisories, sortedTargetAdvisories := sortAdvisories(v.Advisories), sortAdvisories(vv.Advisories)
	for idx, item := range sortedBaseAdvisories {
		if item != sortedTargetAdvisories[idx] {
			return false
		}
	}
	sortedBasePkgQualifiers, sortedTargetPkgQualifiers := sortPackageQualifiers(v.PackageQualifiers), sortPackageQualifiers(vv.PackageQualifiers)
	for idx, item := range sortedBasePkgQualifiers {
		if item != sortedTargetPkgQualifiers[idx] {
			return false
		}
	}

	sort.Strings(v.Fix.Versions)
	sort.Strings(vv.Fix.Versions)
	for idx, item := range v.Fix.Versions {
		if item != vv.Fix.Versions[idx] {
			return false
		}
	}

	return true
}

func sortRelatedVulns(vulns []VulnerabilityReference) []VulnerabilityReference {
	sort.SliceStable(vulns, func(i, j int) bool {
		b1, b2 := strings.Builder{}, strings.Builder{}
		b1.WriteString(vulns[i].ID)
		b1.WriteString(vulns[i].Namespace)
		b2.WriteString(vulns[j].ID)
		b2.WriteString(vulns[j].Namespace)
		return b1.String() < b2.String()
	})
	return vulns
}

func sortAdvisories(advisories []Advisory) []Advisory {
	sort.SliceStable(advisories, func(i, j int) bool {
		b1, b2 := strings.Builder{}, strings.Builder{}
		b1.WriteString(advisories[i].ID)
		b1.WriteString(advisories[i].Link)
		b2.WriteString(advisories[j].ID)
		b2.WriteString(advisories[j].Link)
		return b1.String() < b2.String()
	})
	return advisories
}

func sortPackageQualifiers(qualifiers []qualifier.Qualifier) []qualifier.Qualifier {
	sort.SliceStable(qualifiers, func(i, j int) bool {
		return qualifiers[i].String() < qualifiers[j].String()
	})
	return qualifiers
}
