package vulnerability

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"

	"github.com/goharbor/harbor/src/controller/artifact"
	scanCtl "github.com/goharbor/harbor/src/controller/scan"
	art "github.com/goharbor/harbor/src/pkg/artifact"
	"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
	"github.com/goharbor/harbor/src/pkg/scan/report"
	"github.com/goharbor/harbor/src/pkg/task"
	htesting "github.com/goharbor/harbor/src/testing"
	artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
	scanCtlTest "github.com/goharbor/harbor/src/testing/controller/scan"
	"github.com/goharbor/harbor/src/testing/mock"
	accessorytesting "github.com/goharbor/harbor/src/testing/pkg/accessory"
	reporttesting "github.com/goharbor/harbor/src/testing/pkg/scan/report"
	tasktesting "github.com/goharbor/harbor/src/testing/pkg/task"

	"github.com/goharbor/harbor/src/common/rbac"
	"github.com/goharbor/harbor/src/lib/orm"
	accessoryModel "github.com/goharbor/harbor/src/pkg/accessory/model"
	"github.com/goharbor/harbor/src/pkg/permission/types"
	"github.com/goharbor/harbor/src/pkg/robot/model"
	"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
	"github.com/goharbor/harbor/src/pkg/scan/postprocessors"
	v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
	"github.com/goharbor/harbor/src/testing/jobservice"
	ormtesting "github.com/goharbor/harbor/src/testing/lib/orm"
	postprocessorstesting "github.com/goharbor/harbor/src/testing/pkg/scan/postprocessors"
)

func TestRequiredPermissions(t *testing.T) {
	v := &scanHandler{}
	expected := []*types.Policy{
		{
			Resource: rbac.ResourceRepository,
			Action:   rbac.ActionPull,
		},
		{
			Resource: rbac.ResourceRepository,
			Action:   rbac.ActionScannerPull,
		},
	}

	result := v.RequiredPermissions()

	assert.Equal(t, expected, result, "RequiredPermissions should return correct permissions")
}

func TestPostScan(t *testing.T) {
	v := &scanHandler{}
	ctx := &jobservice.MockJobContext{}
	artifact := &v1.Artifact{}
	origRp := &scan.Report{}
	rawReport := ""

	mocker := &postprocessorstesting.NativeScanReportConverter{}
	mocker.On("ToRelationalSchema", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", "original report", nil)
	postprocessors.Converter = mocker
	sr := &v1.ScanRequest{Artifact: artifact}
	refreshedReport, err := v.PostScan(ctx, sr, origRp, rawReport, time.Now(), &model.Robot{})
	assert.Equal(t, "original report", refreshedReport, "PostScan should return the refreshed report")
	assert.Nil(t, err, "PostScan should not return an error")
}

func TestScanHandler_RequiredPermissions(t *testing.T) {
	tests := []struct {
		name string
		want []*types.Policy
	}{
		{"normal", []*types.Policy{
			{
				Resource: rbac.ResourceRepository,
				Action:   rbac.ActionPull,
			},
			{
				Resource: rbac.ResourceRepository,
				Action:   rbac.ActionScannerPull,
			},
		}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			v := &scanHandler{}
			assert.Equalf(t, tt.want, v.RequiredPermissions(), "RequiredPermissions()")
		})
	}
}

func TestScanHandler_ReportURLParameter(t *testing.T) {
	type args struct {
		in0 *v1.ScanRequest
	}
	tests := []struct {
		name    string
		args    args
		want    string
		wantErr assert.ErrorAssertionFunc
	}{
		{"normal", args{&v1.ScanRequest{}}, "", assert.NoError},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			v := &scanHandler{}
			got, err := v.URLParameter(tt.args.in0)
			if !tt.wantErr(t, err, fmt.Sprintf("URLParameter(%v)", tt.args.in0)) {
				return
			}
			assert.Equalf(t, tt.want, got, "URLParameter(%v)", tt.args.in0)
		})
	}
}

func TestScanHandler_RequestProducesMineTypes(t *testing.T) {
	tests := []struct {
		name string
		want []string
	}{
		{"normal", []string{v1.MimeTypeGenericVulnerabilityReport}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			v := &scanHandler{}
			assert.Equalf(t, tt.want, v.RequestProducesMineTypes(), "RequestProducesMineTypes()")
		})
	}
}

type VulHandlerTestSuite struct {
	htesting.Suite
	ar             *artifacttesting.Controller
	accessoryMgr   *accessorytesting.Manager
	artifact       *artifact.Artifact
	taskMgr        *tasktesting.Manager
	reportMgr      *reporttesting.Manager
	scanController *scanCtlTest.Controller
	handler        *scanHandler
}

