// Copyright 2015-2018 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 utils

import (
	"errors"

	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/adapter"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ecs"
)

const (
	defaultMemLimit = 512
)

type taskLevelValues struct {
	MemLimit string
}

// reconcileContainerDef transforms each service in docker-compose.yml and
// ecs-params.yml to an equivalent ECS container definition
func reconcileContainerDef(inputCfg *adapter.ContainerConfig, ecsConDef *ContainerDef, taskVals taskLevelValues) (*ecs.ContainerDefinition, error) {
	outputContDef := &ecs.ContainerDefinition{}

	// Populate ECS container definition, offloading the validation to aws-sdk
	outputContDef.SetCommand(aws.StringSlice(inputCfg.Command))
	outputContDef.SetDnsSearchDomains(aws.StringSlice(inputCfg.DNSSearchDomains))
	outputContDef.SetDnsServers(aws.StringSlice(inputCfg.DNSServers))
	outputContDef.SetDockerLabels(inputCfg.DockerLabels)
	outputContDef.SetDockerSecurityOptions(aws.StringSlice(inputCfg.DockerSecurityOptions))
	outputContDef.SetEntryPoint(aws.StringSlice(inputCfg.Entrypoint))
	outputContDef.SetEnvironment(inputCfg.Environment)
	outputContDef.SetExtraHosts(inputCfg.ExtraHosts)
	if inputCfg.Hostname != "" {
		outputContDef.SetHostname(inputCfg.Hostname)
	}
	outputContDef.SetImage(inputCfg.Image)
	outputContDef.SetLinks(aws.StringSlice(inputCfg.Links)) // TODO, read from external links
	outputContDef.SetLogConfiguration(inputCfg.LogConfiguration)
	outputContDef.SetMountPoints(inputCfg.MountPoints)
	outputContDef.SetName(inputCfg.Name)
	outputContDef.SetPrivileged(inputCfg.Privileged)
	outputContDef.SetPortMappings(inputCfg.PortMappings)
	outputContDef.SetPseudoTerminal(inputCfg.PseudoTerminal)
	outputContDef.SetReadonlyRootFilesystem(inputCfg.ReadOnly)
	outputContDef.SetUlimits(inputCfg.Ulimits)

	if inputCfg.User != "" {
		outputContDef.SetUser(inputCfg.User)
	}
	outputContDef.SetVolumesFrom(inputCfg.VolumesFrom)
	if inputCfg.WorkingDirectory != "" {
		outputContDef.SetWorkingDirectory(inputCfg.WorkingDirectory)
	}

	// Set Linux Parameters
	outputContDef.LinuxParameters = &ecs.LinuxParameters{Capabilities: &ecs.KernelCapabilities{}}
	if inputCfg.CapAdd != nil {
		outputContDef.LinuxParameters.Capabilities.SetAdd(aws.StringSlice(inputCfg.CapAdd))
	}
	if inputCfg.CapDrop != nil {
		outputContDef.LinuxParameters.Capabilities.SetDrop(aws.StringSlice(inputCfg.CapDrop))
	}
	if inputCfg.Devices != nil {
		outputContDef.LinuxParameters.SetDevices(inputCfg.Devices)
	}
	if ecsConDef.InitProcessEnabled != false {
		outputContDef.LinuxParameters.SetInitProcessEnabled(ecsConDef.InitProcessEnabled)
	}

	// Only set shmSize if specified. Docker will by default allocate 64M
	// for shared memory if shmSize is null.
	if inputCfg.ShmSize != 0 {
		outputContDef.LinuxParameters.SetSharedMemorySize(inputCfg.ShmSize)
	}

	if inputCfg.StopTimeout != nil {
		outputContDef.SetStopTimeout(*inputCfg.StopTimeout)
	}

	// Only set tmpfs if tmpfs mounts are specified.
	if inputCfg.Tmpfs != nil { // TODO: will never be nil?
		outputContDef.LinuxParameters.SetTmpfs(inputCfg.Tmpfs)
	}

	// initialize container resources from inputCfg
	cpu := inputCfg.CPU
	memLimit := inputCfg.Memory
	memRes := inputCfg.MemoryReservation
	healthCheck := inputCfg.HealthCheck
	var resourceRequirements []*ecs.ResourceRequirement

	if ecsConDef != nil {
		outputContDef.Essential = aws.Bool(ecsConDef.Essential)

		// CPU and Memory are expected to be set here if compose v3 was used
		cpu = resolveIntResourceOverride(inputCfg.Name, cpu, ecsConDef.Cpu, "CPU")

		ecsMemInMB := adapter.ConvertToMemoryInMB(int64(ecsConDef.Memory))
		memLimit = resolveIntResourceOverride(inputCfg.Name, memLimit, ecsMemInMB, "MemoryLimit")

		ecsMemResInMB := adapter.ConvertToMemoryInMB(int64(ecsConDef.MemoryReservation))

		memRes = resolveIntResourceOverride(inputCfg.Name, memRes, ecsMemResInMB, "MemoryReservation")

		credParam := ecsConDef.RepositoryCredentials.CredentialsParameter

		if ecsConDef.FirelensConfiguration.Type != "" {
			outputContDef.SetFirelensConfiguration(convertToECSFirelensConfiguration(ecsConDef.FirelensConfiguration))
		}

		if credParam != "" {
			outputContDef.RepositoryCredentials = &ecs.RepositoryCredentials{}
			outputContDef.RepositoryCredentials.SetCredentialsParameter(credParam)
		}

		if len(ecsConDef.Secrets) > 0 {
			outputContDef.SetSecrets(convertToECSSecrets(ecsConDef.Secrets))
		}

		if len(ecsConDef.ContainerDependencies) > 0 {
			outputContDef.SetDependsOn(convertToECSContainerDependency(ecsConDef.ContainerDependencies))
		}

		if len(ecsConDef.Logging.SecretOptions) > 0 {
			convertedSecrets := convertToECSSecrets(ecsConDef.Logging.SecretOptions)
			outputContDef.LogConfiguration.SetSecretOptions(convertedSecrets)
		}

		var err error
		healthCheck, err = resolveHealthCheck(inputCfg.Name, healthCheck, ecsConDef.HealthCheck)
		if err != nil {
			return nil, err
		}

		if ecsConDef.GPU != "" {
			resourceType := ecs.ResourceTypeGpu
			resourceRequirement := ecs.ResourceRequirement{
				Type:  &resourceType,
				Value: &ecsConDef.GPU,
			}
			resourceRequirements = append(resourceRequirements, &resourceRequirement)
		}
	}

	// At least one memory value is required to register a task definition.
	// If no memory value is set, set default limit
	if memLimit == 0 && memRes == 0 && taskVals.MemLimit == "" {
		memLimit = defaultMemLimit
	}

	// if memLimit is set and less than memRes, show error
	if memLimit != 0 && memLimit < memRes {
		return nil, errors.New("mem_limit must be greater than mem_reservation")
	}

	if memLimit != 0 {
		outputContDef.SetMemory(memLimit)
	}

	if memRes != 0 {
		outputContDef.SetMemoryReservation(memRes)
	}

	outputContDef.SetCpu(cpu)

	if healthCheck != nil {
		outputContDef.SetHealthCheck(healthCheck)
	}

	if len(resourceRequirements) > 0 {
		outputContDef.SetResourceRequirements(resourceRequirements)
	}

	return outputContDef, nil
}
