// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.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.

//go:build e2e
// +build e2e

package s3resource

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/fileutil/artifact"
	filemock "github.com/aws/amazon-ssm-agent/agent/fileutil/filemanager/mock"
	"github.com/aws/amazon-ssm-agent/agent/mocks/context"
	"github.com/aws/amazon-ssm-agent/agent/plugins/downloadcontent/mocks/s3resource"
	"github.com/aws/amazon-ssm-agent/agent/s3util"
	"github.com/stretchr/testify/assert"
)

var contextMock = context.NewMockDefault()

func TestS3Resource_ValidateLocationInfoPath(t *testing.T) {

	locationInfo := `{
		"Path": "newpath"
	}`

	s3resource, _ := NewS3Resource(contextMock, locationInfo)
	_, err := s3resource.ValidateLocationInfo()

	assert.NoError(t, err)
}

func TestS3Resource_ValidateLocationInfoNoPath(t *testing.T) {

	locationInfo := `{
		"path": ""
	}`

	s3resource, _ := NewS3Resource(contextMock, locationInfo)
	_, err := s3resource.ValidateLocationInfo()

	assert.Error(t, err)
	assert.Equal(t, err.Error(), "S3 source path in SourceInfo must be specified")
}

func TestIsFolder_JSON(t *testing.T) {
	res := isPathType("nameOfFolder/nameOfFile.json")

	assert.False(t, res)
}

func TestIsFolder_Folder(t *testing.T) {
	res := isPathType("nameOfFolder/someOtherFolder/")

	assert.True(t, res)
}

func TestS3Resource_GetS3BucketURLString(t *testing.T) {
	resource := &S3Resource{
		context: contextMock,
		Info:    S3Info{Path: "https://s3.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/myfile.ps"},
		s3Object: s3util.AmazonS3URL{
			Bucket: "ssm-test-agent-bucket",
		},
	}

	res, err := resource.getS3BucketURLString()

	assert.Equal(t, "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket", res.String())
	assert.NoError(t, err)
}

func TestS3Resource_GetS3BucketURLString_sameBucketNameFile(t *testing.T) {
	resource := &S3Resource{
		context: contextMock,
		Info:    S3Info{Path: "https://s3.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/my-bucket.ps"},
		s3Object: s3util.AmazonS3URL{
			Bucket: "ssm-test-agent-bucket",
		},
	}

	res, err := resource.getS3BucketURLString()

	assert.Equal(t, "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket", res.String())
	assert.NoError(t, err)
}

func TestS3Resource_GetS3BucketURLString_hyphenatedEndpoint(t *testing.T) {
	resource := &S3Resource{
		context: contextMock,
		Info:    S3Info{Path: "https://s3-us-east-1.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/my-bucket.ps"},
		s3Object: s3util.AmazonS3URL{
			Bucket: "ssm-test-agent-bucket",
		},
	}

	res, err := resource.getS3BucketURLString()

	assert.Equal(t, "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket", res.String())
	assert.NoError(t, err)
}

func TestS3Resource_GetS3BucketURLString_bucketNameInS3URL(t *testing.T) {
	resource := &S3Resource{
		context: contextMock,
		Info:    S3Info{Path: "https://ssm-test-agent-bucket.s3.amazonaws.com/mydummyfolder/my-bucket.ps"},
		s3Object: s3util.AmazonS3URL{
			Bucket: "ssm-test-agent-bucket",
		},
	}

	res, err := resource.getS3BucketURLString()

	assert.Equal(t, "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket", res.String())
	assert.NoError(t, err)
}

func TestS3Resource_ValidateAndParseSourceInfo_WithAlphabeticExpectedBucketOwner_ThrowsError(t *testing.T) {

	// alphabetic expectedBucketOwner
	sourceInfo := `{
		"Path": "newpath",
		"ExpectedBucketOwner": "invalidBucketOwner"
	}`

	s3resource, err := NewS3Resource(contextMock, sourceInfo)
	assert.Error(t, err)
	assert.Nil(t, s3resource)
}

