/*
 * Copyright 2019 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/
 *
 * or in the "license" file accompanying this file. This file 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 privategit

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/aws/amazon-ssm-agent/agent/fileutil"
	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/gitresource"
	"github.com/aws/amazon-ssm-agent/agent/plugins/downloadcontent/gitresource/privategit/handler"
	"github.com/aws/amazon-ssm-agent/agent/plugins/downloadcontent/gitresource/privategit/handler/core"
	handlermock "github.com/aws/amazon-ssm-agent/agent/plugins/downloadcontent/gitresource/privategit/handler/mock"
	bridgemock "github.com/aws/amazon-ssm-agent/agent/ssm/ssmparameterresolver/mock"
	gogit "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing/transport/http"
	"github.com/stretchr/testify/assert"
)

var contextMock = context.NewMockDefault()
var logMock = contextMock.Log()
var bm = bridgemock.GetSsmParamResolverBridge(map[string]string{})

var downloadRemoteResourceDestPath = os.TempDir()
var downloadRemoteResourceTempCloneDir = filepath.Join(downloadRemoteResourceDestPath, "tempCloneDir")
var downloadRemoteResourceTestFile = filepath.Join(downloadRemoteResourceDestPath, "testFile")

func CollectFilesAndRebaseTest(sourceDir, rebaseToDir string) (files []string, err error) {
	return []string{downloadRemoteResourceTestFile}, nil
}

func MoveFilesTest(sourceDir, destDir string) error {
	return nil
}

func getString(obj interface{}) string {
	return fmt.Sprintf("%v", obj)
}

func TestNewGitResource(t *testing.T) {
	testGitHandler, err := handler.NewGitHandler("private-git-repo", handler.GitAuthConfig{
		PrivateSSHKey:       "private-ssh-key",
		SkipHostKeyChecking: true,
		Username:            "admin",
		Password:            "pwd",
	}, gitresource.CheckoutOptions{
		Branch:   "master",
		CommitID: "",
	}, bm)

	assert.NoError(t, err)

	tests := []struct {
		sourceInfo  string
		gitResource *GitResource
		err         error
	}{
		{
			`{
				"repository": "private-git-repo,
			}`,
			nil,
			errors.New("SourceInfo could not be unmarshalled for source type Git: " +
				"invalid character '\\n' in string literal"),
		},
		{
			`{
					"repository": "private-git-repo",
					"getOptions": "test"
				}`,
			nil,
			errors.New("SourceInfo could not be unmarshalled for source type Git: " +
				"getOptions is not specified in the right format"),
		},
		{
			`{
					"repository": "private-git-repo",
					"getOptions": "test"
				}`,
			nil,
			errors.New("SourceInfo could not be unmarshalled for source type Git: " +
				"getOptions is not specified in the right format"),
		},
		{
			`{
					"repository": "http:// test"
				}`,
			nil,
			errors.New("Invalid repository url format: parse \"http:// test\": invalid character \" \" in host name"),
		},
		{
			`{
				"repository": "private-git-repo",
				"privateSSHKey": "private-ssh-key",
				"skipHostKeyChecking": true,
				"username": "admin",
				"password": "pwd",
				"getOptions": "branch:master"
			}`,
			&GitResource{
				context: contextMock,
				Handler: testGitHandler,
			},
			nil,
		},
	}

	for _, test := range tests {
		gitResource, err := NewGitResource(contextMock, test.sourceInfo, bm)

		if test.err != nil {
			assert.Nil(t, gitResource)
			assert.Error(t, err, getString(test))
			assert.EqualError(t, err, test.err.Error())
		} else {
			assert.NoError(t, err, getString(test))
			assert.Equal(t, test.gitResource, gitResource, getString(test))
		}
	}
}

func TestNewGitResource_parseSourceInfo(t *testing.T) {
	tests := []struct {
		sourceInfo string
		gitInfo    GitInfo
		err        error
	}{
		{
			`{
				"repository": "private-git-repo",
				"privateSSHKey": "--key--",
				"skipHostKeyChecking": true,
				"username": "admin",
				"password": "pwd",
				"getOptions": "branch:master"
			}`,
			GitInfo{
				Repository:          "private-git-repo",
				PrivateSSHKey:       "--key--",
				SkipHostKeyChecking: true,
				Username:            "admin",
				Password:            "pwd",
				GetOptions:          "branch:master",
			},
			nil,
		},
		{
			`{
				"repository": "git://"
			}`,
			GitInfo{
				Repository:          "git://",
				SkipHostKeyChecking: false,
			},
			nil,
		},
		{
			fmt.Sprintf(`{
				"repository": "git://",
				"privateSSHKey": "%s"

			}`, "private-ssh-key"),
			GitInfo{
				Repository:          "git://",
				PrivateSSHKey:       "private-ssh-key",
				SkipHostKeyChecking: false,
			},
			nil,
		},
		{
			`{
				"repository": "git://
			}`,
			GitInfo{},
			errors.New("invalid character '\\n' in string literal"),
		},
	}

	for _, test := range tests {
		gitInfo, err := parseSourceInfo(test.sourceInfo)

		if test.err != nil {
			assert.Equal(t, GitInfo{}, gitInfo)
			assert.Error(t, err, getString(test))
			assert.EqualError(t, err, test.err.Error())
		} else {
			assert.NoError(t, err, getString(test))
			assert.Equal(t, test.gitInfo, gitInfo, getString(test))
		}
	}
}

func TestGitResource_ValidateLocationInfo(t *testing.T) {
	gitHandlerMock := handlermock.GitHandlerMock{}
	gitHandlerMock.On("Validate").Return(true, nil).Once()

	resource := GitResource{
		context: contextMock,
		Handler: &gitHandlerMock,
	}

	_, _ = resource.ValidateLocationInfo()
	gitHandlerMock.AssertExpectations(t)
}

func TestGitResource_DownloadRemoteResource(t *testing.T) {
	authMethod := &http.BasicAuth{}
	repository := &gogit.Repository{}

	fileSysMock := filemock.FileSystemMock{}
	fileSysMock.On("MakeDirs", os.TempDir()).Return(nil).Once()
	fileSysMock.On("CreateTempDir", os.TempDir(), "tempCloneDir").Return(downloadRemoteResourceTempCloneDir, nil).Once()
	fileSysMock.On("DeleteDirectory", downloadRemoteResourceTempCloneDir).Return(nil).Once()

	collectFilesAndRebaseFunction = CollectFilesAndRebaseTest
	moveFilesFunction = MoveFilesTest

	gitHandlerMock := handlermock.GitHandlerMock{}
	gitHandlerMock.On("GetAuthMethod", logMock).Return(authMethod, nil).Once()
	gitHandlerMock.On("CloneRepository", logMock, authMethod, downloadRemoteResourceTempCloneDir).Return(repository, nil).Once()
	gitHandlerMock.On("PerformCheckout", core.NewGitRepository(repository)).Return(nil).Once()

	resource := GitResource{
		context: contextMock,
		Handler: &gitHandlerMock,
	}

	err, result := resource.DownloadRemoteResource(&fileSysMock, downloadRemoteResourceDestPath)

	assert.NoError(t, err)
	assert.Equal(t, []string{downloadRemoteResourceTestFile}, result.Files)

	gitHandlerMock.AssertExpectations(t)
	fileSysMock.AssertExpectations(t)

	collectFilesAndRebaseFunction = fileutil.CollectFilesAndRebase
	moveFilesFunction = fileutil.MoveFiles
}
