package spdxhelpers

import (
	"testing"

	"github.com/spdx/tools-golang/spdx"
	"github.com/spdx/tools-golang/spdx/v2/common"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/anchore/syft/syft/artifact"
	"github.com/anchore/syft/syft/file"
	"github.com/anchore/syft/syft/pkg"
	"github.com/anchore/syft/syft/source"
)

func TestToSyftModel(t *testing.T) {
	sbom, err := ToSyftModel(&spdx.Document{
		SPDXVersion:                "1",
		DataLicense:                "GPL",
		SPDXIdentifier:             "id-doc-1",
		DocumentName:               "docName",
		DocumentNamespace:          "docNamespace",
		ExternalDocumentReferences: nil,
		DocumentComment:            "",
		CreationInfo: &spdx.CreationInfo{
			LicenseListVersion: "",
			Created:            "",
			CreatorComment:     "",
		},
		Packages: []*spdx.Package{
			{
				PackageName:            "pkg-1",
				PackageSPDXIdentifier:  "id-pkg-1",
				PackageVersion:         "5.4.3",
				PackageLicenseDeclared: "",
				PackageDescription:     "",
				PackageExternalReferences: []*spdx.PackageExternalReference{
					{
						Category: "SECURITY",
						Locator:  "cpe:2.3:a:pkg-1:pkg-1:5.4.3:*:*:*:*:*:*:*",
						RefType:  "cpe23Type",
					},
					{
						Category: "SECURITY",
						Locator:  "cpe:2.3:a:pkg_1:pkg_1:5.4.3:*:*:*:*:*:*:*",
						RefType:  "cpe23Type",
					},
					{
						Category: "PACKAGE-MANAGER",
						Locator:  "pkg:apk/alpine/pkg-1@5.4.3?arch=x86_64&upstream=p1-origin&distro=alpine-3.10.9",
						RefType:  "purl",
					},
				},
				Files: nil,
			},
			{
				PackageName:            "pkg-2",
				PackageSPDXIdentifier:  "id-pkg-2",
				PackageVersion:         "7.3.1",
				PackageLicenseDeclared: "",
				PackageDescription:     "",
				PackageExternalReferences: []*spdx.PackageExternalReference{
					{
						Category: "SECURITY",
						Locator:  "cpe:2.3:a:pkg-2:pkg-2:7.3.1:*:*:*:*:*:*:*",
						RefType:  "cpe23Type",
					},
					{
						Category: "SECURITY",
						Locator:  "cpe:2.3:a:pkg_2:pkg_2:7.3.1:*:*:*:*:*:*:*",
						RefType:  "cpe23Type",
					},
					{
						Category: "SECURITY",
						Locator:  "cpe:2.3:a:pkg-2:pkg_2:7.3.1:*:*:*:*:*:*:*",
						RefType:  "cpe23Type",
					},
					{
						Category: "PACKAGE-MANAGER",
						Locator:  "pkg:deb/pkg-2@7.3.1?arch=x86_64&upstream=p2-origin@9.1.3&distro=debian-3.10.9",
						RefType:  "purl",
					},
				},
				Files: nil,
			},
		},
		Relationships: []*spdx.Relationship{},
	})

	assert.NoError(t, err)

	assert.NotNil(t, sbom)

	pkgs := sbom.Artifacts.Packages.Sorted()

	assert.Len(t, pkgs, 2)

	p1 := pkgs[0]
	assert.Equal(t, p1.Name, "pkg-1")
	assert.Equal(t, p1.MetadataType, pkg.ApkMetadataType)
	p1meta := p1.Metadata.(pkg.ApkMetadata)
	assert.Equal(t, p1meta.OriginPackage, "p1-origin")
	assert.Len(t, p1.CPEs, 2)

	p2 := pkgs[1]
	assert.Equal(t, p2.Name, "pkg-2")
	assert.Equal(t, p2.MetadataType, pkg.DpkgMetadataType)
	p2meta := p2.Metadata.(pkg.DpkgMetadata)
	assert.Equal(t, p2meta.Source, "p2-origin")
	assert.Equal(t, p2meta.SourceVersion, "9.1.3")
	assert.Len(t, p2.CPEs, 3)
}

