// Copyright Project Harbor Authors
//
// 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 scan

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/q"
	"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
	"github.com/goharbor/harbor/src/testing/mock"
	mocks "github.com/goharbor/harbor/src/testing/pkg/scan/scanner"
)

func TestEnsureScanners(t *testing.T) {

	t.Run("Should do nothing when list of wanted scanners is empty", func(t *testing.T) {
		err := EnsureScanners(context.TODO(), []scanner.Registration{})
		assert.NoError(t, err)
	})

	t.Run("Should return error when listing scanners fails", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{
				"name__in": []string{"scanner"},
			},
		}).Return(nil, errors.New("DB error"))

		err := EnsureScanners(context.TODO(), []scanner.Registration{
			{Name: "scanner", URL: "http://scanner:8080"},
		})

		assert.EqualError(t, err, "listing scanners: DB error")
		mgr.AssertExpectations(t)
	})

	t.Run("Should create only non-existing scanners", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{
				"name__in": []string{
					"trivy",
				},
			},
		}).Return([]*scanner.Registration{}, nil)
		mgr.On("Create", mock.Anything, &scanner.Registration{
			Name: "trivy",
			URL:  "http://trivy:8080",
		}).Return("uuid-trivy", nil)

		err := EnsureScanners(context.TODO(), []scanner.Registration{
			{Name: "trivy", URL: "http://trivy:8080"},
		})

		assert.NoError(t, err)
		mgr.AssertExpectations(t)
	})

	t.Run("Should update scanners", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{
				"name__in": []string{
					"trivy",
				},
			},
		}).Return([]*scanner.Registration{
			{Name: "trivy", URL: "http://trivy:8080"},
		}, nil)
		mgr.On("Update", mock.Anything, &scanner.Registration{
			Name: "trivy",
			URL:  "http://trivy:8443",
		}).Return(nil)

		err := EnsureScanners(context.TODO(), []scanner.Registration{
			{Name: "trivy", URL: "http://trivy:8443"},
		})

		assert.NoError(t, err)
		mgr.AssertExpectations(t)
	})

}

func TestEnsureDefaultScanner(t *testing.T) {

	t.Run("Should return error when getting default scanner fails", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("GetDefault", mock.Anything).Return(nil, errors.New("DB error"))

		err := EnsureDefaultScanner(context.TODO(), "trivy")
		assert.EqualError(t, err, "getting default scanner: DB error")
		mgr.AssertExpectations(t)
	})

	t.Run("Should do nothing when the default scanner is already set", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("GetDefault", mock.Anything).Return(&scanner.Registration{
			Name: "trivy",
		}, nil)

		err := EnsureDefaultScanner(context.TODO(), "trivy")
		assert.NoError(t, err)
		mgr.AssertExpectations(t)
	})

	t.Run("Should return error when listing scanners fails", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("GetDefault", mock.Anything).Return(nil, nil)
		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{"name": "trivy"},
		}).Return(nil, errors.New("DB error"))

		err := EnsureDefaultScanner(context.TODO(), "trivy")
		assert.EqualError(t, err, "listing scanners: DB error")
		mgr.AssertExpectations(t)
	})

	t.Run("Should return error when listing scanners returns unexpected scanners count", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("GetDefault", mock.Anything).Return(nil, nil)
		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{"name": "trivy"},
		}).Return([]*scanner.Registration{
			{Name: "trivy"},
			{Name: "trivy"},
		}, nil)

		err := EnsureDefaultScanner(context.TODO(), "trivy")
		assert.EqualError(t, err, "expected only one scanner with name trivy but got 2")
		mgr.AssertExpectations(t)
	})

	t.Run("Should set the default scanner when it is not set", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("GetDefault", mock.Anything).Return(nil, nil)
		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{"name": "trivy"},
		}).Return([]*scanner.Registration{
			{
				Name: "trivy",
				UUID: "trivy-uuid",
				URL:  "http://trivy:8080",
			},
		}, nil)
		mgr.On("SetAsDefault", mock.Anything, "trivy-uuid").Return(nil)

		err := EnsureDefaultScanner(context.TODO(), "trivy")
		assert.NoError(t, err)
		mgr.AssertExpectations(t)
	})

	t.Run("Should return error when setting the default scanner fails", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("GetDefault", mock.Anything).Return(nil, nil)
		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{"name": "trivy"},
		}).Return([]*scanner.Registration{
			{
				Name: "trivy",
				UUID: "trivy-uuid",
				URL:  "http://trivy:8080",
			},
		}, nil)
		mgr.On("SetAsDefault", mock.Anything, "trivy-uuid").Return(errors.New("DB error"))

		err := EnsureDefaultScanner(context.TODO(), "trivy")
		assert.EqualError(t, err, "setting trivy as default scanner: DB error")
		mgr.AssertExpectations(t)
	})

}

func TestRemoveImmutableScanners(t *testing.T) {

	t.Run("Should do nothing when list of names is empty", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		err := RemoveImmutableScanners(context.TODO(), []string{})
		assert.NoError(t, err)
		mgr.AssertExpectations(t)
	})

	t.Run("Should return error when listing scanners fails", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{
				"immutable": true,
				"name__in":  []string{"scanner"},
			},
		}).Return(nil, errors.New("DB error"))

		err := RemoveImmutableScanners(context.TODO(), []string{"scanner"})
		assert.EqualError(t, err, "listing scanners: DB error")
		mgr.AssertExpectations(t)
	})

	t.Run("Should delete multiple scanners", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		registrations := []*scanner.Registration{
			{
				Name: "scanner-1",
				UUID: "uuid-1",
				URL:  "http://scanner-1",
			},
			{
				Name: "scanner-2",
				UUID: "uuid-2",
				URL:  "http://scanner-2",
			}}

		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{
				"immutable": true,
				"name__in": []string{
					"scanner-1",
					"scanner-2",
				},
			},
		}).Return(registrations, nil)
		mgr.On("Delete", mock.Anything, "uuid-1").Return(nil)
		mgr.On("Delete", mock.Anything, "uuid-2").Return(nil)

		err := RemoveImmutableScanners(context.TODO(), []string{
			"scanner-1",
			"scanner-2",
		})
		assert.NoError(t, err)
		mgr.AssertExpectations(t)
	})

	t.Run("Should return error when deleting any scanner fails", func(t *testing.T) {
		mgr := &mocks.Manager{}
		scannerManager = mgr

		registrations := []*scanner.Registration{
			{
				Name: "scanner-1",
				UUID: "uuid-1",
				URL:  "http://scanner-1",
			},
			{
				Name: "scanner-2",
				UUID: "uuid-2",
				URL:  "http://scanner-2",
			}}

		mgr.On("List", mock.Anything, &q.Query{
			Keywords: map[string]any{
				"immutable": true,
				"name__in": []string{
					"scanner-1",
					"scanner-2",
				},
			},
		}).Return(registrations, nil)
		mgr.On("Delete", mock.Anything, "uuid-1").Return(nil)
		mgr.On("Delete", mock.Anything, "uuid-2").Return(errors.New("DB error"))

		err := RemoveImmutableScanners(context.TODO(), []string{
			"scanner-1",
			"scanner-2",
		})
		assert.EqualError(t, err, "deleting scanner: uuid-2: DB error")
		mgr.AssertExpectations(t)
	})

}
