// 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 task

import (
	"flag"
	"testing"

	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/context"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/entity"
	mock_ecs "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/ecs/mock"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/config"
	utils "github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/compose"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/urfave/cli"
)

func TestTaskCreate(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	taskDefinition := ecs.TaskDefinition{
		Family:               aws.String("family"),
		ContainerDefinitions: []*ecs.ContainerDefinition{},
		Volumes:              []*ecs.Volume{},
	}
	respTaskDef := taskDefinition
	respTaskDef.TaskDefinitionArn = aws.String("taskDefinitionArn")

	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	mockEcs.EXPECT().RegisterTaskDefinitionIfNeeded(gomock.Any(), gomock.Any()).Do(func(x, y interface{}) {
		// verify input fields
		req := x.(*ecs.RegisterTaskDefinitionInput)
		assert.Equal(t, aws.StringValue(taskDefinition.Family), aws.StringValue(req.Family), "Expected Task Definition family to match.")
	}).Return(&respTaskDef, nil)

	flagSet := flag.NewFlagSet("ecs-cli", 0)
	cliContext := cli.NewContext(nil, flagSet, nil)

	context := &context.ECSContext{
		ECSClient:     mockEcs,
		CommandConfig: &config.CommandConfig{},
		CLIContext:    cliContext,
	}
	task := NewTask(context)
	task.SetTaskDefinition(&taskDefinition)

	err := task.Create()
	assert.NoError(t, err, "Unexpected error while create")
	assert.Equal(t, aws.StringValue(respTaskDef.TaskDefinitionArn), aws.StringValue(task.TaskDefinition().TaskDefinitionArn), "Expected TaskDefArn to match.")
}

func TestTaskCreateWithTags(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	taskDefinition := ecs.TaskDefinition{
		Family:               aws.String("family"),
		ContainerDefinitions: []*ecs.ContainerDefinition{},
		Volumes:              []*ecs.Volume{},
	}
	respTaskDef := taskDefinition
	respTaskDef.TaskDefinitionArn = aws.String("taskDefinitionArn")

	flagSet := flag.NewFlagSet("ecs-cli", 0)
	flagSet.String(flags.ResourceTagsFlag, "holmes=watson", "")
	cliContext := cli.NewContext(nil, flagSet, nil)

	mockEcs := mock_ecs.NewMockECSClient(ctrl)

	context := &context.ECSContext{
		ECSClient:     mockEcs,
		CommandConfig: &config.CommandConfig{},
		CLIContext:    cliContext,
	}

	expectedTags := []*ecs.Tag{
		&ecs.Tag{
			Key:   aws.String("holmes"),
			Value: aws.String("watson"),
		},
	}

	mockEcs.EXPECT().RegisterTaskDefinitionIfNeeded(gomock.Any(), gomock.Any()).Do(func(x, y interface{}) {
		// verify input fields
		req := x.(*ecs.RegisterTaskDefinitionInput)
		assert.Equal(t, aws.StringValue(taskDefinition.Family), aws.StringValue(req.Family), "Expected Task Definition family to match.")
		assert.ElementsMatch(t, expectedTags, req.Tags, "Expected resource tags to match")
	}).Return(&respTaskDef, nil)

	task := NewTask(context)
	task.SetTaskDefinition(&taskDefinition)

	err := task.Create()
	assert.NoError(t, err, "Unexpected error while create")
	assert.Equal(t, aws.StringValue(respTaskDef.TaskDefinitionArn), aws.StringValue(task.TaskDefinition().TaskDefinitionArn), "Expected TaskDefArn to match.")
}

func TestTaskInfoFilterLocal(t *testing.T) {
	entity.TestInfo(func(context *context.ECSContext) entity.ProjectEntity {
		return NewTask(context)
	}, func(req *ecs.ListTasksInput, projectName string, t *testing.T) {
		assert.Equal(t, projectName, aws.StringValue(req.Family), "Expected Task Definition Family to be project name")
	}, t, true, "")
}

func TestTaskInfoAll(t *testing.T) {
	entity.TestInfo(func(context *context.ECSContext) entity.ProjectEntity {
		return NewTask(context)
	}, func(req *ecs.ListTasksInput, projectName string, t *testing.T) {
		assert.Nil(t, req.StartedBy, "Unexpected filter on StartedBy")
		assert.Nil(t, req.Family, "Unexpected filter on Task Definition family")
		assert.Nil(t, req.ServiceName, "Unexpected filter on Service Name")
	}, t, false, "")
}

