package cmd

import (
	"io"
	"os"
	"testing"

	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/fake"

	piperDocker "github.com/SAP/jenkins-library/pkg/docker"
	"github.com/SAP/jenkins-library/pkg/malwarescan"
	"github.com/SAP/jenkins-library/pkg/mock"
	"github.com/stretchr/testify/assert"
)

var malwareScanConfig = malwareExecuteScanOptions{
	Host:     "https://example.org/malwarescanner",
	Username: "me",
	Password: "********",
	ScanFile: "target/myFile",
	Timeout:  "60",
}

type malwareScanUtilsMockBundle struct {
	*mock.FilesMock

	returnScanResult *malwarescan.ScanResult
	returnSHA256     string
}

func (utils *malwareScanUtilsMockBundle) SHA256(filePath string) (string, error) {
	if utils.returnSHA256 == "" {
		return utils.returnScanResult.SHA256, nil
	}

	return utils.returnSHA256, nil
}

func (utils *malwareScanUtilsMockBundle) OpenFile(path string, flag int, perm os.FileMode) (io.ReadCloser, error) {
	return utils.FilesMock.OpenFile(path, flag, perm)
}

func (utils *malwareScanUtilsMockBundle) FileWrite(path string, content []byte, perm os.FileMode) error {
	return utils.FilesMock.FileWrite(path, content, perm)
}

func (utils *malwareScanUtilsMockBundle) Info() (*malwarescan.Info, error) {
	return &malwarescan.Info{EngineVersion: "Mock Malware Scanner", SignatureTimestamp: "n/a"}, nil
}

func (utils *malwareScanUtilsMockBundle) Scan(candidate io.Reader) (*malwarescan.ScanResult, error) {
	return utils.returnScanResult, nil
}

func (utils *malwareScanUtilsMockBundle) newDockerClient(options piperDocker.ClientOptions) piperDocker.Download {
	return &dockerClientMock{imageName: options.ImageName, registryURL: options.RegistryURL, localPath: options.LocalPath}
}

func TestMalwareScanWithNoBuildtool(t *testing.T) {
	files := &mock.FilesMock{}
	files.AddFile("target/myFile", []byte(`HELLO`))

	utils := malwareScanUtilsMockBundle{
		FilesMock: files,
	}

	t.Run("No malware, no encrypted content in file", func(t *testing.T) {
		utils.returnScanResult = &malwarescan.ScanResult{
			MalwareDetected:          false,
			EncryptedContentDetected: false,
			ScanSize:                 298782,
			MimeType:                 "application/octet-stream",
			SHA256:                   "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85",
		}

		error := runMalwareScan(&malwareScanConfig, nil, &utils)

		assert.NoError(t, error)
	})

	t.Run("Malware detected in file", func(t *testing.T) {
		utils.returnScanResult = &malwarescan.ScanResult{
			MalwareDetected:          true,
			EncryptedContentDetected: false,
			ScanSize:                 298782,
			MimeType:                 "application/octet-stream",
			SHA256:                   "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85",
			Finding:                  "Win.Test.EICAR_HDB-1",
		}

		error := runMalwareScan(&malwareScanConfig, nil, &utils)
		assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: true, encrypted content detected: false, finding: Win.Test.EICAR_HDB-1")
	})

	t.Run("Encrypted content detected in file", func(t *testing.T) {
		utils.returnScanResult = &malwarescan.ScanResult{
			MalwareDetected:          false,
			EncryptedContentDetected: true,
			ScanSize:                 298782,
			MimeType:                 "application/octet-stream",
			SHA256:                   "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85",
		}

		error := runMalwareScan(&malwareScanConfig, nil, &utils)
		assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: false, encrypted content detected: true, finding: ")
	})

	t.Run("Malware and encrypted content detected in file", func(t *testing.T) {
		utils.returnScanResult = &malwarescan.ScanResult{
			MalwareDetected:          true,
			EncryptedContentDetected: true,
			ScanSize:                 298782,
			MimeType:                 "application/octet-stream",
			SHA256:                   "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85",
			Finding:                  "Win.Test.EICAR_HDB-1",
		}

		error := runMalwareScan(&malwareScanConfig, nil, &utils)
		assert.EqualError(t, error, "Malware scan failed for file 'target/myFile'. Malware detected: true, encrypted content detected: true, finding: Win.Test.EICAR_HDB-1")
	})

	t.Run("No file and no buildtool specified", func(t *testing.T) {
		malwareScanConfig := malwareExecuteScanOptions{
			Host:     "https://example.org/malwarescanner",
			Username: "me",
			Password: "********",
			Timeout:  "60",
		}

		error := runMalwareScan(&malwareScanConfig, nil, nil)
		assert.EqualError(t, error, "Please specify a file to be scanned")
	})

	t.Run("File to be scanned, can't be found", func(t *testing.T) {
		utils.returnScanResult = nil

		malwareScanConfig := malwareExecuteScanOptions{
			Host:     "https://example.org/malwarescanner",
			Username: "me",
			Password: "********",
			Timeout:  "60",
			ScanFile: "target/fileWhichDoesntExist",
		}

		error := runMalwareScan(&malwareScanConfig, nil, &utils)
		assert.EqualError(t, error, "the file 'target/fileWhichDoesntExist' does not exist: file does not exist")
	})
}