func TestS3Resource_ValidateAndParseSourceInfo_WithShortExpectedBucketOwner_ThrowsError(t *testing.T) {

	// short expectedBucketOwner
	sourceInfo := `{
		"Path": "newpath",
		"ExpectedBucketOwner": "1234"
	}`

	s3resource, err := NewS3Resource(contextMock, sourceInfo)

	assert.Nil(t, s3resource)
	assert.Error(t, err)
}

func TestS3Resource_ValidateAndParseSourceInfo_WithLongExpectedBucketOwner_ThrowsError(t *testing.T) {

	// long expectedBucketOwner
	sourceInfo := `{
		"Path": "newpath",
		"ExpectedBucketOwner": "1234567890123456"
	}`

	s3resource, err := NewS3Resource(contextMock, sourceInfo)

	assert.Nil(t, s3resource)
	assert.Error(t, err)
}

func TestS3Resource_Download(t *testing.T) {

	depMock := new(s3resource.S3DepMock)
	locationInfo := `{
		"path" : "https://s3.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/file.rb"
	}`
	fileMock := &filemock.FileSystemMock{}

	fileMock.On("IsDirectory", "destination").Return(true)
	fileMock.On("Exists", "destination").Return(true)
	resource, _ := NewS3Resource(contextMock, locationInfo)

	input := artifact.DownloadInput{
		DestinationDirectory: "destination",
		SourceURL:            "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/file.rb",
	}
	output := artifact.DownloadOutput{
		LocalFilePath: input.DestinationDirectory,
	}

	s3Object := s3util.AmazonS3URL{
		IsValidS3URI: true,
		IsPathStyle:  true,
		Bucket:       "ssm-test-agent-bucket",
		Key:          "mydummyfolder/file.rb",
		Region:       "us-east-1",
	}
	var folders []string
	depMock.On("Download", contextMock, input).Return(output, nil)
	depMock.On("ListS3Directory", contextMock, s3Object).Return(folders, nil)

	fileMock.On("MoveAndRenameFile", ".", "destination", ".", "file.rb").Return(true, nil)

	dep = depMock
	err, result := resource.DownloadRemoteResource(fileMock, "destination")

	assert.NoError(t, err)
	depMock.AssertExpectations(t)
	fileMock.AssertExpectations(t)
	assert.NotNil(t, result)
	assert.Equal(t, 1, len(result.Files))
	assert.Equal(t, filepath.Join("destination", "file.rb"), result.Files[0])
}

func TestS3Resource_DownloadDirectory(t *testing.T) {
	depMock := new(s3resource.S3DepMock)
	locationInfo := `{
		"Path" : "https://s3.amazonaws.com/ssm-test-agent-bucket/foldername"
	}`
	downloadsDirectory := strings.TrimSuffix(appconfig.DownloadRoot, string(os.PathSeparator))
	fileMock := &filemock.FileSystemMock{}
	resource, _ := NewS3Resource(contextMock, locationInfo)

	input1 := artifact.DownloadInput{
		DestinationDirectory: downloadsDirectory,
		SourceURL:            "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket/foldername/filename.ps",
	}
	input2 := artifact.DownloadInput{
		DestinationDirectory: downloadsDirectory,
		SourceURL:            "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket/foldername/anotherfile.ps",
	}
	s3Object := s3util.AmazonS3URL{
		IsValidS3URI: true,
		IsPathStyle:  true,
		Bucket:       "ssm-test-agent-bucket",
		Key:          "foldername",
		Region:       "us-east-1",
	}
	output1 := artifact.DownloadOutput{
		LocalFilePath: filepath.Join(input1.DestinationDirectory, "randomfilename"),
	}
	output2 := artifact.DownloadOutput{
		LocalFilePath: filepath.Join(input2.DestinationDirectory, "anotherrandomfile"),
	}
	var folders []string
	folders = append(folders, "foldername/filename.ps")
	folders = append(folders, "foldername/anotherfile.ps")
	depMock.On("Download", contextMock, input1).Return(output1, nil).Once()
	depMock.On("Download", contextMock, input2).Return(output2, nil).Once()
	depMock.On("ListS3Directory", contextMock, s3Object).Return(folders, nil)

	fileMock.On("MoveAndRenameFile", downloadsDirectory, "randomfilename", downloadsDirectory, "filename.ps").Return(true, nil)
	fileMock.On("MoveAndRenameFile", downloadsDirectory, "anotherrandomfile", downloadsDirectory, "anotherfile.ps").Return(true, nil)

	dep = depMock
	err, result := resource.DownloadRemoteResource(fileMock, "")

	assert.NoError(t, err)
	depMock.AssertExpectations(t)
	fileMock.AssertExpectations(t)
	assert.NotNil(t, result)
	assert.Equal(t, 2, len(result.Files))
	assert.Equal(t, filepath.Join(downloadsDirectory, "filename.ps"), result.Files[0])
	assert.Equal(t, filepath.Join(downloadsDirectory, "anotherfile.ps"), result.Files[1])
}