func TestTaskInfoRunning(t *testing.T) {
	entity.TestInfo(func(context *context.ECSContext) entity.ProjectEntity {
		return NewTask(context)
	}, func(req *ecs.ListTasksInput, projectName string, t *testing.T) {
		assert.Nil(t, req.StartedBy, "Unexpected filter on StartedBy")
		assert.Nil(t, req.Family, "Unexpected filter on Task Definition family")
		assert.Nil(t, req.ServiceName, "Unexpected filter on Service Name")
		assert.Equal(t, ecs.DesiredStatusRunning, aws.StringValue(req.DesiredStatus), "Expected Desired status to match")
	}, t, false, ecs.DesiredStatusRunning)
}

func TestTaskInfoStopped(t *testing.T) {
	entity.TestInfo(func(context *context.ECSContext) entity.ProjectEntity {
		return NewTask(context)
	}, func(req *ecs.ListTasksInput, projectName string, t *testing.T) {
		assert.Nil(t, req.StartedBy, "Unexpected filter on StartedBy")
		assert.Nil(t, req.Family, "Unexpected filter on Task Definition family")
		assert.Nil(t, req.ServiceName, "Unexpected filter on Service Name")
		assert.Equal(t, ecs.DesiredStatusStopped, aws.StringValue(req.DesiredStatus), "Expected Desired status to match")
	}, t, false, ecs.DesiredStatusStopped)
}

// TODO: Test UP

// tests for helpers
func TestConvertToECSTaskOverride(t *testing.T) {
	container := "railsapp"
	command := []string{"bundle exec puma -C config/puma.rb"}

	input := map[string][]string{
		container: command,
	}

	expected := &ecs.TaskOverride{
		ContainerOverrides: []*ecs.ContainerOverride{
			{
				Name:    aws.String(container),
				Command: aws.StringSlice(command),
			},
		},
	}

	actual, err := convertToECSTaskOverride(input)

	if assert.NoError(t, err) {
		assert.Equal(t, expected, actual)
	}
}

func TestConvertToECSTaskOverride_WithNil(t *testing.T) {
	var input map[string][]string

	actual, err := convertToECSTaskOverride(input)

	if assert.NoError(t, err) {
		assert.Nil(t, actual)
	}
}

func TestBuildRuntaskInput(t *testing.T) {
	taskDef := "clydeApp"
	count := 1
	cluster := "myCluster"
	launchType := "EC2"

	flagSet := flag.NewFlagSet("ecs-cli", 0)
	cliContext := cli.NewContext(nil, flagSet, nil)
	ctrl := gomock.NewController(t)
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	context := &context.ECSContext{
		ECSClient:  mockEcs,
		CLIContext: cliContext,
		CommandConfig: &config.CommandConfig{
			Cluster:    cluster,
			LaunchType: launchType,
		},
	}

	gomock.InOrder(
		mockEcs.EXPECT().ListAccountSettings(gomock.Any()).Do(func(input interface{}) {
			req := input.(*ecs.ListAccountSettingsInput)
			assert.True(t, aws.BoolValue(req.EffectiveSettings), "Expected Effective settings to be true")
			assert.Equal(t, ecs.SettingNameTaskLongArnFormat, aws.StringValue(req.Name), "Expected setting name to be task long ARN")
		}).Return(&ecs.ListAccountSettingsOutput{
			Settings: []*ecs.Setting{
				&ecs.Setting{
					Value: aws.String("disabled"),
					Name:  aws.String(ecs.SettingNameTaskLongArnFormat),
				},
			},
		}, nil),
	)

	task := &Task{
		ecsContext: context,
	}

	req, err := task.buildRunTaskInput(taskDef, count, nil)

	if assert.NoError(t, err) {
		assert.Equal(t, aws.String(cluster), req.Cluster)
		assert.Equal(t, aws.String(taskDef), req.TaskDefinition)
		assert.Equal(t, aws.String(launchType), req.LaunchType)
		assert.Equal(t, int64(count), aws.Int64Value(req.Count))
		assert.Nil(t, req.Overrides)
		assert.False(t, aws.BoolValue(req.EnableECSManagedTags), "Expected ECS Managed tags to be disabled")
	}
}

