package v6

import (
	"encoding/hex"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	v5 "github.com/anchore/grype/grype/db/v5"
	"github.com/anchore/grype/grype/db/v5/namespace"
	distroNs "github.com/anchore/grype/grype/db/v5/namespace/distro"
	"github.com/anchore/grype/grype/db/v5/namespace/language"
	"github.com/anchore/grype/grype/vulnerability"
	"github.com/anchore/grype/internal/log"
	"github.com/anchore/syft/syft/cpe"
)

func testVulnerabilityProvider(t *testing.T) vulnerability.Provider {
	t.Helper()
	tmp := t.TempDir()
	w, err := NewWriter(Config{
		DBDirPath: tmp,
	})
	defer log.CloseAndLogError(w, tmp)
	require.NoError(t, err)

	aDayAgo := time.Now().Add(-1 * 24 * time.Hour)
	aWeekAgo := time.Now().Add(-7 * 24 * time.Hour)
	twoWeeksAgo := time.Now().Add(-14 * 24 * time.Hour)

	debianProvider := &Provider{
		ID:           "debian",
		Version:      "1",
		Processor:    "debian-processor",
		DateCaptured: &aDayAgo,
		InputDigest:  hex.EncodeToString([]byte("debian")),
	}

	nvdProvider := &Provider{
		ID:           "nvd",
		Version:      "1",
		Processor:    "nvd-processor",
		DateCaptured: &aDayAgo,
		InputDigest:  hex.EncodeToString([]byte("nvd")),
	}

	v5vulns := []v5.Vulnerability{
		// neutron
		{
			PackageName:       "neutron",
			Namespace:         "debian:distro:debian:8",
			VersionConstraint: "< 2014.1.3-6",
			ID:                "CVE-2014-fake-1",
			VersionFormat:     "deb",
		},
		{
			PackageName:       "neutron",
			Namespace:         "debian:distro:debian:8",
			VersionConstraint: "< 2013.0.2-1",
			ID:                "CVE-2013-fake-2",
			VersionFormat:     "deb",
		},
		// poison the well! this is not a valid entry, but we want the matching process to survive and find other good results...
		{
			PackageName:       "neutron",
			Namespace:         "debian:distro:debian:8",
			VersionConstraint: "< fred70.3.0-rc0", // intentionally bad value
			ID:                "CVE-2014-fake-3",
			VersionFormat:     "apk",
		},

		// activerecord
		{
			PackageName:       "activerecord",
			Namespace:         "nvd:cpe",
			VersionConstraint: "< 3.7.6",
			ID:                "CVE-2014-fake-3",
			VersionFormat:     "unknown",
			CPEs: []string{
				"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
			},
		},
		{
			PackageName:       "activerecord",
			Namespace:         "nvd:cpe",
			VersionConstraint: "< 3.7.4",
			ID:                "CVE-2014-fake-4",
			VersionFormat:     "unknown",
			CPEs: []string{
				"cpe:2.3:*:activerecord:activerecord:*:*:something:*:*:ruby:*:*",
			},
		},
		{
			PackageName:       "activerecord",
			Namespace:         "nvd:cpe",
			VersionConstraint: "= 4.0.1",
			ID:                "CVE-2014-fake-5",
			VersionFormat:     "unknown",
			CPEs: []string{
				"cpe:2.3:*:couldntgetthisrightcouldyou:activerecord:4.0.1:*:*:*:*:*:*:*", // shouldn't match on this
			},
		},
		{
			PackageName:       "activerecord",
			Namespace:         "nvd:cpe",
			VersionConstraint: "< 98SP3",
			ID:                "CVE-2014-fake-6",
			VersionFormat:     "unknown",
			CPEs: []string{
				"cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*", // shouldn't match on this
			},
		},
		{
			PackageName:       "Newtonsoft.Json",
			Namespace:         "github:language:dotnet",
			ID:                "GHSA-5crp-9r3c-p9vr",
			VersionFormat:     "unknown",
			VersionConstraint: "<13.0.1",
		},
		// poison the well! this is not a valid entry, but we want the matching process to survive and find other good results...
		{
			PackageName:       "activerecord",
			Namespace:         "nvd:cpe",
			VersionConstraint: "< 70.3.0-rc0", // intentionally bad value
			ID:                "CVE-2014-fake-7",
			VersionFormat:     "apk",
			CPEs: []string{
				"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
			},
		},
	}

	for _, v := range v5vulns {
		var os *OperatingSystem
		prov := nvdProvider

		switch v.Namespace {
		case "nvd:cpe":
		case "debian:distro:debian:8":
			prov = debianProvider
			os = &OperatingSystem{
				Name:         "debian",
				MajorVersion: "8",
			}
		}

		vuln := &VulnerabilityHandle{
			ID:            0,
			Name:          v.ID,
			Status:        "",
			PublishedDate: &twoWeeksAgo,
			ModifiedDate:  &aWeekAgo,
			WithdrawnDate: nil,
			ProviderID:    prov.ID,
			Provider:      prov,
			BlobID:        0,
			BlobValue: &VulnerabilityBlob{
				ID:          v.ID,
				Assigners:   []string{v.ID + "-assigner-1", v.ID + "-assigner-2"},
				Description: v.ID + "-description",
				References: []Reference{
					{
						URL:  "http://somewhere/" + v.ID,
						Tags: []string{v.ID + "-tag-1", v.ID + "-tag-2"},
					},
				},
				//Aliases: []string{"GHSA-" + v.ID},
				Severities: []Severity{
					{
						Scheme: SeveritySchemeCVSS,
						Value:  "high",
						Source: "",
						Rank:   0,
					},
				},
			},
		}

		err = w.AddVulnerabilities(vuln)
		require.NoError(t, err)

		var cpes []Cpe
		for _, c := range v.CPEs {
			cp, err := cpe.New(c, "")
			require.NoError(t, err)
			cpes = append(cpes, Cpe{
				Part:            cp.Attributes.Part,
				Vendor:          cp.Attributes.Vendor,
				Product:         cp.Attributes.Product,
				Edition:         cp.Attributes.Edition,
				Language:        cp.Attributes.Language,
				SoftwareEdition: cp.Attributes.SWEdition,
				TargetHardware:  cp.Attributes.TargetHW,
				TargetSoftware:  cp.Attributes.TargetSW,
				Other:           cp.Attributes.Other,
			})
		}

		packageType := ""

		ns, err := namespace.FromString(v.Namespace)
		require.NoError(t, err)

		d, _ := ns.(*distroNs.Namespace)
		if d != nil {
			packageType = string(d.DistroType())
		}
		lang, _ := ns.(*language.Namespace)
		if lang != nil {
			packageType = string(lang.Language())
		}

		pkg := &Package{
			ID:        0,
			Ecosystem: packageType,
			Name:      v.PackageName,
			//CPEs: cpes,
		}

		ap := &AffectedPackageHandle{
			ID:                0,
			VulnerabilityID:   0,
			Vulnerability:     vuln,
			OperatingSystemID: nil,
			OperatingSystem:   os,
			PackageID:         0,
			Package:           pkg,
			BlobID:            0,
			BlobValue: &PackageBlob{
				CVEs:       nil,
				Qualifiers: nil,
				Ranges: []Range{
					{
						Fix: nil,
						Version: Version{
							Type:       v.VersionFormat,
							Constraint: v.VersionConstraint,
						},
					},
				},
			},
		}

		err = w.AddAffectedPackages(ap)
		require.NoError(t, err)

		for _, c := range cpes {
			ac := &AffectedCPEHandle{
				Vulnerability: vuln,
				CPE:           &c,
				BlobValue: &PackageBlob{
					Ranges: []Range{
						{
							Version: Version{
								Type:       v.VersionFormat,
								Constraint: v.VersionConstraint,
							},
						},
					},
				},
			}

			err = w.AddAffectedCPEs(ac)
			require.NoError(t, err)
		}
	}

	// add unaffected packages for testing unaffected stores
	unaffectedVuln := &VulnerabilityHandle{
		ID:            0,
		Name:          "CVE-2024-unaffected-test",
		Status:        "",
		PublishedDate: &twoWeeksAgo,
		ModifiedDate:  &aWeekAgo,
		WithdrawnDate: nil,
		ProviderID:    nvdProvider.ID,
		Provider:      nvdProvider,
		BlobID:        0,
		BlobValue: &VulnerabilityBlob{
			ID:          "CVE-2024-unaffected-test",
			Assigners:   []string{"CVE-2024-unaffected-test-assigner-1"},
			Description: "CVE-2024-unaffected-test-description",
			References: []Reference{
				{
					URL:  "http://somewhere/CVE-2024-unaffected-test",
					Tags: []string{"CVE-2024-unaffected-test-tag-1"},
				},
			},
			Severities: []Severity{
				{
					Scheme: SeveritySchemeCVSS,
					Value:  "medium",
					Source: "",
					Rank:   0,
				},
			},
		},
	}

	err = w.AddVulnerabilities(unaffectedVuln)
	require.NoError(t, err)

	// add unaffected package: test-unaffected-package
	testUnaffectedPkg := &Package{
		ID:        0,
		Ecosystem: "deb",
		Name:      "test-unaffected-package",
	}

	testUnaffectedPackageHandle := &UnaffectedPackageHandle{
		ID:                0,
		VulnerabilityID:   0,
		Vulnerability:     unaffectedVuln,
		OperatingSystemID: nil,
		OperatingSystem:   nil,
		PackageID:         0,
		Package:           testUnaffectedPkg,
		BlobID:            0,
		BlobValue: &PackageBlob{
			CVEs:       nil,
			Qualifiers: nil,
			Ranges: []Range{
				{
					Fix: nil,
					Version: Version{
						Type:       "deb",
						Constraint: "< 1.0.0",
					},
				},
			},
		},
	}

	err = w.AddUnaffectedPackages(testUnaffectedPackageHandle)
	require.NoError(t, err)

	// add unaffected CPE
	testUnaffectedCPE, err := cpe.New("cpe:2.3:a:test:unaffected:*:*:*:*:*:*:*:*", "")
	require.NoError(t, err)

	testUnaffectedCPEModel := Cpe{
		Part:            testUnaffectedCPE.Attributes.Part,
		Vendor:          testUnaffectedCPE.Attributes.Vendor,
		Product:         testUnaffectedCPE.Attributes.Product,
		Edition:         testUnaffectedCPE.Attributes.Edition,
		Language:        testUnaffectedCPE.Attributes.Language,
		SoftwareEdition: testUnaffectedCPE.Attributes.SWEdition,
		TargetHardware:  testUnaffectedCPE.Attributes.TargetHW,
		TargetSoftware:  testUnaffectedCPE.Attributes.TargetSW,
		Other:           testUnaffectedCPE.Attributes.Other,
	}

	testUnaffectedCPEHandle := &UnaffectedCPEHandle{
		ID:              0,
		VulnerabilityID: 0,
		Vulnerability:   unaffectedVuln,
		CpeID:           0,
		CPE:             &testUnaffectedCPEModel,
		BlobID:          0,
		BlobValue: &PackageBlob{
			CVEs:       nil,
			Qualifiers: nil,
			Ranges: []Range{
				{
					Fix: nil,
					Version: Version{
						Type:       "unknown",
						Constraint: "< 1.0.0",
					},
				},
			},
		},
	}

	err = w.AddUnaffectedCPEs(testUnaffectedCPEHandle)
	require.NoError(t, err)

	// add unaffected neutron package for distro test
	neutronUnaffectedPkg := &Package{
		ID:        0,
		Ecosystem: "deb",
		Name:      "neutron",
	}

	neutronUnaffectedHandle := &UnaffectedPackageHandle{
		ID:                0,
		VulnerabilityID:   0,
		Vulnerability:     unaffectedVuln,
		OperatingSystemID: nil,
		OperatingSystem: &OperatingSystem{
			Name:         "debian",
			MajorVersion: "8",
		},
		PackageID: 0,
		Package:   neutronUnaffectedPkg,
		BlobID:    0,
		BlobValue: &PackageBlob{
			CVEs:       nil,
			Qualifiers: nil,
			Ranges: []Range{
				{
					Fix: nil,
					Version: Version{
						Type:       "deb",
						Constraint: ">= 2015.0.0",
					},
				},
			},
		},
	}

	err = w.AddUnaffectedPackages(neutronUnaffectedHandle)
	require.NoError(t, err)

	return NewVulnerabilityProvider(setupReadOnlyTestStore(t, tmp))
}
