package engine

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"testing"

	"github.com/containerd/containerd/pkg/cri/util"

	"github.com/Checkmarx/kics/v2/pkg/detector/docker"
	"github.com/Checkmarx/kics/v2/pkg/detector/helm"

	"github.com/stretchr/testify/require"

	"github.com/Checkmarx/kics/v2/internal/tracker"
	"github.com/Checkmarx/kics/v2/pkg/detector"
	"github.com/Checkmarx/kics/v2/pkg/model"
	"github.com/Checkmarx/kics/v2/pkg/utils"
)

type vbArgs struct {
	ctx     *QueryContext
	v       interface{}
	tracker Tracker
}

// TestDefaultVulnerabilityBuilder tests the functions [DefaultVulnerabilityBuilder] and all the methods called by them
func TestDefaultVulnerabilityBuilder(t *testing.T) {
	var vbTests = []struct {
		name                string
		args                vbArgs
		want                model.Vulnerability
		useNewVulnerability bool
		wantErr             bool
		kicsComputeNewSimID bool
	}{
		{
			name: "DefaultVulnerabilityBuilder",
			args: vbArgs{
				tracker: &tracker.CITracker{},
				ctx: &QueryContext{
					scanID: "ScanID",
					Query: &PreparedQuery{
						Metadata: model.QueryMetadata{
							Metadata: map[string]interface{}{
								"key":         "123",
								"severity":    model.SeverityInfo,
								"oldSeverity": model.SeverityCritical,
								"issueType":   "IncorrectValue",
								"searchKey":   "testSearchKey",
							},
							Query:     "TestQuery",
							CWE:       "22",
							RiskScore: "0",
						},
					},
					Files: map[string]model.FileMetadata{
						"testV": {LinesOriginalData: &[]string{}},
					},
				},
				v: map[string]interface{}{
					"documentId": "testV",
				},
			},
			want: model.Vulnerability{
				ID:               0,
				SimilarityID:     "2fefa27cc667decf203d10f103b7ffdec232e9af16e361f47d626e72c72b8d63",
				ScanID:           "ScanID",
				FileID:           "",
				FileName:         "",
				DescriptionID:    "Undefined",
				CWE:              "22",
				RiskScore:        "0",
				QueryID:          "Undefined",
				QueryName:        "Anonymous",
				QueryURI:         "https://github.com/Checkmarx/kics/",
				Severity:         model.SeverityInfo,
				Line:             1,
				SearchLine:       -1,
				VulnLines:        &[]model.CodeLine{},
				IssueType:        "IncorrectValue",
				SearchKey:        "testSearchKey",
				KeyActualValue:   "",
				KeyExpectedValue: "",
				Value:            nil,
				Output:           `{"documentId":"testV","issueType":"IncorrectValue","key":"123","oldSeverity":"CRITICAL","searchKey":"testSearchKey","severity":"INFO"}`,
			},
			wantErr:             false,
			kicsComputeNewSimID: true,
		},
		{
			name: "DefaultVulnerabilityBuilder with override for severity",
			args: vbArgs{
				tracker: &tracker.CITracker{},
				ctx: &QueryContext{
					scanID: "ScanID",
					Query: &PreparedQuery{
						Metadata: model.QueryMetadata{
							Metadata: map[string]interface{}{
								"key":         "123",
								"severity":    model.SeverityInfo,
								"issueType":   "IncorrectValue",
								"searchKey":   "testSearchKey",
								"overrideKey": "testOverride",
								"override": map[string]interface{}{
									"testOverride": map[string]interface{}{
										"severity": model.SeverityHigh,
									},
								},
							},
							Query:     "TestQuery",
							CWE:       "22",
							RiskScore: "0",
						},
					},
					Files: map[string]model.FileMetadata{
						"testV": {LinesOriginalData: &[]string{}},
					},
				},
				v: map[string]interface{}{
					"documentId": "testV",
				},
			},
			want: model.Vulnerability{
				ID:               0,
				SimilarityID:     "2fefa27cc667decf203d10f103b7ffdec232e9af16e361f47d626e72c72b8d63",
				ScanID:           "ScanID",
				FileID:           "",
				FileName:         "",
				DescriptionID:    "Undefined",
				CWE:              "22",
				RiskScore:        "0",
				QueryID:          "Undefined",
				QueryName:        "Anonymous",
				QueryURI:         "https://github.com/Checkmarx/kics/",
				Severity:         model.SeverityHigh,
				Line:             1,
				SearchLine:       -1,
				VulnLines:        &[]model.CodeLine{},
				IssueType:        "IncorrectValue",
				SearchKey:        "testSearchKey",
				KeyActualValue:   "",
				KeyExpectedValue: "",
				Value:            nil,
				Output:           `{"documentId":"testV","issueType":"IncorrectValue","key":"123","override":{"testOverride":{"severity":"HIGH"}},"overrideKey":"testOverride","searchKey":"testSearchKey","severity":"INFO"}`, //nolint
			},
			wantErr:             false,
			kicsComputeNewSimID: true,
		},
		{
			name: "DefaultVulnerabilityBuilder with override for name",
			args: vbArgs{
				tracker: &tracker.CITracker{},
				ctx: &QueryContext{
					scanID: "ScanID",
					Query: &PreparedQuery{
						Metadata: model.QueryMetadata{
							Metadata: map[string]interface{}{
								"key":         "123",
								"severity":    model.SeverityInfo,
								"issueType":   "IncorrectValue",
								"searchKey":   "testSearchKey",
								"overrideKey": "testOverride",
								"queryName":   "test",
								"override": map[string]interface{}{
									"testOverride": map[string]interface{}{
										"queryName": "testName",
									},
								},
							},
							Query:     "TestQuery",
							CWE:       "22",
							RiskScore: "0",
						},
					},
					Files: map[string]model.FileMetadata{
						"testV": {LinesOriginalData: &[]string{}},
					},
				},
				v: map[string]interface{}{
					"documentId": "testV",
				},
			},
			want: model.Vulnerability{
				ID:               0,
				SimilarityID:     "2fefa27cc667decf203d10f103b7ffdec232e9af16e361f47d626e72c72b8d63",
				ScanID:           "ScanID",
				FileID:           "",
				FileName:         "",
				DescriptionID:    "Undefined",
				CWE:              "22",
				RiskScore:        "0",
				QueryID:          "Undefined",
				QueryName:        "testName",
				QueryURI:         "https://github.com/Checkmarx/kics/",
				Severity:         model.SeverityInfo,
				Line:             1,
				SearchLine:       -1,
				VulnLines:        &[]model.CodeLine{},
				IssueType:        "IncorrectValue",
				SearchKey:        "testSearchKey",
				KeyActualValue:   "",
				KeyExpectedValue: "",
				Value:            nil,
				Output:           `{"documentId":"testV","issueType":"IncorrectValue","key":"123","override":{"testOverride":{"queryName":"testName"}},"overrideKey":"testOverride","queryName":"test","searchKey":"testSearchKey","severity":"INFO"}`, //nolint
			},
			wantErr:             false,
			kicsComputeNewSimID: true,
		},
		{
			name: "DefaultVulnerabilityBuilder with new Severity",
			args: vbArgs{
				tracker: &tracker.CITracker{},
				ctx: &QueryContext{
					scanID: "ScanID",
					Query: &PreparedQuery{
						Metadata: model.QueryMetadata{
							Metadata: map[string]interface{}{
								"key":         "123",
								"severity":    model.SeverityInfo,
								"oldSeverity": model.SeverityCritical,
								"issueType":   "IncorrectValue",
								"searchKey":   "testSearchKey",
								"queryName":   "testName",
							},
							Query:     "TestQuery",
							CWE:       "22",
							RiskScore: "0",
						},
					},
					Files: map[string]model.FileMetadata{
						"testV": {LinesOriginalData: &[]string{}},
					},
				},
				v: map[string]interface{}{
					"documentId": "testV",
				},
			},
			useNewVulnerability: true,
			want: model.Vulnerability{
				ID:               0,
				SimilarityID:     "2fefa27cc667decf203d10f103b7ffdec232e9af16e361f47d626e72c72b8d63",
				ScanID:           "ScanID",
				FileID:           "",
				FileName:         "",
				DescriptionID:    "Undefined",
				CWE:              "22",
				RiskScore:        "0",
				QueryID:          "Undefined",
				QueryName:        "testName",
				QueryURI:         "https://github.com/Checkmarx/kics/",
				Severity:         model.SeverityCritical,
				Line:             1,
				SearchLine:       -1,
				VulnLines:        &[]model.CodeLine{},
				IssueType:        "IncorrectValue",
				SearchKey:        "testSearchKey",
				KeyActualValue:   "",
				KeyExpectedValue: "",
				Value:            nil,
				Output:           `{"documentId":"testV","issueType":"IncorrectValue","key":"123","oldSeverity":"CRITICAL","queryName":"testName","searchKey":"testSearchKey","severity":"INFO"}`, //nolint
			},
			wantErr:             false,
			kicsComputeNewSimID: true,
		},
		{
			name: "DefaultVulnerabilityBuilder with override for platform and cloud provider",
			args: vbArgs{
				tracker: &tracker.CITracker{},
				ctx: &QueryContext{
					scanID: "ScanID",
					Query: &PreparedQuery{
						Metadata: model.QueryMetadata{
							Metadata: map[string]interface{}{
								"key":           "123",
								"severity":      model.SeverityInfo,
								"oldSeverity":   model.SeverityCritical,
								"issueType":     "IncorrectValue",
								"searchKey":     "testSearchKey",
								"platform":      "CICD",
								"cloudProvider": "common",
							},
							Query:     "TestQuery",
							CWE:       "22",
							RiskScore: "0",
						},
					},
					Files: map[string]model.FileMetadata{
						"testV": {LinesOriginalData: &[]string{}},
					},
				},
				v: map[string]interface{}{
					"documentId": "testV",
				},
			},
			want: model.Vulnerability{
				ID:               0,
				SimilarityID:     "2fefa27cc667decf203d10f103b7ffdec232e9af16e361f47d626e72c72b8d63",
				ScanID:           "ScanID",
				FileID:           "",
				FileName:         "",
				DescriptionID:    "Undefined",
				CWE:              "22",
				RiskScore:        "0",
				QueryID:          "Undefined",
				QueryName:        "Anonymous",
				QueryURI:         "https://github.com/Checkmarx/kics/",
				Severity:         model.SeverityInfo,
				Line:             1,
				SearchLine:       -1,
				VulnLines:        &[]model.CodeLine{},
				IssueType:        "IncorrectValue",
				SearchKey:        "testSearchKey",
				KeyActualValue:   "",
				KeyExpectedValue: "",
				Platform:         "CICD",
				CloudProvider:    "common",
				Value:            nil,
				Output:           `{"cloudProvider":"common","documentId":"testV","issueType":"IncorrectValue","key":"123","oldSeverity":"CRITICAL","platform":"CICD","searchKey":"testSearchKey","severity":"INFO"}`,
			},
			wantErr:             false,
			kicsComputeNewSimID: true,
		},
	}

	for _, tt := range vbTests {
		insDetector := detector.NewDetectLine(3)
		t.Run(tt.name, func(t *testing.T) {
			got, err := DefaultVulnerabilityBuilder(tt.args.ctx, tt.args.tracker, tt.args.v, insDetector, tt.useNewVulnerability, tt.kicsComputeNewSimID, map[string]TransitionQueryInfo{})
			if (err != nil) != tt.wantErr {
				t.Errorf("test[%s] DefaultVulnerabilityBuilder() error %v, wantErr %v", tt.name, err, tt.wantErr)
				return
			}
			var gotJSON, wantJSON []byte
			gotJSON, err = json.Marshal(got)
			require.NoError(t, err)
			wantJSON, err = json.Marshal(tt.want)
			require.NoError(t, err)

			require.Equal(t, string(wantJSON), string(gotJSON), "test[%s] DefaultVulnerabilityBuilder() mismatch", tt.name)
		})
	}
}