func TestBuildRuntaskInput_WithOverride(t *testing.T) {
	taskDef := "clydeApp"
	count := 1
	cluster := "myCluster"
	container := "railsapp"
	launchType := "EC2"
	command := []string{"bundle exec puma -C config/puma.rb"}
	override := map[string][]string{
		container: command,
	}

	flagSet := flag.NewFlagSet("ecs-cli", 0)
	cliContext := cli.NewContext(nil, flagSet, nil)
	ctrl := gomock.NewController(t)
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	context := &context.ECSContext{
		ECSClient:  mockEcs,
		CLIContext: cliContext,
		CommandConfig: &config.CommandConfig{
			Cluster:    cluster,
			LaunchType: launchType,
		},
	}

	gomock.InOrder(
		mockEcs.EXPECT().ListAccountSettings(gomock.Any()).Do(func(input interface{}) {
			req := input.(*ecs.ListAccountSettingsInput)
			assert.True(t, aws.BoolValue(req.EffectiveSettings), "Expected Effective settings to be true")
			assert.Equal(t, ecs.SettingNameTaskLongArnFormat, aws.StringValue(req.Name), "Expected setting name to be task long ARN")
		}).Return(&ecs.ListAccountSettingsOutput{
			Settings: []*ecs.Setting{
				&ecs.Setting{
					Value: aws.String("disabled"),
					Name:  aws.String(ecs.SettingNameTaskLongArnFormat),
				},
			},
		}, nil),
	)

	task := &Task{
		ecsContext: context,
	}

	expectedOverride := &ecs.TaskOverride{
		ContainerOverrides: []*ecs.ContainerOverride{
			{
				Name:    aws.String("railsapp"),
				Command: aws.StringSlice(command),
			},
		},
	}

	req, err := task.buildRunTaskInput(taskDef, count, override)

	if assert.NoError(t, err) {
		assert.Equal(t, aws.String(cluster), req.Cluster)
		assert.Equal(t, aws.String(taskDef), req.TaskDefinition)
		assert.Equal(t, aws.String(launchType), req.LaunchType)
		assert.Equal(t, int64(count), aws.Int64Value(req.Count))
		assert.Equal(t, expectedOverride, req.Overrides)
		assert.False(t, aws.BoolValue(req.EnableECSManagedTags), "Expected ECS Managed tags to be disabled")
	}
}

func TestBuildRuntaskInputLongARNEnabled(t *testing.T) {
	taskDef := "clydeApp"
	count := 1
	cluster := "myCluster"
	launchType := "EC2"

	flagSet := flag.NewFlagSet("ecs-cli", 0)
	cliContext := cli.NewContext(nil, flagSet, nil)
	ctrl := gomock.NewController(t)
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	context := &context.ECSContext{
		ECSClient:  mockEcs,
		CLIContext: cliContext,
		CommandConfig: &config.CommandConfig{
			Cluster:    cluster,
			LaunchType: launchType,
		},
	}

	gomock.InOrder(
		mockEcs.EXPECT().ListAccountSettings(gomock.Any()).Do(func(input interface{}) {
			req := input.(*ecs.ListAccountSettingsInput)
			assert.True(t, aws.BoolValue(req.EffectiveSettings), "Expected Effective settings to be true")
			assert.Equal(t, ecs.SettingNameTaskLongArnFormat, aws.StringValue(req.Name), "Expected setting name to be task long ARN")
		}).Return(&ecs.ListAccountSettingsOutput{
			Settings: []*ecs.Setting{
				&ecs.Setting{
					Value: aws.String("enabled"),
					Name:  aws.String(ecs.SettingNameTaskLongArnFormat),
				},
			},
		}, nil),
	)

	task := &Task{
		ecsContext: context,
	}

	req, err := task.buildRunTaskInput(taskDef, count, nil)

	if assert.NoError(t, err) {
		assert.Equal(t, aws.String(cluster), req.Cluster)
		assert.Equal(t, aws.String(taskDef), req.TaskDefinition)
		assert.Equal(t, aws.String(launchType), req.LaunchType)
		assert.Equal(t, int64(count), aws.Int64Value(req.Count))
		assert.Nil(t, req.Overrides)
		assert.True(t, aws.BoolValue(req.EnableECSManagedTags), "Expected ECS Managed tags to be enabled")
	}
}