func TestMalwareScanWithDockerAsBuildtoolTests(t *testing.T) {
	files := &mock.FilesMock{}
	files.AddFile("dockerimagename_latest.tar", []byte(``))

	utils := malwareScanUtilsMockBundle{
		FilesMock: files,
	}

	t.Run("No malware detected in docker image", func(t *testing.T) {
		utils.returnScanResult = &malwarescan.ScanResult{
			MalwareDetected:          false,
			EncryptedContentDetected: false,
			ScanSize:                 298782,
			MimeType:                 "application/octet-stream",
			SHA256:                   "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85",
		}

		malwareScanConfig := malwareExecuteScanOptions{
			Host:      "https://example.org/malwarescanner",
			Username:  "me",
			Password:  "********",
			Timeout:   "60",
			BuildTool: "docker",
			ScanImage: "dockerimagename:latest",
		}

		error := runMalwareScan(&malwareScanConfig, nil, &utils)

		assert.NoError(t, error)
	})

	t.Run("No file and no buildtool specified", func(t *testing.T) {
		malwareScanConfig := malwareExecuteScanOptions{
			Host:     "https://example.org/malwarescanner",
			Username: "me",
			Password: "********",
			Timeout:  "60",
		}

		error := runMalwareScan(&malwareScanConfig, nil, &utils)
		assert.EqualError(t, error, "Please specify a file to be scanned")
	})
}

func TestMalwareScanWithOtherBuildtoolTests(t *testing.T) {
	files := &mock.FilesMock{}
	files.AddFile("dockerimagename_latest.tar", []byte(``))

	utils := malwareScanUtilsMockBundle{
		FilesMock: files,
	}

	t.Run("No malware detected in docker image", func(t *testing.T) {
		utils.returnScanResult = &malwarescan.ScanResult{
			MalwareDetected:          false,
			EncryptedContentDetected: false,
			ScanSize:                 298782,
			MimeType:                 "application/octet-stream",
			SHA256:                   "96ca802fbd54d31903f1115a1d95590c685160637d9262bd340ab30d0f817e85",
		}

		malwareScanConfig := malwareExecuteScanOptions{
			Host:      "https://example.org/malwarescanner",
			Username:  "me",
			Password:  "********",
			Timeout:   "60",
			BuildTool: "golang",
			ScanImage: "dockerimagename:latest",
		}

		error := runMalwareScan(&malwareScanConfig, nil, &utils)

		assert.NoError(t, error)
	})
}

type dockerClientMock struct {
	imageName   string
	registryURL string
	localPath   string
}

//DownloadImage download the image to the specified path
func (c *dockerClientMock) DownloadImage(imageSource, filePath string) (v1.Image, error) {
	return &fake.FakeImage{}, nil // fmt.Errorf("%s", filePath)
}

//DownloadImage download the image to the specified path
func (c *dockerClientMock) DownloadImageContent(imageSource, filePath string) (v1.Image, error) {
	return &fake.FakeImage{}, nil // fmt.Errorf("%s", filePath)
}

// GetRemoteImageInfo return remote image information
func (c *dockerClientMock) GetRemoteImageInfo(imageSoure string) (v1.Image, error) {
	return &fake.FakeImage{}, nil
}