func Test_extractMetadata(t *testing.T) {
	oneTwoThreeFour := 1234
	tests := []struct {
		pkg      spdx.Package
		metaType pkg.MetadataType
		meta     interface{}
	}{
		{
			pkg: spdx.Package{
				PackageName:    "SomeDebPkg",
				PackageVersion: "43.1.235",
				PackageExternalReferences: []*spdx.PackageExternalReference{
					{
						Category: "PACKAGE-MANAGER",
						Locator:  "pkg:deb/pkg-2@7.3.1?arch=x86_64&upstream=somedebpkg-origin@9.1.3&distro=debian-3.10.9",
						RefType:  "purl",
					},
				},
			},
			metaType: pkg.DpkgMetadataType,
			meta: pkg.DpkgMetadata{
				Package:       "SomeDebPkg",
				Source:        "somedebpkg-origin",
				Version:       "43.1.235",
				SourceVersion: "9.1.3",
				Architecture:  "x86_64",
			},
		},
		{
			pkg: spdx.Package{
				PackageName:    "SomeApkPkg",
				PackageVersion: "3.2.9",
				PackageExternalReferences: []*spdx.PackageExternalReference{
					{
						Category: "PACKAGE-MANAGER",
						Locator:  "pkg:apk/alpine/pkg-2@7.3.1?arch=x86_64&upstream=apk-origin@9.1.3&distro=alpine-3.10.9",
						RefType:  "purl",
					},
				},
			},
			metaType: pkg.ApkMetadataType,
			meta: pkg.ApkMetadata{
				Package:       "SomeApkPkg",
				OriginPackage: "apk-origin",
				Version:       "3.2.9",
				Architecture:  "x86_64",
			},
		},
		{
			pkg: spdx.Package{
				PackageName:    "SomeRpmPkg",
				PackageVersion: "13.2.79",
				PackageExternalReferences: []*spdx.PackageExternalReference{
					{
						Category: "PACKAGE-MANAGER",
						Locator:  "pkg:rpm/pkg-2@7.3.1?arch=x86_64&epoch=1234&upstream=some-rpm-origin-1.16.3&distro=alpine-3.10.9",
						RefType:  "purl",
					},
				},
			},
			metaType: pkg.RpmMetadataType,
			meta: pkg.RpmMetadata{
				Name:      "SomeRpmPkg",
				Version:   "13.2.79",
				Epoch:     &oneTwoThreeFour,
				Arch:      "x86_64",
				Release:   "",
				SourceRpm: "some-rpm-origin-1.16.3",
			},
		},
	}

	for _, test := range tests {
		t.Run(test.pkg.PackageName, func(t *testing.T) {
			info := extractPkgInfo(&test.pkg)
			metaType, meta := extractMetadata(&test.pkg, info)
			assert.Equal(t, test.metaType, metaType)
			assert.EqualValues(t, test.meta, meta)
		})
	}
}

func TestExtractSourceFromNamespaces(t *testing.T) {
	tests := []struct {
		namespace string
		expected  source.Scheme
	}{
		{
			namespace: "https://anchore.com/syft/file/d42b01d0-7325-409b-b03f-74082935c4d3",
			expected:  source.FileScheme,
		},
		{
			namespace: "https://anchore.com/syft/image/d42b01d0-7325-409b-b03f-74082935c4d3",
			expected:  source.ImageScheme,
		},
		{
			namespace: "https://anchore.com/syft/dir/d42b01d0-7325-409b-b03f-74082935c4d3",
			expected:  source.DirectoryScheme,
		},
		{
			namespace: "https://another-host/blob/123",
			expected:  source.UnknownScheme,
		},
		{
			namespace: "bla bla",
			expected:  source.UnknownScheme,
		},
		{
			namespace: "",
			expected:  source.UnknownScheme,
		},
	}

	for _, tt := range tests {
		require.Equal(t, tt.expected, extractSchemeFromNamespace(tt.namespace))
	}
}