func TestBuildRuntaskInputManagedTagsDisabled(t *testing.T) {
	taskDef := "clydeApp"
	count := 1
	cluster := "myCluster"
	launchType := "EC2"

	flagSet := flag.NewFlagSet("ecs-cli", 0)
	flagSet.Bool(flags.DisableECSManagedTagsFlag, true, "")
	cliContext := cli.NewContext(nil, flagSet, nil)
	ctrl := gomock.NewController(t)
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	context := &context.ECSContext{
		ECSClient:  mockEcs,
		CLIContext: cliContext,
		CommandConfig: &config.CommandConfig{
			Cluster:    cluster,
			LaunchType: launchType,
		},
	}

	task := &Task{
		ecsContext: context,
	}

	req, err := task.buildRunTaskInput(taskDef, count, nil)

	if assert.NoError(t, err) {
		assert.Equal(t, aws.String(cluster), req.Cluster)
		assert.Equal(t, aws.String(taskDef), req.TaskDefinition)
		assert.Equal(t, aws.String(launchType), req.LaunchType)
		assert.Equal(t, int64(count), aws.Int64Value(req.Count))
		assert.Nil(t, req.Overrides)
		assert.Nil(t, req.EnableECSManagedTags, "Expected ECS Managed tags to be unset")
	}
}

func TestBuildRunTaskInput_EFSFargate(t *testing.T) {
	taskDef := "dogPicService"
	count := 1
	cluster := "myCluster"
	launchType := config.LaunchTypeFargate
	flagSet := flag.NewFlagSet("ecs-cli", 0)
	flagSet.Bool(flags.DisableECSManagedTagsFlag, true, "")
	cliContext := cli.NewContext(nil, flagSet, nil)
	ctrl := gomock.NewController(t)
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	context := &context.ECSContext{
		ECSClient:  mockEcs,
		CLIContext: cliContext,
		ECSParams:  ecsParamsWithEFSVolume(),
		CommandConfig: &config.CommandConfig{
			Cluster:    cluster,
			LaunchType: launchType,
		},
	}

	task := &Task{
		ecsContext: context,
	}

	req, err := task.buildRunTaskInput(taskDef, count, nil)

	if assert.NoError(t, err) {
		assert.Equal(t, aws.String(cluster), req.Cluster)
		assert.Equal(t, aws.String(taskDef), req.TaskDefinition)
		assert.Equal(t, aws.String(launchType), req.LaunchType)
		assert.Equal(t, int64(count), aws.Int64Value(req.Count))
		assert.Equal(t, aws.String(config.PlatformVersion140), req.PlatformVersion)
		assert.Nil(t, req.Overrides)
	}
}

func TestBuildRunTaskInput_EFSEC2(t *testing.T) {
	taskDef := "dogPicService"
	count := 1
	cluster := "myCluster"
	launchType := config.LaunchTypeEC2
	flagSet := flag.NewFlagSet("ecs-cli", 0)
	flagSet.Bool(flags.DisableECSManagedTagsFlag, true, "")
	cliContext := cli.NewContext(nil, flagSet, nil)
	ctrl := gomock.NewController(t)
	mockEcs := mock_ecs.NewMockECSClient(ctrl)
	context := &context.ECSContext{
		ECSClient:  mockEcs,
		CLIContext: cliContext,
		ECSParams:  ecsParamsWithEFSVolume(),
		CommandConfig: &config.CommandConfig{
			Cluster:    cluster,
			LaunchType: launchType,
		},
	}

	task := &Task{
		ecsContext: context,
	}

	req, err := task.buildRunTaskInput(taskDef, count, nil)

	if assert.NoError(t, err) {
		assert.Equal(t, aws.String(cluster), req.Cluster)
		assert.Equal(t, aws.String(taskDef), req.TaskDefinition)
		assert.Equal(t, aws.String(launchType), req.LaunchType)
		assert.Equal(t, int64(count), aws.Int64Value(req.Count))
		assert.Nil(t, req.PlatformVersion)
		assert.Nil(t, req.Overrides)
	}
}
func ecsParamsWithEFSVolume() *utils.ECSParams {
	return &utils.ECSParams{
		TaskDefinition: utils.EcsTaskDef{
			ExecutionRole: "arn:aws:iam::123456789012:role/my_execution_role",
			NetworkMode:   "awsvpc",
			TaskSize: utils.TaskSize{
				Cpu:    "512",
				Memory: "1GB",
			},
			EFSVolumes: []utils.EFSVolume{
				{
					Name:         "myVolume",
					FileSystemID: aws.String("fs-1234"),
				},
			},
		},
		RunParams: utils.RunParams{
			NetworkConfiguration: utils.NetworkConfiguration{
				AwsVpcConfiguration: utils.AwsVpcConfiguration{
					Subnets:        []string{"sg-bafff1ed", "sg-c0ffeefe"},
					AssignPublicIp: utils.Enabled,
				},
			},
		},
	}
}
