// Copyright 2015-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/
//
// 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 ecr

import (
	"errors"
	"testing"

	mock_login "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/ecr/mock/credential-helper"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/ecr/mock/sdk"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/config"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecr"
	login "github.com/awslabs/amazon-ecr-credential-helper/ecr-login/api"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
)

const (
	registryID     = "123456789012"
	repositoryName = "repo-name"
	imageDigest    = "sha:256"
	registryURI    = registryID +".dkr.ecr.us-west-2.amazonaws.com"
)

func TestGetAuthorizationToken(t *testing.T) {
	_, mockLogin, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	username := "username"
	password := "password"
	registry := "proxyEndpoint"
	proxyEndpoint := "https://" + registry
	mockLogin.EXPECT().GetCredentials(gomock.Any()).Return(&login.Auth{
		Username:      username,
		Password:      password,
		ProxyEndpoint: proxyEndpoint,
	}, nil)

	ecrAuth, err := client.GetAuthorizationToken(registryURI)
	assert.NoError(t, err, "GetAuthorizationToken")
	assert.Equal(t, username, ecrAuth.Username, "Expected username to match")
	assert.Equal(t, password, ecrAuth.Password, "Expected password to match")
	assert.Equal(t, proxyEndpoint, ecrAuth.ProxyEndpoint, "Expected proxyEndpoint to match")
	assert.Equal(t, registry, ecrAuth.Registry, "Expected registry to match")
}

func TestGetAuthorizationTokenErrorCase(t *testing.T) {
	_, mockLogin, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	mockLogin.EXPECT().GetCredentials(gomock.Any()).Return(nil, errors.New("something failed"))

	_, err := client.GetAuthorizationToken(registryURI)
	assert.Error(t, err, "Expected error while GetAuthorizationToken is called")
}

func TestGetAuthorizationTokenByID(t *testing.T) {
	_, mockLogin, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	username := "username"
	password := "password"
	registry := "proxyEndpoint"
	proxyEndpoint := "https://" + registry
	mockLogin.EXPECT().GetCredentialsByRegistryID(gomock.Any()).Return(&login.Auth{
		Username:      username,
		Password:      password,
		ProxyEndpoint: proxyEndpoint,
	}, nil)

	ecrAuth, err := client.GetAuthorizationTokenByID(registryID)
	assert.NoError(t, err, "GetAuthorizationToken")
	assert.Equal(t, username, ecrAuth.Username, "Expected username to match")
	assert.Equal(t, password, ecrAuth.Password, "Expected password to match")
	assert.Equal(t, proxyEndpoint, ecrAuth.ProxyEndpoint, "Expected proxyEndpoint to match")
	assert.Equal(t, registry, ecrAuth.Registry, "Expected registry to match")
}

func TestGetAuthorizationTokenByIDErrorCase(t *testing.T) {
	_, mockLogin, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	mockLogin.EXPECT().GetCredentialsByRegistryID(gomock.Any()).Return(nil, errors.New("something failed"))

	_, err := client.GetAuthorizationTokenByID(registryID)
	assert.Error(t, err, "Expected error while GetAuthorizationToken is called")
}

func TestRepositoryExists(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	mockEcr.EXPECT().DescribeRepositories(gomock.Any()).Do(func(input interface{}) {
		req := input.(*ecr.DescribeRepositoriesInput)
		assert.Equal(t, repositoryName, aws.StringValue(req.RepositoryNames[0]), "Expected repositoryName to match")
	}).Return(nil, errors.New("something failed"))

	exists := client.RepositoryExists(repositoryName)
	assert.False(t, exists, "RepositoryExists should return false")
}

func TestRepositoryDoesNotExists(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	mockEcr.EXPECT().DescribeRepositories(gomock.Any()).Return(&ecr.DescribeRepositoriesOutput{}, nil)

	exists := client.RepositoryExists(repositoryName)
	assert.True(t, exists, "RepositoryExists should return true")
}