func TestH1Digest(t *testing.T) {
	tests := []struct {
		name           string
		pkg            spdx.Package
		expectedDigest string
	}{
		{
			name: "valid h1digest",
			pkg: spdx.Package{
				PackageName:    "github.com/googleapis/gnostic",
				PackageVersion: "v0.5.5",
				PackageExternalReferences: []*spdx.PackageExternalReference{
					{
						Category: "PACKAGE-MANAGER",
						Locator:  "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
						RefType:  "purl",
					},
				},
				PackageChecksums: []spdx.Checksum{
					{
						Algorithm: spdx.SHA256,
						Value:     "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
					},
				},
			},
			expectedDigest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
		},
		{
			name: "invalid h1digest algorithm",
			pkg: spdx.Package{
				PackageName:    "github.com/googleapis/gnostic",
				PackageVersion: "v0.5.5",
				PackageExternalReferences: []*spdx.PackageExternalReference{
					{
						Category: "PACKAGE-MANAGER",
						Locator:  "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
						RefType:  "purl",
					},
				},
				PackageChecksums: []spdx.Checksum{
					{
						Algorithm: spdx.SHA1,
						Value:     "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
					},
				},
			},
			expectedDigest: "",
		},
		{
			name: "invalid h1digest digest",
			pkg: spdx.Package{
				PackageName:    "github.com/googleapis/gnostic",
				PackageVersion: "v0.5.5",
				PackageExternalReferences: []*spdx.PackageExternalReference{
					{
						Category: "PACKAGE-MANAGER",
						Locator:  "pkg:golang/github.com/googleapis/gnostic@v0.5.5",
						RefType:  "purl",
					},
				},
				PackageChecksums: []spdx.Checksum{
					{
						Algorithm: spdx.SHA256,
						Value:     "",
					},
				},
			},
			expectedDigest: "",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			p := toSyftPackage(&test.pkg)
			require.Equal(t, pkg.GolangBinMetadataType, p.MetadataType)
			meta := p.Metadata.(pkg.GolangBinMetadata)
			require.Equal(t, test.expectedDigest, meta.H1Digest)
		})
	}
}

func Test_toSyftRelationships(t *testing.T) {
	type args struct {
		spdxIDMap map[string]interface{}
		doc       *spdx.Document
	}

	pkg1 := pkg.Package{
		Name:    "github.com/googleapis/gnostic",
		Version: "v0.5.5",
	}
	pkg1.SetID()

	pkg2 := pkg.Package{
		Name:    "rfc3339",
		Version: "1.2",
		Type:    pkg.RpmPkg,
	}
	pkg2.SetID()

	pkg3 := pkg.Package{
		Name:    "rfc3339",
		Version: "1.2",
		Type:    pkg.PythonPkg,
	}
	pkg3.SetID()

	loc1 := file.NewLocationFromCoordinates(file.Coordinates{
		RealPath:     "/somewhere/real",
		FileSystemID: "abc",
	})

	tests := []struct {
		name string
		args args
		want []artifact.Relationship
	}{
		{
			name: "evident-by relationship",
			args: args{
				spdxIDMap: map[string]interface{}{
					string(toSPDXID(pkg1)): &pkg1,
					string(toSPDXID(loc1)): &loc1,
				},
				doc: &spdx.Document{
					Relationships: []*spdx.Relationship{
						{
							RefA: common.DocElementID{
								ElementRefID: toSPDXID(pkg1),
							},
							RefB: common.DocElementID{
								ElementRefID: toSPDXID(loc1),
							},
							Relationship:        spdx.RelationshipOther,
							RelationshipComment: "evident-by: indicates the package's existence is evident by the given file",
						},
					},
				},
			},
			want: []artifact.Relationship{
				{
					From: pkg1,
					To:   loc1,
					Type: artifact.EvidentByRelationship,
				},
			},
		},
		{
			name: "ownership-by-file-overlap relationship",
			args: args{
				spdxIDMap: map[string]interface{}{
					string(toSPDXID(pkg2)): &pkg2,
					string(toSPDXID(pkg3)): &pkg3,
				},
				doc: &spdx.Document{
					Relationships: []*spdx.Relationship{
						{
							RefA: common.DocElementID{
								ElementRefID: toSPDXID(pkg2),
							},
							RefB: common.DocElementID{
								ElementRefID: toSPDXID(pkg3),
							},
							Relationship:        spdx.RelationshipOther,
							RelationshipComment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
						},
					},
				},
			},
			want: []artifact.Relationship{
				{
					From: pkg2,
					To:   pkg3,
					Type: artifact.OwnershipByFileOverlapRelationship,
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			actual := toSyftRelationships(tt.args.spdxIDMap, tt.args.doc)
			require.Len(t, actual, len(tt.want))
			for i := range actual {
				require.Equal(t, tt.want[i].From.ID(), actual[i].From.ID())
				require.Equal(t, tt.want[i].To.ID(), actual[i].To.ID())
				require.Equal(t, tt.want[i].Type, actual[i].Type)
			}
		})
	}
}