func TestS3Resource_DownloadDirectoryWithSubFolders(t *testing.T) {
	depMock := new(s3resource.S3DepMock)
	locationInfo := `{
		"Path" : "https://s3.amazonaws.com/ssm-test-agent-bucket/foldername"
	}`
	downloadsDirectory := strings.TrimSuffix(appconfig.DownloadRoot, string(os.PathSeparator))
	fileMock := &filemock.FileSystemMock{}
	resource, _ := NewS3Resource(contextMock, locationInfo)

	input1 := artifact.DownloadInput{
		DestinationDirectory: downloadsDirectory,
		SourceURL:            "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket/foldername/filename.ps",
	}
	input2 := artifact.DownloadInput{
		DestinationDirectory: downloadsDirectory,
		SourceURL:            "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket/foldername/anotherfile.ps",
	}
	input3 := artifact.DownloadInput{
		DestinationDirectory: strings.TrimSuffix(filepath.Join(appconfig.DownloadRoot, "subfolder"), string(os.PathSeparator)),
		SourceURL:            "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket/foldername/subfolder/file.ps",
	}
	s3Object := s3util.AmazonS3URL{
		IsValidS3URI: true,
		IsPathStyle:  true,
		Bucket:       "ssm-test-agent-bucket",
		Key:          "foldername",
		Region:       "us-east-1",
	}
	output1 := artifact.DownloadOutput{
		LocalFilePath: filepath.Join(input1.DestinationDirectory, "randomfilename"),
	}
	output2 := artifact.DownloadOutput{
		LocalFilePath: filepath.Join(input2.DestinationDirectory, "anotherrandomfile"),
	}
	output3 := artifact.DownloadOutput{
		LocalFilePath: filepath.Join(input3.DestinationDirectory, "justanumber"),
	}

	var folders []string
	folders = append(folders, "foldername/filename.ps")
	folders = append(folders, "foldername/anotherfile.ps")
	folders = append(folders, "foldername/subfolder/")
	folders = append(folders, "foldername/subfolder/file.ps")
	depMock.On("Download", contextMock, input1).Return(output1, nil).Once()
	depMock.On("Download", contextMock, input2).Return(output2, nil).Once()
	depMock.On("Download", contextMock, input3).Return(output3, nil).Once()
	depMock.On("ListS3Directory", contextMock, s3Object).Return(folders, nil)
	fileMock.On("MoveAndRenameFile", downloadsDirectory, "randomfilename", downloadsDirectory, "filename.ps").Return(true, nil)
	fileMock.On("MoveAndRenameFile", downloadsDirectory, "anotherrandomfile", downloadsDirectory, "anotherfile.ps").Return(true, nil)
	fileMock.On("MoveAndRenameFile", filepath.Join(downloadsDirectory, "subfolder"), "justanumber", filepath.Join(downloadsDirectory, "subfolder"), "file.ps").Return(true, nil)

	dep = depMock
	err, result := resource.DownloadRemoteResource(fileMock, "")

	assert.NoError(t, err)
	depMock.AssertExpectations(t)
	fileMock.AssertExpectations(t)
	assert.NotNil(t, result)
	assert.Equal(t, 3, len(result.Files))
	assert.Equal(t, filepath.Join(downloadsDirectory, "filename.ps"), result.Files[0])
	assert.Equal(t, filepath.Join(downloadsDirectory, "anotherfile.ps"), result.Files[1])
	assert.Equal(t, filepath.Join(downloadsDirectory, "subfolder", "file.ps"), result.Files[2])
}

