package db

import (
	"testing"

	"github.com/go-test/deep"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/anchore/grype/grype/distro"
	"github.com/anchore/grype/grype/pkg"
	"github.com/anchore/grype/grype/pkg/qualifier"
	"github.com/anchore/grype/grype/version"
	"github.com/anchore/grype/grype/vulnerability"
	syftPkg "github.com/anchore/syft/syft/pkg"
)

func TestGetByDistro(t *testing.T) {
	provider, err := NewVulnerabilityProvider(newMockStore())
	require.NoError(t, err)

	d, err := distro.New(distro.Debian, "8", "")
	if err != nil {
		t.Fatalf("failed to create distro: %+v", err)
	}

	p := pkg.Package{
		ID:   pkg.ID(uuid.NewString()),
		Name: "neutron",
	}

	actual, err := provider.GetByDistro(d, p)
	if err != nil {
		t.Fatalf("failed to get by distro: %+v", err)
	}

	expected := []vulnerability.Vulnerability{
		{
			Constraint:        version.MustGetConstraint("< 2014.1.3-6", version.DebFormat),
			ID:                "CVE-2014-fake-1",
			Namespace:         "debian:distro:debian:8",
			PackageQualifiers: []qualifier.Qualifier{},
			CPEs:              []syftPkg.CPE{},
			Advisories:        []vulnerability.Advisory{},
		},
		{
			Constraint:        version.MustGetConstraint("< 2013.0.2-1", version.DebFormat),
			ID:                "CVE-2013-fake-2",
			Namespace:         "debian:distro:debian:8",
			PackageQualifiers: []qualifier.Qualifier{},
			CPEs:              []syftPkg.CPE{},
			Advisories:        []vulnerability.Advisory{},
		},
	}

	assert.Len(t, actual, len(expected))

	for idx, vuln := range actual {
		for _, d := range deep.Equal(expected[idx], vuln) {
			t.Errorf("diff: %+v", d)
		}
	}
}

func TestGetByDistro_nilDistro(t *testing.T) {
	provider, err := NewVulnerabilityProvider(newMockStore())
	require.NoError(t, err)

	p := pkg.Package{
		ID:   pkg.ID(uuid.NewString()),
		Name: "neutron",
	}

	vulnerabilities, err := provider.GetByDistro(nil, p)

	assert.Empty(t, vulnerabilities)
	assert.NoError(t, err)
}

func must(c syftPkg.CPE, e error) syftPkg.CPE {
	if e != nil {
		panic(e)
	}
	return c
}

func TestGetByCPE(t *testing.T) {

	tests := []struct {
		name     string
		cpe      syftPkg.CPE
		expected []vulnerability.Vulnerability
		err      bool
	}{
		{
			name: "match from name and target SW",
			cpe:  must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:ruby:*:*")),
			expected: []vulnerability.Vulnerability{
				{
					Constraint: version.MustGetConstraint("< 3.7.4", version.UnknownFormat),
					ID:         "CVE-2014-fake-4",
					CPEs: []syftPkg.CPE{
						must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:*:*:something:*:*:ruby:*:*")),
					},
					Namespace:         "nvd:cpe",
					PackageQualifiers: []qualifier.Qualifier{},
					Advisories:        []vulnerability.Advisory{},
				},
			},
		},
		{
			name: "match with normalization",
			cpe:  must(syftPkg.NewCPE("cpe:2.3:*:ActiVERecord:ACTiveRecord:*:*:*:*:*:ruby:*:*")),
			expected: []vulnerability.Vulnerability{
				{
					Constraint: version.MustGetConstraint("< 3.7.4", version.UnknownFormat),
					ID:         "CVE-2014-fake-4",
					CPEs: []syftPkg.CPE{
						must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:*:*:something:*:*:ruby:*:*")),
					},
					Namespace:         "nvd:cpe",
					PackageQualifiers: []qualifier.Qualifier{},
					Advisories:        []vulnerability.Advisory{},
				},
			},
		},
		{
			name: "match from vendor & name",
			cpe:  must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:*:*:*")),
			expected: []vulnerability.Vulnerability{
				{
					Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
					ID:         "CVE-2014-fake-3",
					CPEs: []syftPkg.CPE{
						must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*")),
					},
					Namespace:         "nvd:cpe",
					PackageQualifiers: []qualifier.Qualifier{},
					Advisories:        []vulnerability.Advisory{},
				},
				{
					Constraint: version.MustGetConstraint("< 3.7.4", version.UnknownFormat),
					ID:         "CVE-2014-fake-4",
					CPEs: []syftPkg.CPE{
						must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:*:*:something:*:*:ruby:*:*")),
					},
					Namespace:         "nvd:cpe",
					PackageQualifiers: []qualifier.Qualifier{},
					Advisories:        []vulnerability.Advisory{},
				},
			},
		},

		{
			name: "dont allow any name",
			cpe:  must(syftPkg.NewCPE("cpe:2.3:*:couldntgetthisrightcouldyou:*:*:*:*:*:*:*:*:*")),
			err:  true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {

			provider, err := NewVulnerabilityProvider(newMockStore())
			require.NoError(t, err)

			actual, err := provider.GetByCPE(test.cpe)
			if err != nil && !test.err {
				t.Fatalf("expected no err, got: %+v", err)
			} else if err == nil && test.err {
				t.Fatalf("expected an err, got none")
			}

			assert.Len(t, actual, len(test.expected))

			for idx, vuln := range actual {
				for _, d := range deep.Equal(test.expected[idx], vuln) {
					t.Errorf("diff: %+v", d)
				}
			}
		})
	}

}