func (suite *VulHandlerTestSuite) SetupSuite() {
	suite.ar = &artifacttesting.Controller{}
	suite.accessoryMgr = &accessorytesting.Manager{}
	suite.taskMgr = &tasktesting.Manager{}
	suite.scanController = &scanCtlTest.Controller{}
	suite.reportMgr = &reporttesting.Manager{}
	suite.artifact = &artifact.Artifact{Artifact: art.Artifact{ID: 1}}
	suite.artifact.Type = "IMAGE"
	suite.artifact.ProjectID = 1
	suite.artifact.RepositoryName = "library/photon"
	suite.artifact.Digest = "digest-code"
	suite.artifact.ManifestMediaType = v1.MimeTypeDockerArtifact
	suite.handler = &scanHandler{
		reportConverter:    postprocessors.Converter,
		ReportMgrFunc:      func() report.Manager { return suite.reportMgr },
		TaskMgrFunc:        func() task.Manager { return suite.taskMgr },
		ScanControllerFunc: func() scanCtl.Controller { return suite.scanController },
		cloneCtx:           func(ctx context.Context) context.Context { return ctx },
	}

}

func (suite *VulHandlerTestSuite) TearDownSuite() {
}

func TestExampleTestSuite(t *testing.T) {
	suite.Run(t, &VulHandlerTestSuite{})
}

// TestScanControllerGetSummary ...
func (suite *VulHandlerTestSuite) TestScanControllerGetSummary() {
	rpts := []*scan.Report{
		{UUID: "uuid", MimeType: v1.MimeTypeGenericVulnerabilityReport},
	}
	ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
	mock.OnAnything(suite.ar, "HasUnscannableLayer").Return(false, nil).Once()
	mock.OnAnything(suite.accessoryMgr, "List").Return([]accessoryModel.Accessory{}, nil).Once()
	mock.OnAnything(suite.ar, "Walk").Return(nil).Run(func(args mock.Arguments) {
		walkFn := args.Get(2).(func(*artifact.Artifact) error)
		walkFn(suite.artifact)
	}).Once()
	mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return(nil, nil).Once()
	mock.OnAnything(suite.scanController, "GetReport").Return(rpts, nil).Once()
	sum, err := suite.handler.GetSummary(ctx, suite.artifact, []string{v1.MimeTypeGenericVulnerabilityReport})
	require.NoError(suite.T(), err)
	assert.Equal(suite.T(), 1, len(sum))
}

func (suite *VulHandlerTestSuite) TestMakeReportPlaceHolder() {
	ctx := orm.NewContext(nil, &ormtesting.FakeOrmer{})
	art := &artifact.Artifact{Artifact: art.Artifact{ID: 1, Digest: "digest", ManifestMediaType: v1.MimeTypeDockerArtifact}}
	r := &scanner.Registration{
		UUID: "uuid",
		Metadata: &v1.ScannerAdapterMetadata{
			Capabilities: []*v1.ScannerCapability{
				{Type: v1.ScanTypeVulnerability, ConsumesMimeTypes: []string{v1.MimeTypeDockerArtifact}, ProducesMimeTypes: []string{v1.MimeTypeGenericVulnerabilityReport}},
			},
		},
	}
	// mimeTypes := []string{v1.MimeTypeGenericVulnerabilityReport}
	mock.OnAnything(suite.reportMgr, "GetBy").Return([]*scan.Report{{UUID: "uuid"}}, nil).Once()
	mock.OnAnything(suite.reportMgr, "Create").Return("uuid", nil).Once()
	mock.OnAnything(suite.reportMgr, "Delete").Return(nil).Once()
	mock.OnAnything(suite.taskMgr, "ListScanTasksByReportUUID").Return([]*task.Task{{Status: "Success"}}, nil)
	mock.OnAnything(suite.handler.reportConverter, "FromRelationalSchema").Return("", nil)
	rps, err := suite.handler.MakePlaceHolder(ctx, art, r)
	require.NoError(suite.T(), err)
	assert.Equal(suite.T(), 1, len(rps))
}

func (suite *VulHandlerTestSuite) TestGetReportPlaceHolder() {
	mock.OnAnything(suite.reportMgr, "GetBy").Return([]*scan.Report{{UUID: "uuid"}}, nil).Once()
	rp, err := suite.handler.GetPlaceHolder(nil, "repo", "digest", "scannerUUID", "mimeType")
	require.NoError(suite.T(), err)
	assert.Equal(suite.T(), "uuid", rp.UUID)
	mock.OnAnything(suite.reportMgr, "GetBy").Return(nil, fmt.Errorf("not found")).Once()
	rp, err = suite.handler.GetPlaceHolder(nil, "repo", "digest", "scannerUUID", "mimeType")
	require.Error(suite.T(), err)
}
