package cyclonedx

import (
	"testing"

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

	"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 v31",
			input:    "3.1",
			expected: cyclonedx.ScoringMethodCVSSv31,
			errors:   false,
		},
		{
			desc:     "CVSS v3",
			input:    "3",
			expected: cyclonedx.ScoringMethodCVSSv3,
			errors:   false,
		},
		{
			desc:     "invalid (no match)",
			input:    "15.4",
			expected: cyclonedx.ScoringMethodOther,
			errors:   false,
		},
	}
	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)
			require.NoError(t, err)
			require.NotNil(t, actual.Ratings, "cyclonedx document ratings should not be nil")
			require.NotEmpty(t, actual.Ratings)
			require.Equal(t, cdxSeverityFromGrypeSeverity(test.metadataProvider.severity), (*actual.Ratings)[0].Severity)
			if len(test.metadataProvider.cvss) > 0 {
				for i, rating := range *actual.Ratings {
					require.Equal(t, test.metadataProvider.cvss[i].Metrics.BaseScore, *rating.Score)
				}
			}
		})
	}
}
