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

import (
	"reflect"
	"strings"

	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/adapter"
	"github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/value"
	"github.com/docker/cli/cli/compose/types"
	"github.com/docker/libcompose/config"
	"github.com/docker/libcompose/project"
	log "github.com/sirupsen/logrus"
)

// supported fields/options from compose 1/2 YAML file
// Ideally, we should have separate slices for Docker Compose v1 and v2 for more explicit warnings
// because certain fields were introduced after v1. However, due to the lack of popularity of v1,
// for now we combine the 2 versions.
var supportedComposeV1V2YamlOptions = []string{
	"cap_add",
	"cap_drop",
	"command",
	"cpu_shares",
	"devices",
	"dns",
	"dns_search",
	"entrypoint",
	"env_file",
	"environment",
	"extends",
	"extra_hosts",
	"hostname",
	"image",
	"labels",
	"links",
	"logging",
	"log_driver", // v1 only
	"log_opt",    // v1 only
	"mem_limit",
	"mem_reservation",
	"ports",
	"privileged",
	"read_only",
	"security_opt",
	"shm_size",
	"stop_grace_period", // v2 and above
	"tmpfs",
	"tty",
	"ulimits",
	"user",
	"volumes", // v2
	"volumes_from",
	"working_dir",
}

// supported fields/options from compose 3 YAML file
var supportedFieldsInV3 = map[string]bool{
	"CapAdd":          true,
	"CapDrop":         true,
	"Command":         true,
	"Devices":         true,
	"DNS":             true,
	"DNSSearch":       true,
	"Entrypoint":      true,
	"Environment":     true,
	"EnvFile":         true,
	"ExtraHosts":      true,
	"Hostname":        true,
	"HealthCheck":     true,
	"Image":           true,
	"Labels":          true,
	"Links":           true,
	"Logging":         true,
	"Name":            true,
	"Ports":           true,
	"Privileged":      true,
	"ReadOnly":        true,
	"SecurityOpt":     true,
	"StopGracePeriod": true,
	"Tmpfs":           true,
	"Tty":             true,
	"Ulimits":         true,
	"User":            true,
	"Volumes":         true,
	"WorkingDir":      true,
}

var supportedComposeV1V2YamlOptionsMap = getSupportedComposeV1V2YamlOptionsMap()

// Create set of supported YAML fields in docker compose v2 ServiceConfigs
func getSupportedComposeV1V2YamlOptionsMap() map[string]bool {
	optionsMap := make(map[string]bool)
	for _, value := range supportedComposeV1V2YamlOptions {
		optionsMap[value] = true
	}
	return optionsMap
}

// LogUnsupportedV1V2ServiceConfigFields logs a warning if there is an unsupported field specified in the docker-compose file
func LogUnsupportedV1V2ServiceConfigFields(serviceName string, serviceConfig *config.ServiceConfig) {
	configValue := reflect.ValueOf(serviceConfig).Elem()
	configType := configValue.Type()

	for i := 0; i < configValue.NumField(); i++ {
		field := configValue.Field(i)
		fieldType := configType.Field(i)
		// get the tag name (if any), defaults to fieldName
		tagName := fieldType.Name
		yamlTag := fieldType.Tag.Get("yaml") // Expected format `yaml:"tagName,omitempty"`
		if yamlTag != "" {
			tags := strings.Split(yamlTag, ",")
			if tags[0] != "" {
				tagName = tags[0]
			}
		}

		if tagName == "networks" && !validNetworksForService(serviceConfig) {
			logWarningForUnsupportedServiceOption(tagName, serviceName)
		}

		zeroValue := value.IsZero(field)
		// if value is present for the field that is not in supportedYamlTags map, log a warning
		if tagName != "networks" && !zeroValue && !supportedComposeV1V2YamlOptionsMap[tagName] {
			logWarningForUnsupportedServiceOption(tagName, serviceName)
		}
	}
}

// LogUnsupportedV3ServiceConfigFields logs a warning if there is an unsupported field specified in the docker-compose file
func LogUnsupportedV3ServiceConfigFields(servConfig types.ServiceConfig) {
	configValue := reflect.ValueOf(servConfig)
	configType := configValue.Type()

	for i := 0; i < configValue.NumField(); i++ {
		field := configValue.Field(i)
		fieldType := configType.Field(i)

		if supportedFieldsInV3[fieldType.Name] == false && !value.IsZero(field) {
			// convert field name so it more closely resembles option in yaml file
			optionName := adapter.ConvertCamelCaseToUnderScore(fieldType.Name)
			logWarningForUnsupportedServiceOption(optionName, servConfig.Name)
		}
	}
}

// LogUnsupportedProjectFields adds a WARNING to the customer about the fields that are unused.
func LogUnsupportedProjectFields(project *project.Project) {
	// ecsProject#parseCompose, which calls the underlying libcompose.Project#Parse(),
	// always populates the project.NetworkConfig with one entry ("default").
	// See: https://github.com/docker/libcompose/blob/master/project/project.go#L277
	if project.NetworkConfigs != nil && len(project.NetworkConfigs) > 1 {
		log.WithFields(log.Fields{"option name": "networks"}).Warn("Skipping unsupported YAML option...")
	}
}

func logWarningForUnsupportedServiceOption(tagName, serviceName string) {
	log.WithFields(log.Fields{
		"option name":  tagName,
		"service name": serviceName,
	}).Warn("Skipping unsupported YAML option for service...")
}

func validNetworksForService(config *config.ServiceConfig) bool {
	if config.Networks == nil {
		return false
	}
	if config.Networks.Networks == nil {
		return false
	}
	if len(config.Networks.Networks) != 1 {
		return false
	}

	return true
}
