package cyclonedxvex

import (
	"strings"
	"testing"

	"github.com/CycloneDX/cyclonedx-go"
	"github.com/stretchr/testify/assert"

	grypeDb "github.com/anchore/grype/grype/db/v5"
	"github.com/anchore/grype/grype/match"
	"github.com/anchore/grype/grype/pkg"
	"github.com/anchore/grype/grype/vulnerability"
)

func TestCvssVersionToMethod(t *testing.T) {
	testCases := []struct {
		desc     string
		input    string
		expected cyclonedx.ScoringMethod
		errors   bool
	}{
		{
			desc:     "invalid (not float)",
			input:    "",
			expected: "",
			errors:   true,
		},
		{
			desc:     "CVSS v2",
			input:    "2.0",
			expected: cyclonedx.ScoringMethodCVSSv2,
			errors:   false,
		},
		{
			desc:     "CVSS v3",
			input:    "3.1",
			expected: cyclonedx.ScoringMethodCVSSv31,
			errors:   false,
		},
		{
			desc:     "CVSS v3",
			input:    "3.0",
			expected: cyclonedx.ScoringMethodCVSSv3,
			errors:   false,
		},
		{
			desc:     "invalid (no match)",
			input:    "15.4",
			expected: "",
			errors:   true,
		},
	}
	for _, tc := range testCases {
		t.Run(tc.desc, func(t *testing.T) {
			actual, err := cvssVersionToMethod(tc.input)
			if !tc.errors {
				assert.NoError(t, err)
			} else {
				assert.Error(t, err)
			}

			assert.Equal(t, tc.expected, actual)
		})
	}
}

type metadataProvider struct {
	severity string
	cvss     []vulnerability.Cvss
}

func (m metadataProvider) GetMetadata(id, namespace string) (*vulnerability.Metadata, error) {
	return &vulnerability.Metadata{
		ID:          id,
		DataSource:  "",
		Namespace:   namespace,
		Severity:    m.severity,
		URLs:        nil,
		Description: "",
		Cvss:        m.cvss,
	}, nil
}

func TestNewVulnerability_AlwaysIncludesSeverity(t *testing.T) {
	tests := []struct {
		name             string
		match            match.Match
		metadataProvider *metadataProvider
	}{
		{
			name: "populates severity with missing CVSS records",
			match: match.Match{
				Vulnerability: vulnerability.Vulnerability{},
				Package:       pkg.Package{},
				Details:       nil,
			},
			metadataProvider: &metadataProvider{
				severity: "High",
			},
		},
		{
			name: "populates severity with all CVSS records",
			match: match.Match{
				Vulnerability: vulnerability.Vulnerability{},
				Package:       pkg.Package{},
				Details:       nil,
			},
			metadataProvider: &metadataProvider{
				severity: "High",
				cvss: []vulnerability.Cvss{
					{
						Version: "2.0",
						Metrics: vulnerability.CvssMetrics{
							BaseScore: 1.1,
						},
					},
					{
						Version: "3.0",
						Metrics: vulnerability.CvssMetrics{
							BaseScore: 2.1,
						},
					},
					{
						Version: "3.1",
						Metrics: vulnerability.CvssMetrics{
							BaseScore: 3.1,
						},
					},
				},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actual, err := NewVulnerability(test.match, test.metadataProvider)
			assert.NoError(t, err)
			if len(*actual.Ratings) == 0 {
				t.Fatalf("expected a rating but found none")
			}
			assert.Equal(t, string((*actual.Ratings)[0].Severity), strings.ToLower(test.metadataProvider.severity))
			for _, r := range (*actual.Ratings)[1:] {
				assert.Equal(t, string(r.Severity), "")
			}
		})
	}
}

func TestNewVulnerability_AddsFixedVersion(t *testing.T) {
	tests := []struct {
		name             string
		match            match.Match
		metadataProvider *metadataProvider
		expected         *[]cyclonedx.Property
	}{
		{
			name: "No known fixed version",
			match: match.Match{
				Vulnerability: vulnerability.Vulnerability{
					Fix: vulnerability.Fix{
						State:    grypeDb.NotFixedState,
						Versions: []string{},
					},
				},
				Package: pkg.Package{},
				Details: nil,
			},
			metadataProvider: &metadataProvider{},
			expected:         &[]cyclonedx.Property{},
		},
		{
			name: "Multiple known fixed versions",
			match: match.Match{
				Vulnerability: vulnerability.Vulnerability{
					Fix: vulnerability.Fix{
						State: grypeDb.FixedState,
						Versions: []string{
							"v0.1.2",
							"v1.3.7",
						},
					},
				},
				Package: pkg.Package{},
				Details: nil,
			},
			metadataProvider: &metadataProvider{},
			expected: &[]cyclonedx.Property{
				{
					Name:  "grype:fixed_versions",
					Value: "v0.1.2,v1.3.7",
				},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actual, err := NewVulnerability(test.match, test.metadataProvider)

			assert.NoError(t, err)
			assert.Equal(t, test.expected, actual.Properties)
		})
	}
}