func TestS3Resource_DownloadAbsPath(t *testing.T) {
	depMock := new(s3resource.S3DepMock)
	locationInfo := `{
		"path" : "https://s3.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/filename.ps"
	}`
	fileMock := &filemock.FileSystemMock{}

	fileMock.On("IsDirectory", "/var/tmp/foldername").Return(true)
	fileMock.On("Exists", "/var/tmp/foldername").Return(true)
	resource, _ := NewS3Resource(contextMock, locationInfo)
	resource.s3Object.Bucket = "ssm-test-agent-bucket"
	resource.s3Object.Key = "mydummyfolder/filename.ps"
	resource.s3Object.Region = "us-east-1"
	resource.s3Object.IsPathStyle = true
	resource.s3Object.IsValidS3URI = true

	input := artifact.DownloadInput{
		DestinationDirectory: "/var/tmp/foldername",
		SourceURL:            "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/filename.ps",
	}
	output := artifact.DownloadOutput{
		LocalFilePath: filepath.Join(input.DestinationDirectory, "justanumber"),
	}

	var folders []string

	depMock.On("ListS3Directory", contextMock, resource.s3Object).Return(folders, nil).Once()
	depMock.On("Download", contextMock, input).Return(output, nil).Once()

	fileMock.On("MoveAndRenameFile", filepath.Join("/var", "tmp", "foldername"), "justanumber", filepath.Join("/var", "tmp", "foldername"), "filename.ps").Return(true, nil)

	dep = depMock
	err, result := resource.DownloadRemoteResource(fileMock, "/var/tmp/foldername")

	assert.NoError(t, err)
	depMock.AssertExpectations(t)
	fileMock.AssertExpectations(t)
	assert.NotNil(t, result)
	assert.Equal(t, 1, len(result.Files))
	assert.Equal(t, filepath.Join("/var", "tmp", "foldername", "filename.ps"), result.Files[0])
}

func TestS3Resource_DownloadRelativePathNameChange(t *testing.T) {

	depMock := new(s3resource.S3DepMock)
	locationInfo := `{
		"path" : "https://s3.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/file.rb"
	}`
	fileMock := &filemock.FileSystemMock{}

	fileMock.On("Exists", "destination").Return(false)
	resource, _ := NewS3Resource(contextMock, locationInfo)

	input := artifact.DownloadInput{
		DestinationDirectory: ".",
		SourceURL:            "https://s3.us-east-1.amazonaws.com/ssm-test-agent-bucket/mydummyfolder/file.rb",
	}
	output := artifact.DownloadOutput{
		LocalFilePath: filepath.Join(input.DestinationDirectory, "random"),
	}

	s3Object := s3util.AmazonS3URL{
		IsValidS3URI: true,
		IsPathStyle:  true,
		Bucket:       "ssm-test-agent-bucket",
		Key:          "mydummyfolder/file.rb",
		Region:       "us-east-1",
	}
	var folders []string
	depMock.On("Download", contextMock, input).Return(output, nil)
	depMock.On("ListS3Directory", contextMock, s3Object).Return(folders, nil)

	fileMock.On("MoveAndRenameFile", ".", "random", ".", "destination").Return(true, nil)

	dep = depMock
	err, result := resource.DownloadRemoteResource(fileMock, "destination")

	assert.NoError(t, err)
	depMock.AssertExpectations(t)
	fileMock.AssertExpectations(t)
	assert.NotNil(t, result)
	assert.Equal(t, 1, len(result.Files))
	assert.Equal(t, "destination", result.Files[0])
}
