package functions

import (
	"fmt"
	"strings"

	"knative.dev/func/pkg/utils"
)

type Envs []Env

// String returns Envs as a space-separated set of environment variable
// declarations in the form "KEY=VALUE K2=V2"
func (ee Envs) String() string {
	return strings.Join(ee.Slice(), " ")
}

// Slice returns Envs as a []strings in format NAME=VALUE
// Note that Env structs with a nil pointer for name are ignored because
// "=VALUE" is an invalid environment variable declaration.
func (ee Envs) Slice() []string {
	// TODO: removing pointers from the Env slice type (and the others)
	// would probably be worth the effort.
	s := []string{}
	for _, e := range ee {
		if e.Name == nil {
			continue
		}
		if e.Value == nil {
			s = append(s, *e.Name+"=")
			continue
		}
		s = append(s, *e.Name+"="+*e.Value)
	}
	return s
}

type Env struct {
	Name  *string `yaml:"name,omitempty" jsonschema:"pattern=^[-._a-zA-Z][-._a-zA-Z0-9]*$"`
	Value *string `yaml:"value,omitempty"`
}

func (e Env) String() string {
	if e.Name == nil && e.Value != nil {
		match := regWholeSecret.FindStringSubmatch(*e.Value)
		if len(match) == 2 {
			return fmt.Sprintf("All key=value pairs from Secret \"%s\"", match[1])
		}
		match = regWholeConfigMap.FindStringSubmatch(*e.Value)
		if len(match) == 2 {
			return fmt.Sprintf("All key=value pairs from ConfigMap \"%s\"", match[1])
		}
	} else if e.Name != nil && e.Value != nil {
		match := regKeyFromSecret.FindStringSubmatch(*e.Value)
		if len(match) == 3 {
			return fmt.Sprintf("Env \"%s\" with value set from key \"%s\" from Secret \"%s\"", *e.Name, match[2], match[1])
		}
		match = regKeyFromConfigMap.FindStringSubmatch(*e.Value)
		if len(match) == 3 {
			return fmt.Sprintf("Env \"%s\" with value set from key \"%s\" from ConfigMap \"%s\"", *e.Name, match[2], match[1])
		}
		match = regLocalEnv.FindStringSubmatch(*e.Value)
		if len(match) == 2 {
			return fmt.Sprintf("Env \"%s\" with value set from local env variable \"%s\"", *e.Name, match[1])
		}

		return fmt.Sprintf("Env \"%s\" with value \"%s\"", *e.Name, *e.Value)
	}
	return ""
}

// KeyValuePair returns a string representation of the Env field in form NAME=VALUE
// if NAME is not defined for an Env, empty string is returned
func (e Env) KeyValuePair() string {
	keyValue := ""
	if e.Name != nil {
		value := ""
		if e.Value != nil {
			value = *e.Value
		}

		keyValue = fmt.Sprintf("%s=%s", *e.Name, value)
	}

	return keyValue
}

// ValidateEnvs checks that input Envs are correct and contain all necessary fields.
// Returns array of error messages, empty if no errors are found
//
// Allowed settings:
//   - name: EXAMPLE1                					# ENV directly from a value
//     value: value1
//   - name: EXAMPLE2                 				# ENV from the local ENV var
//     value: {{ env:MY_ENV }}
//   - name: EXAMPLE3
//     value: {{ secret:secretName:key }}   			# ENV from a key in secret
//   - value: {{ secret:secretName }}          		# all key-pair values from secret are set as ENV
//   - name: EXAMPLE4
//     value: {{ configMap:configMapName:key }}   	# ENV from a key in configMap
//   - value: {{ configMap:configMapName }}          	# all key-pair values from configMap are set as ENV
func ValidateEnvs(envs []Env) (errors []string) {
	for i, env := range envs {
		if env.Name == nil && env.Value == nil {
			errors = append(errors, fmt.Sprintf("env entry #%d is not properly set", i))
		} else if env.Value == nil {
			errors = append(errors, fmt.Sprintf("env entry #%d is missing value field, only name '%s' is set", i, *env.Name))
		} else if env.Name == nil {
			// all key-pair values from secret are set as ENV; {{ secret:secretName }} or {{ configMap:configMapName }}
			if !regWholeSecret.MatchString(*env.Value) && !regWholeConfigMap.MatchString(*env.Value) {
				errors = append(errors, fmt.Sprintf("env entry #%d has invalid value field set, it has '%s', but allowed is only '{{ secret:secretName }}' or '{{ configMap:configMapName }}'",
					i, *env.Value))
			}
		} else {

			if err := utils.ValidateEnvVarName(*env.Name); err != nil {
				errors = append(errors, fmt.Sprintf("env entry #%d has invalid name set: %q; %s", i, *env.Name, err.Error()))
			}

			if strings.HasPrefix(*env.Value, "{{") {
				// ENV from the local ENV var; {{ env:MY_ENV }}
				// or
				// ENV from a key in secret/configMap;  {{ secret:secretName:key }} or {{ configMap:configMapName:key }}
				if !regLocalEnv.MatchString(*env.Value) && !regKeyFromSecret.MatchString(*env.Value) && !regKeyFromConfigMap.MatchString(*env.Value) {
					errors = append(errors,
						fmt.Sprintf(
							"env entry #%d with name '%s' has invalid value field set, it has '%s', but allowed is only '{{ env:MY_ENV }}', '{{ secret:secretName:key }}' or '{{ configMap:configMapName:key }}'",
							i, *env.Name, *env.Value))
				}
			}
		}
	}

	return
}

// ValidateBuildEnvs checks that input BuildEnvs are correct and contain all necessary fields.
// Returns array of error messages, empty if no errors are found
//
// Allowed settings:
//   - name: EXAMPLE1                					# ENV directly from a value
//     value: value1
//   - name: EXAMPLE2                 				# ENV from the local ENV var
//     value: {{ env:MY_ENV }}
func ValidateBuildEnvs(envs []Env) (errors []string) {
	for i, env := range envs {
		if env.Name == nil && env.Value == nil {
			errors = append(errors, fmt.Sprintf("env entry #%d is not properly set", i))
		} else if env.Value == nil {
			errors = append(errors, fmt.Sprintf("env entry #%d is missing value field, only name '%s' is set", i, *env.Name))
		} else {

			if err := utils.ValidateEnvVarName(*env.Name); err != nil {
				errors = append(errors, fmt.Sprintf("env entry #%d has invalid name set: %q; %s", i, *env.Name, err.Error()))
			}

			if strings.HasPrefix(*env.Value, "{{") {
				// ENV from the local ENV var; {{ env:MY_ENV }}
				if !regLocalEnv.MatchString(*env.Value) {
					errors = append(errors,
						fmt.Sprintf(
							"env entry #%d with name '%s' has invalid value field set, it has '%s', but allowed is only '{{ env:MY_ENV }}'",
							i, *env.Name, *env.Value))
				}
			}
		}
	}

	return
}