func TestCreateRepository(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	mockEcr.EXPECT().CreateRepository(gomock.Any()).Do(func(input interface{}) {
		req := input.(*ecr.CreateRepositoryInput)
		assert.Equal(t, repositoryName, aws.StringValue(req.RepositoryName), "Expected repositoryName to match")
	}).Return(&ecr.CreateRepositoryOutput{Repository: &ecr.Repository{RepositoryName: aws.String(repositoryName)}}, nil)

	_, err := client.CreateRepository(repositoryName)
	assert.NoError(t, err, "Create Repository should not fail")
}

func TestCreateRepositoryErrorCase(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	mockEcr.EXPECT().CreateRepository(gomock.Any()).Return(nil, errors.New("something failed"))

	_, err := client.CreateRepository(repositoryName)
	assert.Error(t, err, "Expected error while CreateRepository is called")
}

func TestGetImages(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	repositoryNames := []*string{aws.String(repositoryName)}
	imageDetail := &ecr.ImageDetail{RepositoryName: aws.String(repositoryName), ImageDigest: aws.String(imageDigest)}
	tagStatus := ecr.TagStatusTagged

	// Does not call describeRepositories when repositoryNames are specified
	mockEcr.EXPECT().DescribeImagesPages(gomock.Any(), gomock.Any()).Do(func(x, y interface{}) {
		req := x.(*ecr.DescribeImagesInput)
		assert.Equal(t, repositoryName, aws.StringValue(req.RepositoryName), "Expected repository name to match")
		assert.NotNil(t, req.Filter, "Expected filter not to be empty")
		assert.Equal(t, tagStatus, aws.StringValue(req.Filter.TagStatus), "Expected tag status to match")
		assert.Equal(t, registryID, aws.StringValue(req.RegistryId), "Expected registry id to match")

		funct := y.(func(output *ecr.DescribeImagesOutput, lastPage bool) bool)
		funct(&ecr.DescribeImagesOutput{ImageDetails: []*ecr.ImageDetail{imageDetail}}, true)
	}).Return(nil)

	err := client.GetImages(repositoryNames, tagStatus, registryID, func(imageDetails []*ecr.ImageDetail) error {
		assert.Equal(t, 1, len(imageDetails), "Expected imageDetails to be 1")
		assert.Equal(t, imageDetail.ImageDigest, imageDetails[0].ImageDigest, "Expected image digest to match")
		assert.Equal(t, imageDetail.RepositoryName, imageDetails[0].RepositoryName, "Expected repository name to match")
		return nil
	})
	assert.NoError(t, err, "Get Images should not fail")
}

func TestGetImagesPagination(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	repos := []*ecr.Repository{
		&ecr.Repository{RepositoryName: aws.String("repo1")},
		&ecr.Repository{RepositoryName: aws.String("repo2")},
	}
	images := []*ecr.ImageDetail{
		&ecr.ImageDetail{ImageDigest: aws.String("sha:256first")},
		&ecr.ImageDetail{ImageDigest: aws.String("sha:256second")},
	}

	// Returns 2 repositories
	mockEcr.EXPECT().DescribeRepositoriesPages(gomock.Any(), gomock.Any()).Do(func(_, y interface{}) {
		funct := y.(func(output *ecr.DescribeRepositoriesOutput, lastPage bool) bool)
		callNextPage := funct(&ecr.DescribeRepositoriesOutput{Repositories: repos}, false)
		assert.True(t, callNextPage, "Expected to call next page")
	}).Return(nil)
	// Returns 1 image without pagination (lastPage=true)
	mockEcr.EXPECT().DescribeImagesPages(gomock.Any(), gomock.Any()).Do(func(x, y interface{}) {
		// Expects to be the first repo returned
		req := x.(*ecr.DescribeImagesInput)
		assert.Equal(t, repos[0].RepositoryName, req.RepositoryName, "Expected repository name to match")

		funct := y.(func(output *ecr.DescribeImagesOutput, lastPage bool) bool)
		funct(&ecr.DescribeImagesOutput{
			ImageDetails: []*ecr.ImageDetail{images[0]},
		}, true)
	}).Return(nil)
	// Returns 1 image with pagination (lastPage=false)
	mockEcr.EXPECT().DescribeImagesPages(gomock.Any(), gomock.Any()).Do(func(x, y interface{}) {
		// Expects to be the second repo returned
		req := x.(*ecr.DescribeImagesInput)
		assert.Equal(t, repos[1].RepositoryName, req.RepositoryName, "Expected repository name to match")

		funct := y.(func(output *ecr.DescribeImagesOutput, lastPage bool) bool)
		callNextPage := funct(&ecr.DescribeImagesOutput{
			ImageDetails: []*ecr.ImageDetail{images[1]},
		}, false)
		assert.True(t, callNextPage, "Expected to call next page")
	}).Return(nil)

	count := 0
	err := client.GetImages(nil, "", "", func(imageDetails []*ecr.ImageDetail) error {
		assert.Equal(t, 1, len(imageDetails), "Expected imageDetails to be 1")
		assert.Equal(t, images[count].ImageDigest, imageDetails[0].ImageDigest, "Expected image digest to match")
		count++
		return nil
	})
	assert.NoError(t, err, "Get Images should not fail")
}

