//nolint:gosec
package namer

import (
	"crypto/md5"
	"fmt"
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	hashSize = 8

	GcpPscNameLimit = 63
)

type IdentifierOptions func(string) string

func WithPrefix(prefix string) IdentifierOptions {
	return func(name string) string {
		if len(prefix) == 0 || prefix[len(prefix)-1] == '-' {
			return prefix + name
		}
		return prefix + "-" + name
	}
}

// WithLimit limits the generated unique identifier.
// Note this option needs to be the last option for `UniqueIdentifier` function
// namely `WithLimit` should be after `WithPrefix` and other `IdentifierOptions`.
// Otherwise the final generated uid would exceed the limit
func WithLimit(limit int) IdentifierOptions {
	return func(name string) string {
		resultLimit := limit - hashSize - 1
		if len(name) > resultLimit {
			return name[:resultLimit]
		}
		return name
	}
}

func UniqueIdentifier(meta metav1.ObjectMeta, options ...IdentifierOptions) string {
	// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names
	const (
		labelLengthLimit = 63
		resultLimit      = labelLengthLimit - hashSize - 1
	)

	namespacedName := fmt.Sprintf("%s/%s", meta.GetNamespace(), meta.GetName())
	hash := fmt.Sprintf("%x", md5.Sum([]byte(namespacedName)))[:hashSize]

	// The 8-digit hash will not be trimmed. Keep the namespaced name if possible
	var result = meta.GetName()
	if meta.GetNamespace() != "" {
		result = fmt.Sprintf("%s-%s", meta.GetNamespace(), meta.GetName())
	}
	for _, opt := range options {
		result = opt(result)
	}
	if len(result) > resultLimit {
		result = result[:resultLimit]
	}
	return fmt.Sprintf("%s-%s", result, hash)
}

func PscEndpointName(meta metav1.ObjectMeta, prefix string) string {
	return UniqueIdentifier(meta, WithPrefix(prefix), WithLimit(GcpPscNameLimit))
}

func AzurePLSEndpointName(meta metav1.ObjectMeta, prefix string) string {
	return UniqueIdentifier(meta, WithPrefix(prefix))
}

func DomainNamespace(domainName string) string {
	i := strings.Index(domainName, ".")
	if i > 0 {
		domainName = domainName[:i]
	}
	return domainName
}

func InterfaceEndpointName(meta metav1.ObjectMeta, domainName, prefix string) string {
	domainName = DomainNamespace(domainName)
	return UniqueIdentifier(meta, WithPrefix(domainName), WithPrefix(prefix))
}
