/*
    Copyright (C) 2022 Tenable, Inc.

	Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

		http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

package vulnerability

import (
	"context"
	"fmt"
	"reflect"
	"testing"

	containeranalysis "cloud.google.com/go/containeranalysis/apiv1"
	"github.com/tenable/terrascan/pkg/iac-providers/output"
	grafeaspb "google.golang.org/genproto/googleapis/grafeas/v1"
)

type mockGCRScanner struct {
	client         *containeranalysis.Client
	newClientErr   error
	closeErr       error
	occurrences    []*grafeaspb.Occurrence
	occurrencesErr error
}

func (m mockGCRScanner) newClient(ctx context.Context) (*containeranalysis.Client, error) {
	return m.client, m.newClientErr
}

func (m mockGCRScanner) close(client *containeranalysis.Client) error {
	return m.closeErr
}

func (m mockGCRScanner) listOccurrences(ctx context.Context, client *containeranalysis.Client, req *grafeaspb.ListOccurrencesRequest) ([]*grafeaspb.Occurrence, error) {
	return m.occurrences, m.occurrencesErr
}

func TestGetProjectIDFromImageName(t *testing.T) {
	type args struct {
		image string
	}
	tests := []struct {
		name    string
		args    args
		want    string
		wantErr error
	}{
		{
			name: "invalid image reference",
			args: args{
				image: "test",
			},
			want:    "",
			wantErr: fmt.Errorf(invalidImageReferenceMsg, "test"),
		},
		{
			name: "valid image reference",
			args: args{
				image: "gcr.io/test/test-image",
			},
			want:    "test",
			wantErr: nil,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := getProjectIDFromImageName(tt.args.image)
			if !reflect.DeepEqual(err, tt.wantErr) {
				t.Errorf("getProjectIDFromImageName() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("getProjectIDFromImageName() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGCRScanImage(t *testing.T) {
	type fields struct {
		scanner gcrScanner
	}
	type args struct {
		image string
	}
	tests := []struct {
		name       string
		fields     fields
		args       args
		wantResult []*grafeaspb.Occurrence
		wantErr    error
	}{
		{
			name: "client create error",
			args: args{
				image: "gcr.io/test/test-image@sha256:3f13b4376446cf",
			},
			fields: fields{
				scanner: mockGCRScanner{
					newClientErr: fmt.Errorf("error creating new client"),
				},
			},
			wantResult: nil,
			wantErr:    fmt.Errorf("error creating new client"),
		},
		{
			name: "found vulnerability",
			args: args{
				image: "gcr.io/test/test-image@sha256:3f13b4376446cf",
			},
			fields: fields{
				scanner: mockGCRScanner{
					occurrences: []*grafeaspb.Occurrence{},
				},
			},
			wantResult: []*grafeaspb.Occurrence{},
			wantErr:    nil,
		},
		{
			name: "invalid image reference",
			args: args{
				image: "test-image@sha256:3f13b4376446cf",
			},
			fields: fields{
				scanner: mockGCRScanner{},
			},
			wantResult: nil,
			wantErr:    fmt.Errorf("invalid image reference test-image@sha256:3f13b4376446cf "),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := &GCR{
				scanner: tt.fields.scanner,
			}
			gotResult, err := g.ScanImage(tt.args.image)
			if !reflect.DeepEqual(err, tt.wantErr) {
				t.Errorf("GCR.ScanImage() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(gotResult, tt.wantResult) {
				t.Errorf("GCR.ScanImage() = %v, want %v", gotResult, tt.wantResult)
			}
		})
	}
}

func TestGCRGetVulnerabilities(t *testing.T) {
	type fields struct {
		scanner gcrScanner
	}
	type args struct {
		container output.ContainerDetails
		options   map[string]interface{}
	}
	tests := []struct {
		name                string
		fields              fields
		args                args
		wantVulnerabilities []output.Vulnerability
	}{
		{
			name: "invalid image reference",
			args: args{
				container: output.ContainerDetails{
					Image: "test-image@sha256:3f13b4376446cf",
				},
			},
			fields: fields{
				scanner: mockGCRScanner{},
			},
			wantVulnerabilities: nil,
		},
		{
			name: "vulnerabilities found",
			args: args{
				container: output.ContainerDetails{
					Image: "gcr.io/test/test-image@sha256:3f13b4376446cf",
				},
			},
			fields: fields{
				scanner: mockGCRScanner{
					occurrences: []*grafeaspb.Occurrence{
						{NoteName: "test/cve-1342"},
					},
				},
			},
			wantVulnerabilities: []output.Vulnerability{
				{
					VulnerabilityID: "cve-1342",
					Severity:        "SEVERITY_UNSPECIFIED",
					PrimaryURL:      "https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-1342",
					CVSS: output.VendorCVSS{
						"nvd": output.CVSS{},
					},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := &GCR{
				scanner: tt.fields.scanner,
			}
			if gotVulnerabilities := g.getVulnerabilities(tt.args.container, tt.args.options); !reflect.DeepEqual(gotVulnerabilities, tt.wantVulnerabilities) {
				t.Errorf("GCR.getVulnerabilities() = %#v, want %v", gotVulnerabilities, tt.wantVulnerabilities)
			}
		})
	}
}