type similarityIDPair struct {
	SimilarityID    string
	OldSimilarityID string
}

// TestDefaultVulnerabilityBuilderTransition tests the functions [DefaultVulnerabilityBuilder] with all the types of transition for similarity IDs
func TestDefaultVulnerabilityBuilderTransition(t *testing.T) {
	sample, err := getFileMetadataSample()
	require.NoError(t, err)

	// same as in regular inspector
	lineDetector := detector.NewDetectLine(3).
		Add(helm.DetectKindLine{}, model.KindHELM).
		Add(docker.DetectKindLine{}, model.KindDOCKER).
		Add(docker.DetectKindLine{}, model.KindBUILDAH)

	var allVulnerabilityBuilderTransitions = []VulnerabilityBuilderTransition{
		YetToBeChecked,
		NonGracefullyTransition,
		TransitionWithoutChanges,
		AddedSearchValue,
		AddedSearchLine,
		AddedSearchValueAndAddedSearchLine,
	}

	// arguments got from real query execution during debugging
	// query "containers_running_as_root" from assets/queries/k8s/containers_running_as_root/query.rego
	// with the file assets/queries/k8s/containers_running_as_root/test/positive2.yaml
	args := vbArgs{
		tracker: &tracker.CITracker{},
		ctx: &QueryContext{
			scanID: "ScanID",
			Query: &PreparedQuery{
				Metadata: model.QueryMetadata{
					InputData: "{}",
					Query:     "containers_running_as_root",
					Content:   "package Cx\n\nimport data.generic.k8s as k8sLib\nimport data.generic.common as common_lib\n\ntypes := {\"initContainers\", \"containers\"}\noptions := {\"runAsUser\", \"runAsNonRoot\"}\n\n## container defines runAsNonRoot or runAsUser\nCxPolicy[result] {\n    document := input.document[i]\n    metadata := document.metadata\n\n    specInfo := k8sLib.getSpecInfo(document)\n\n\tcommon_lib.valid_key(specInfo.spec[types[x]][c].securityContext, options[_])\n\trunsAsRoot(specInfo.spec[types[x]][c].securityContext)\n\n\tresult := {\n\t\t\"documentId\": document.id,\n\t\t\"resourceType\": document.kind,\n\t\t\"resourceName\": metadata.name,\n\t\t\"searchKey\": sprintf(\"metadata.name={{%s}}.%s.%s.name={{%s}}.securityContext.runAsUser\", [metadata.name, specInfo.path, types[x], specInfo.spec[types[x]][c].name]),\n\t\t\"issueType\": \"IncorrectValue\",\n\t\t\"searchValue\": document.kind, # multiple kind can match the same spec structure\n\t\t\"keyExpectedValue\": sprintf(\"metadata.name={{%s}}.%s.%s.name={{%s}}.securityContext runAsUser is higher than 0 and/or 'runAsNonRoot' is true\", [metadata.name, specInfo.path, types[x], specInfo.spec[types[x]][c].name]),\n",
					Metadata: map[string]interface{}{
						"queryName":       "Container Running As Root",
						"severity":        model.SeverityMedium,
						"category":        "Best Practices",
						"platform":        "Kubernetes",
						"descriptionID":   "9d5b1d16",
						"cloudProvider":   "common",
						"cwe":             "1188",
						"id":              "cf34805e-3872-4c08-bf92-6ff7bb0cfadb",
						"descriptionText": "Containers should only run as non-root user. This limits the exploitability of security misconfigurations and restricts an attacker's possibilities in case of compromise",
						"descriptionUrl":  "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/",
					},
					Platform:     "k8s",
					CWE:          "",
					Aggregation:  1,
					Experimental: false,
				},
			},
			BaseScanPaths: []string{"assets/queries/k8s/containers_running_as_root/test/positive2.yaml"},
			Files: map[string]model.FileMetadata{
				"3f8f6358-90f6-41a9-9b37-70c3a2523028": sample,
			},
		},
	}

	v := map[string]interface{}{
		"documentId":       "3f8f6358-90f6-41a9-9b37-70c3a2523028",
		"resourceType":     "Pod",
		"searchValue":      "Pod",
		"descriptionUrl":   "https://kubernetes.io/docs/tasks/configure-pod-container",
		"queryName":        "Container Running As Root",
		"issueType":        "IncorrectValue",
		"resourceName":     "security-context-demo-2",
		"cwe":              "1188",
		"descriptionID":    "9d5b1d16",
		"keyActualValue":   "metadata.name={{security-context-demo-2}}.spec.containers.name={{sec-ctx-demo-100}}.securityContext runAsUser is 0 and 'runAsNonRoot' is false",
		"searchKey":        "metadata.name={{security-context-demo-2}}.spec.containers.name={{sec-ctx-demo-100}}.securityContext.runAsUser",
		"id":               "cf34805e-3872-4c08-bf92-6ff7bb0cfadb",
		"descriptionText":  "Containers should only run as non-root user. This limits the exploitability of security misconfigurations and restricts an attacker's possibilities in case of compromise",
		"severity":         "MEDIUM",
		"keyExpectedValue": "metadata.name={{security-context-demo-2}}.spec.containers.name={{sec-ctx-demo-100}}.securityContext runAsUser is higher than 0 and/or 'runAsNonRoot' is true",
		"searchLine":       []string{"spec", "containers", "0"},
		"cloudProvider":    "common",
		"category":         "Best Practices",
		"platform":         "Kubernetes",
	}

	vbTests := []struct {
		name                string
		argsModifier        map[string]interface{}
		wantVB              map[VulnerabilityBuilderTransition]similarityIDPair
		useNewVulnerability bool
	}{

		{
			name: "VulnerabilityBuilder only with searchKey",
			argsModifier: map[string]interface{}{ // modify args to remove searchValue & searchLine
				"searchValue": nil,
				"searchLine":  nil,
			},
			wantVB: map[VulnerabilityBuilderTransition]similarityIDPair{
				YetToBeChecked: {
					SimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // similarity ID using only searchKey
				},
				NonGracefullyTransition: {
					SimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // similarity ID using only searchKey
				},
				TransitionWithoutChanges: {
					SimilarityID:    "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // similarity ID using only searchKey
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
				AddedSearchValue: {
					SimilarityID:    "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // similarity ID using only searchKey
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
				AddedSearchLine: {
					SimilarityID:    "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // similarity ID using only searchKey
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
				AddedSearchValueAndAddedSearchLine: {
					SimilarityID:    "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // similarity ID using only searchKey
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
			},
		},

		{
			name: "VulnerabilityBuilder only with searchKey & searchValue",
			argsModifier: map[string]interface{}{ // modify args to remove searchLine
				"searchLine": nil,
			},
			wantVB: map[VulnerabilityBuilderTransition]similarityIDPair{
				YetToBeChecked: {
					SimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchValue and searchKey
				},
				NonGracefullyTransition: {
					SimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchValue and searchKey
				},
				TransitionWithoutChanges: {
					SimilarityID:    "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchValue and searchKey
					OldSimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchValue and searchKey
				},
				AddedSearchValue: {
					SimilarityID:    "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchValue and searchKey
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
				AddedSearchLine: {
					SimilarityID:    "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchValue and searchKey
					OldSimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // old similarity ID using searchValue and searchKey
				},
				AddedSearchValueAndAddedSearchLine: {
					SimilarityID:    "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchValue and searchKey
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
			},
		},

		{
			name: "VulnerabilityBuilder only with searchKey & searchLine",
			argsModifier: map[string]interface{}{ // modify args to remove searchValue
				"searchValue": nil,
			},
			wantVB: map[VulnerabilityBuilderTransition]similarityIDPair{
				YetToBeChecked: {
					SimilarityID: "c86ed7ed49ee86e7ff4ff44b570004dc5a2066573276a577a0332c57e2a9a90e", // similarity ID using old SearchLine as line number
				},
				NonGracefullyTransition: {
					SimilarityID: "d99f209a3746015252b7ee4a28f98f084e418c48edcff5918df3276b065b102a", // similarity ID using new SearchLine as concatenated string
				},
				TransitionWithoutChanges: {
					SimilarityID:    "d99f209a3746015252b7ee4a28f98f084e418c48edcff5918df3276b065b102a", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "c86ed7ed49ee86e7ff4ff44b570004dc5a2066573276a577a0332c57e2a9a90e", // old similarity ID using old SearchLine as line number
				},
				AddedSearchValue: {
					SimilarityID:    "d99f209a3746015252b7ee4a28f98f084e418c48edcff5918df3276b065b102a", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "c86ed7ed49ee86e7ff4ff44b570004dc5a2066573276a577a0332c57e2a9a90e", // old similarity ID using old SearchLine as line number & searchValue removed ( searchValue is empty )
				},
				AddedSearchLine: {
					SimilarityID:    "d99f209a3746015252b7ee4a28f98f084e418c48edcff5918df3276b065b102a", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
				AddedSearchValueAndAddedSearchLine: {
					SimilarityID:    "d99f209a3746015252b7ee4a28f98f084e418c48edcff5918df3276b065b102a", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
			},
		},
		{
			name:         "VulnerabilityBuilder only with searchKey & searchValue & searchLine",
			argsModifier: map[string]interface{}{ // do not modify args, all fields are present
			},
			wantVB: map[VulnerabilityBuilderTransition]similarityIDPair{
				YetToBeChecked: {
					SimilarityID: "5cb66634b37820f6e0d94c64f8f444f14f790a25b196dd955eabe72d40f709c0", // similarity ID using only searchKey & searchValue
				},
				NonGracefullyTransition: {
					SimilarityID: "24e2b2b7efd5293e58ecd72c92a45684e811a172e9c1a74588adf1a5f80f560d", // similarity ID using new SearchLine as concatenated string
				},
				TransitionWithoutChanges: {
					SimilarityID:    "24e2b2b7efd5293e58ecd72c92a45684e811a172e9c1a74588adf1a5f80f560d", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "5cb66634b37820f6e0d94c64f8f444f14f790a25b196dd955eabe72d40f709c0", // old similarity ID using old SearchLine as line number
				},
				AddedSearchValue: {
					SimilarityID:    "24e2b2b7efd5293e58ecd72c92a45684e811a172e9c1a74588adf1a5f80f560d", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "c86ed7ed49ee86e7ff4ff44b570004dc5a2066573276a577a0332c57e2a9a90e", // old similarity ID using old SearchLine as line number & searchValue removed
				},
				AddedSearchLine: {
					SimilarityID:    "24e2b2b7efd5293e58ecd72c92a45684e811a172e9c1a74588adf1a5f80f560d", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // old similarity ID using only searchKey & searchValue
				},
				AddedSearchValueAndAddedSearchLine: {
					SimilarityID:    "24e2b2b7efd5293e58ecd72c92a45684e811a172e9c1a74588adf1a5f80f560d", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
			},
		},
		{
			name: "VulnerabilityBuilder only invalid searchLine",
			argsModifier: map[string]interface{}{
				"searchLine": []interface{}{"invalid", "spec", "containers", "0"}, // invalid searchLine
			},
			wantVB: map[VulnerabilityBuilderTransition]similarityIDPair{
				YetToBeChecked: {
					SimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchKey & searchValue, faulty searchLine is skipped
				},
				NonGracefullyTransition: {
					SimilarityID: "89881be4391fba8e14bd8e540aae7265dfb267f0f53dd969f0e45f9978d071a7", // similarity ID using new SearchLine as concatenated string, even with faulty searchLine
				},
				TransitionWithoutChanges: {
					SimilarityID:    "89881be4391fba8e14bd8e540aae7265dfb267f0f53dd969f0e45f9978d071a7", // similarity ID using new SearchLine as concatenated string, even with faulty searchLine
					OldSimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // old similarity ID using old SearchLine as line number, faulty searchLine is skipped
				},
				AddedSearchValue: {
					SimilarityID:    "89881be4391fba8e14bd8e540aae7265dfb267f0f53dd969f0e45f9978d071a7", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey, faulty searchLine is skipped
				},
				AddedSearchLine: {
					SimilarityID:    "89881be4391fba8e14bd8e540aae7265dfb267f0f53dd969f0e45f9978d071a7", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // old similarity ID using only searchKey & searchValue
				},
				AddedSearchValueAndAddedSearchLine: {
					SimilarityID:    "89881be4391fba8e14bd8e540aae7265dfb267f0f53dd969f0e45f9978d071a7", // similarity ID using new SearchLine as concatenated string
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
			},
		},
		{
			name: "VulnerabilityBuilder with searchLine for key in root",
			argsModifier: map[string]interface{}{
				"searchLine": []interface{}{"apiVersion"}, // searchLine for key in root
			},
			wantVB: map[VulnerabilityBuilderTransition]similarityIDPair{
				YetToBeChecked: {
					SimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // similarity ID using searchKey & searchValue. SearchLine is skipped
				},
				NonGracefullyTransition: {
					SimilarityID: "92dddf4ce9d0571961e9f85b802411f7bf5dfde242df9123e80fd8764b66f128", // similarity ID using searchKey & searchValue, and searchLine WITH new capabilities
				},
				TransitionWithoutChanges: {
					SimilarityID:    "92dddf4ce9d0571961e9f85b802411f7bf5dfde242df9123e80fd8764b66f128", // similarity ID using searchKey & searchValue, and searchLine WITH new capabilities
					OldSimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // old similarity ID using searchKey & searchValue. SearchLine is skipped
				},
				AddedSearchValue: {
					SimilarityID:    "92dddf4ce9d0571961e9f85b802411f7bf5dfde242df9123e80fd8764b66f128", // similarity ID using searchKey & searchValue, and searchLine WITH new capabilities
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // similarity ID using searchKey. SearchLine is skipped
				},
				AddedSearchLine: {
					SimilarityID:    "92dddf4ce9d0571961e9f85b802411f7bf5dfde242df9123e80fd8764b66f128", // similarity ID using searchKey & searchValue, and searchLine WITH new capabilities
					OldSimilarityID: "0a0a6474dfba7e9fc468195074890337f6b8c7b2cff40d84ed3c7547b9a3cd0d", // old similarity ID using searchKey & searchValue. SearchLine is skipped
				},
				AddedSearchValueAndAddedSearchLine: {
					SimilarityID:    "92dddf4ce9d0571961e9f85b802411f7bf5dfde242df9123e80fd8764b66f128", // similarity ID using searchKey & searchValue, and searchLine WITH new capabilities
					OldSimilarityID: "3889df0dedd460b7b493032422c0683520445a01c6aa9809cbd57580b98ca8cc", // old similarity ID using only searchKey
				},
			},
		},
	}

	for _, tt := range vbTests {
		t.Run(tt.name, func(t *testing.T) {
			// Create a copy of args to modify it without affecting the original
			vTest := map[string]interface{}{}
			err := util.DeepCopy(&vTest, v)
			require.NoError(t, err)
			args.v = vTest
			for key, value := range tt.argsModifier {
				if value == nil {
					delete(args.v.(map[string]interface{}), key)
				} else {
					args.v.(map[string]interface{})[key] = value
				}
			}

			// test similarity ID for each transition type for the given result
			for _, transition := range allVulnerabilityBuilderTransitions {
				t.Run(fmt.Sprintf("%s_Type%d", tt.name, transition), func(t *testing.T) {
					queryId := args.ctx.Query.Metadata.Metadata["id"].(string)
					got, err := DefaultVulnerabilityBuilder(
						args.ctx, args.tracker, args.v, lineDetector, tt.useNewVulnerability, true,
						map[string]TransitionQueryInfo{
							queryId: {
								QueryID:    queryId,
								QueryName:  args.ctx.Query.Metadata.Metadata["queryName"].(string),
								Transition: transition,
							},
						},
					)
					require.NoError(t, err)
					require.Equal(t, tt.wantVB[transition].SimilarityID, got.SimilarityID, "SimilarityID mismatch for transition %d", transition)
					require.Equal(t, tt.wantVB[transition].OldSimilarityID, got.OldSimilarityID, "OldSimilarityID mismatch for transition %d", transition)

					//Test for similarity ID when kicsComputeNewSimID is false
					gotOld, err := DefaultVulnerabilityBuilder(
						args.ctx, args.tracker, args.v, lineDetector, tt.useNewVulnerability, false,
						map[string]TransitionQueryInfo{
							queryId: {
								QueryID:    queryId,
								QueryName:  args.ctx.Query.Metadata.Metadata["queryName"].(string),
								Transition: transition,
							},
						},
					)
					require.NoError(t, err)
					if transition != NonGracefullyTransition {
						if tt.wantVB[transition].OldSimilarityID != "" {
							require.Equal(t, tt.wantVB[transition].OldSimilarityID, gotOld.SimilarityID, "SimilarityID mismatch for transition %d when kicsComputeNewSimID is false", transition)
						} else {
							require.Equal(t, tt.wantVB[transition].SimilarityID, gotOld.SimilarityID, "SimilarityID mismatch for transition %d when kicsComputeNewSimID is false", transition)
						}
					}
				})
			}
		})
	}
}

var OriginalData = `{
	"father": {
		"son": {
			"grandson": "value"
		}
	}
}`

func TestEngine_calculate(t *testing.T) { //nolint
	type fields struct {
		lineNr               int
		vObj                 map[string]interface{}
		file                 model.FileMetadata
		detector             *detector.DetectLine
		similarityIDLineInfo string
		linesVulne           model.VulnerabilityLines
	}
	tests := []struct {
		name           string
		fields         fields
		wantLine       int
		wantLinesVulne model.VulnerabilityLines
		wantSimID      string
	}{
		{
			name: "should have default values",
			fields: fields{
				lineNr: -1,
				vObj: map[string]interface{}{
					"no_search_line": "",
				},
				file:                 model.FileMetadata{LinesOriginalData: &[]string{}},
				detector:             &detector.DetectLine{},
				similarityIDLineInfo: "should be default",
				linesVulne: model.VulnerabilityLines{
					Line:                  5,
					LineWithVulnerability: "this line",
					VulnLines:             &[]model.CodeLine{},
				},
			},
			wantLine: -1,
			wantLinesVulne: model.VulnerabilityLines{
				Line:                  5,
				LineWithVulnerability: "this line",
				VulnLines:             &[]model.CodeLine{},
			},
			wantSimID: "should be default",
		},
		{
			name: "should change values",
			fields: fields{
				lineNr: -1,
				vObj: map[string]interface{}{
					"searchLine": []interface{}{"father", "son", "grandson"},
				},
				file: model.FileMetadata{
					LineInfoDocument: map[string]interface{}{
						"_kics_lines": map[string]interface{}{
							"_kics__default": map[string]interface{}{
								"_kics_line": 0,
							},
							"_kics_father": map[string]interface{}{
								"_kics_line": 3,
							},
						},
						"father": map[string]interface{}{
							"_kics_lines": map[string]interface{}{
								"_kics__default": map[string]interface{}{
									"_kics_line": 3,
								},
								"_kics_son": map[string]interface{}{
									"_kics_line": 4,
								},
							},
							"son": map[string]interface{}{
								"_kics_lines": map[string]interface{}{
									"_kics__default": map[string]interface{}{
										"_kics_line": 4,
									},
									"_kics_grandson": map[string]interface{}{
										"_kics_line": 5,
									},
								},
								"grandson": "value",
							},
						},
					},
					OriginalData:      OriginalData,
					LinesOriginalData: utils.SplitLines(OriginalData),
				},
				detector:             detector.NewDetectLine(3),
				similarityIDLineInfo: "should be default",
				linesVulne: model.VulnerabilityLines{
					Line:                  2,
					LineWithVulnerability: "this line",
					VulnLines:             &[]model.CodeLine{},
				},
			},
			wantLine: 5,
			wantLinesVulne: model.VulnerabilityLines{
				Line: 5,
				VulnLines: &[]model.CodeLine{
					{
						Position: 4, Line: "\t\t\t\"grandson\": \"value\"",
					},
					{
						Position: 5, Line: "\t\t}",
					},
					{
						Position: 6, Line: "\t}",
					},
				},
				LineWithVulnerability: "",
			},
			wantSimID: "5",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			testCalc := &searchLineCalculator{
				lineNr:              tt.fields.lineNr,
				vObj:                tt.fields.vObj,
				file:                tt.fields.file,
				detector:            tt.fields.detector,
				oldSearchLineOutput: tt.fields.similarityIDLineInfo,
				newSearchLineOutput: tt.fields.similarityIDLineInfo,
				vulnerabilityLines:  tt.fields.linesVulne,
			}
			testCalc.calculate()
			require.Equal(t, tt.wantLine, testCalc.lineNr)
			require.Equal(t, tt.wantLinesVulne, testCalc.vulnerabilityLines)
			require.Equal(t, tt.wantSimID, testCalc.oldSearchLineOutput)
		})
	}
}

func TestSanitize(t *testing.T) {
	tests := []struct {
		searchKey string
		wantStr   string
	}{
		{
			searchKey: "all.children.tower",
			wantStr:   "[tower]",
		},
		{
			searchKey: "all.children.defaults.hosts",
			wantStr:   "[defaults]",
		},
		{
			searchKey: "all.children.galaxy.hosts.galaxy_server",
			wantStr:   "[galaxy].galaxy_server",
		},
		{
			searchKey: "all.children",
			wantStr:   "all.children",
		},
	}
	for _, tt := range tests {
		t.Run(tt.searchKey, func(t *testing.T) {
			_, wanted := sanitizeINIKey(strings.Split(tt.searchKey, "."))
			require.Equal(t, tt.wantStr, wanted)
		})
	}
}

const (
	samplePathCI    = "./test/assets/fileMetadataVBSample.json"
	samplePathLocal = "../../test/assets/fileMetadataVBSample.json"
)

func getFileMetadataSample() (model.FileMetadata, error) {
	paths := []string{samplePathCI, samplePathLocal}
	var content []byte
	var err error
	for _, path := range paths {
		content, err = os.ReadFile(path)
		if err == nil {
			var fileMetadata model.FileMetadata
			if err := json.Unmarshal(content, &fileMetadata); err != nil {
				return model.FileMetadata{}, err
			}
			return fileMetadata, nil
		}
	}
	return model.FileMetadata{}, err
}