func TestGetImagesErrorCase(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	repositoryNames := []*string{aws.String(repositoryName)}
	repositoryDetail := &ecr.Repository{RepositoryName: repositoryNames[0], RegistryId: aws.String(registryID)}
	imageDetail := &ecr.ImageDetail{RepositoryName: aws.String(repositoryName), ImageDigest: aws.String(imageDigest)}

	mockEcr.EXPECT().DescribeRepositoriesPages(gomock.Any(), gomock.Any()).Do(func(_, y interface{}) {
		funct := y.(func(output *ecr.DescribeRepositoriesOutput, lastPage bool) bool)
		funct(&ecr.DescribeRepositoriesOutput{Repositories: []*ecr.Repository{repositoryDetail}}, true)
	}).Return(nil)

	mockEcr.EXPECT().DescribeImagesPages(gomock.Any(), gomock.Any()).Do(func(_, y interface{}) {
		funct := y.(func(output *ecr.DescribeImagesOutput, lastPage bool) bool)
		funct(&ecr.DescribeImagesOutput{ImageDetails: []*ecr.ImageDetail{imageDetail}}, true)
	}).Return(nil)

	err := client.GetImages(nil, "", "", func(imageDetails []*ecr.ImageDetail) error {
		return errors.New("something failed")
	})
	assert.Error(t, err, "Get Images should not fail")
}

func TestDescribeRepositoriesErrorCase(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	mockEcr.EXPECT().DescribeRepositoriesPages(gomock.Any(), gomock.Any()).Return(errors.New("something failed"))

	err := client.GetImages(nil, "", "", nil)
	assert.Error(t, err, "Get Images should fail")
}

func TestDescribeImageErrorCase(t *testing.T) {
	mockEcr, _, client, ctrl := setupTestController(t)
	defer ctrl.Finish()

	repositoryNames := []*string{aws.String(repositoryName)}
	repositoryDetail := &ecr.Repository{RepositoryName: repositoryNames[0], RegistryId: aws.String(registryID)}

	mockEcr.EXPECT().DescribeRepositoriesPages(gomock.Any(), gomock.Any()).Do(func(_, y interface{}) {
		funct := y.(func(output *ecr.DescribeRepositoriesOutput, lastPage bool) bool)
		funct(&ecr.DescribeRepositoriesOutput{Repositories: []*ecr.Repository{repositoryDetail}}, true)
	}).Return(nil)
	mockEcr.EXPECT().DescribeImagesPages(gomock.Any(), gomock.Any()).Return(errors.New("something failed"))

	err := client.GetImages(nil, "", "", nil)
	assert.Error(t, err, "Get Images should fail")
}

func setupTestController(t *testing.T) (*mock_ecriface.MockECRAPI, *mock_login.MockClient, Client, *gomock.Controller) {
	ctrl := gomock.NewController(t)
	mockEcr := mock_ecriface.NewMockECRAPI(ctrl)
	mockLogin := mock_login.NewMockClient(ctrl)
	mockSession, err := session.NewSession()
	assert.NoError(t, err, "Unexpected error in creating session")

	client := newClient(&config.CommandConfig{Session: mockSession}, mockEcr, mockLogin)

	return mockEcr, mockLogin, client, ctrl
}
