package label

import (
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"github.com/containous/traefik/log"
)

const (
	mapEntrySeparator = "||"
	mapValueSeparator = ":"
)

// Default values
const (
	DefaultWeight                                  = 1
	DefaultProtocol                                = "http"
	DefaultPassHostHeader                          = true
	DefaultPassTLSCert                             = false
	DefaultFrontendPriority                        = 0
	DefaultCircuitBreakerExpression                = "NetworkErrorRatio() > 1"
	DefaultBackendLoadBalancerMethod               = "wrr"
	DefaultBackendMaxconnExtractorFunc             = "request.host"
	DefaultBackendLoadbalancerStickinessCookieName = ""
	DefaultBackendHealthCheckPort                  = 0
)

var (
	// RegexpBaseFrontendErrorPage used to extract error pages from service's label
	RegexpBaseFrontendErrorPage = regexp.MustCompile(`^frontend\.errors\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)

	// RegexpFrontendErrorPage used to extract error pages from label
	RegexpFrontendErrorPage = regexp.MustCompile(`^traefik\.frontend\.errors\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)

	// RegexpBaseFrontendRateLimit used to extract rate limits from service's label
	RegexpBaseFrontendRateLimit = regexp.MustCompile(`^frontend\.rateLimit\.rateSet\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)

	// RegexpFrontendRateLimit used to extract rate limits from label
	RegexpFrontendRateLimit = regexp.MustCompile(`^traefik\.frontend\.rateLimit\.rateSet\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
)

// GetStringValue get string value associated to a label
func GetStringValue(labels map[string]string, labelName string, defaultValue string) string {
	if value, ok := labels[labelName]; ok && len(value) > 0 {
		return value
	}
	return defaultValue
}

// GetBoolValue get bool value associated to a label
func GetBoolValue(labels map[string]string, labelName string, defaultValue bool) bool {
	rawValue, ok := labels[labelName]
	if ok {
		v, err := strconv.ParseBool(rawValue)
		if err == nil {
			return v
		}
		log.Errorf("Unable to parse %q: %q, falling back to %v. %v", labelName, rawValue, defaultValue, err)
	}
	return defaultValue
}

// GetIntValue get int value associated to a label
func GetIntValue(labels map[string]string, labelName string, defaultValue int) int {
	if rawValue, ok := labels[labelName]; ok {
		value, err := strconv.Atoi(rawValue)
		if err == nil {
			return value
		}
		log.Errorf("Unable to parse %q: %q, falling back to %v. %v", labelName, rawValue, defaultValue, err)
	}
	return defaultValue
}

// GetInt64Value get int64 value associated to a label
func GetInt64Value(labels map[string]string, labelName string, defaultValue int64) int64 {
	if rawValue, ok := labels[labelName]; ok {
		value, err := strconv.ParseInt(rawValue, 10, 64)
		if err == nil {
			return value
		}
		log.Errorf("Unable to parse %q: %q, falling back to %v. %v", labelName, rawValue, defaultValue, err)
	}
	return defaultValue
}

// GetSliceStringValue get a slice of string associated to a label
func GetSliceStringValue(labels map[string]string, labelName string) []string {
	var value []string

	if values, ok := labels[labelName]; ok {
		value = SplitAndTrimString(values, ",")

		if len(value) == 0 {
			log.Debugf("Could not load %q.", labelName)
		}
	}
	return value
}

// ParseMapValue get Map value for a label value
func ParseMapValue(labelName, values string) map[string]string {
	mapValue := make(map[string]string)

	for _, parts := range strings.Split(values, mapEntrySeparator) {
		pair := strings.SplitN(parts, mapValueSeparator, 2)
		if len(pair) != 2 {
			log.Warnf("Could not load %q: %q, skipping...", labelName, parts)
		} else {
			mapValue[http.CanonicalHeaderKey(strings.TrimSpace(pair[0]))] = strings.TrimSpace(pair[1])
		}
	}

	if len(mapValue) == 0 {
		log.Errorf("Could not load %q, skipping...", labelName)
		return nil
	}
	return mapValue
}

// GetMapValue get Map value associated to a label
func GetMapValue(labels map[string]string, labelName string) map[string]string {
	if values, ok := labels[labelName]; ok {

		if len(values) == 0 {
			log.Errorf("Missing value for %q, skipping...", labelName)
			return nil
		}

		return ParseMapValue(labelName, values)
	}

	return nil
}

// GetStringMultipleStrict get multiple string values associated to several labels
// Fail if one label is missing
func GetStringMultipleStrict(labels map[string]string, labelNames ...string) (map[string]string, error) {
	foundLabels := map[string]string{}
	for _, name := range labelNames {
		value := GetStringValue(labels, name, "")
		// Error out only if one of them is not defined.
		if len(value) == 0 {
			return nil, fmt.Errorf("label not found: %s", name)
		}
		foundLabels[name] = value
	}
	return foundLabels, nil
}

// Has Check if a value is associated to a label
func Has(labels map[string]string, labelName string) bool {
	value, ok := labels[labelName]
	return ok && len(value) > 0
}

// HasPrefix Check if a value is associated to a less one label with a prefix
func HasPrefix(labels map[string]string, prefix string) bool {
	for name, value := range labels {
		if strings.HasPrefix(name, prefix) && len(value) > 0 {
			return true
		}
	}
	return false
}

// IsEnabled Check if a container is enabled in Træfik
func IsEnabled(labels map[string]string, exposedByDefault bool) bool {
	return GetBoolValue(labels, TraefikEnable, exposedByDefault)
}

// SplitAndTrimString splits separatedString at the separator character and trims each
// piece, filtering out empty pieces. Returns the list of pieces or nil if the input
// did not contain a non-empty piece.
func SplitAndTrimString(base string, sep string) []string {
	var trimmedStrings []string

	for _, s := range strings.Split(base, sep) {
		s = strings.TrimSpace(s)
		if len(s) > 0 {
			trimmedStrings = append(trimmedStrings, s)
		}
	}

	return trimmedStrings
}

// GetFuncString a func related to GetStringValue
func GetFuncString(labelName string, defaultValue string) func(map[string]string) string {
	return func(labels map[string]string) string {
		return GetStringValue(labels, labelName, defaultValue)
	}
}

// GetFuncInt a func related to GetIntValue
func GetFuncInt(labelName string, defaultValue int) func(map[string]string) int {
	return func(labels map[string]string) int {
		return GetIntValue(labels, labelName, defaultValue)
	}
}

// GetFuncBool a func related to GetBoolValue
func GetFuncBool(labelName string, defaultValue bool) func(map[string]string) bool {
	return func(labels map[string]string) bool {
		return GetBoolValue(labels, labelName, defaultValue)
	}
}

// GetFuncSliceString a func related to GetSliceStringValue
func GetFuncSliceString(labelName string) func(map[string]string) []string {
	return func(labels map[string]string) []string {
		return GetSliceStringValue(labels, labelName)
	}
}
