package crd

import (
	"crypto/sha256"
	"fmt"
	"strconv"
	"strings"

	"github.com/traefik/traefik/v3/pkg/provider"
)

// nameSeparator joins the components of a SafeNaming generated name.
// It is guaranteed to never appear inside a Kubernetes namespace or name (both are restricted to
// lowercase alphanumeric characters, '-' and '.'), so joining components with it is unambiguous.
const nameSeparator = "_"

// nameBuilder builds the names of the routers, services, middlewares, and other objects generated by
// the CRD provider, according to whichever naming scheme is active.
type nameBuilder struct {
	// safe selects the SafeNaming scheme (collision-safe, identity-based) over the legacy one
	// (flattened and normalized, collision-prone) when true.
	safe bool
}

// makeSafeKey builds a generated name from its components separated by the nameSeparator.
func makeSafeKey(components ...string) string {
	return strings.Join(components, nameSeparator)
}

// makeFlattenedKey builds the flattened generated name for a namespace and a name, used when
// SafeNaming is disabled.
func makeFlattenedKey(namespace, name string) string {
	if namespace == "" {
		return name
	}

	return namespace + "-" + name
}

// makeServiceKey builds the generated key for a route, derived from its rule, used when
// SafeNaming is disabled.
func makeServiceKey(rule, ingressName string) (string, error) {
	h := sha256.New()
	if _, err := h.Write([]byte(rule)); err != nil {
		return "", err
	}

	return fmt.Sprintf("%s-%.10x", ingressName, h.Sum(nil)), nil
}

// makeID names an object (a middleware, ServersTransport, or TraefikService) after its own
// (namespace, name) identity.
func (n nameBuilder) makeID(namespace, name string) string {
	if n.safe {
		return makeSafeKey(namespace, name)
	}

	return provider.Normalize(makeFlattenedKey(namespace, name))
}

// makeRawID is makeID, without normalization: used for TLSOptions and TLSStores, whose legacy
// names are not normalized.
func (n nameBuilder) makeRawID(namespace, name string) string {
	if n.safe {
		return makeSafeKey(namespace, name)
	}

	return makeFlattenedKey(namespace, name)
}

// httpRouter names the HTTP router generated for routeIndex within an IngressRoute.
func (n nameBuilder) httpRouter(namespace, ingressName string, routeIndex int, rule string) (string, error) {
	if n.safe {
		return makeSafeKey(namespace, ingressName, strconv.Itoa(routeIndex)), nil
	}

	serviceKey, err := makeServiceKey(rule, ingressName)
	if err != nil {
		return "", err
	}

	return provider.Normalize(makeFlattenedKey(namespace, serviceKey)), nil
}

// tcpRouter names the TCP router generated for routeIndex within an IngressRouteTCP.
// Its legacy name is not normalized.
func (n nameBuilder) tcpRouter(namespace, ingressName string, routeIndex int, rule string) (string, error) {
	if n.safe {
		return makeSafeKey(namespace, ingressName, strconv.Itoa(routeIndex)), nil
	}

	serviceKey, err := makeServiceKey(rule, ingressName)
	if err != nil {
		return "", err
	}

	return makeFlattenedKey(namespace, serviceKey), nil
}

// udpRouter names the UDP router generated for routeIndex within an IngressRouteUDP.
// Its legacy name is not normalized and does not need to derive from a rule.
func (n nameBuilder) udpRouter(namespace, ingressName string, routeIndex int) string {
	if n.safe {
		return makeSafeKey(namespace, ingressName, strconv.Itoa(routeIndex))
	}

	return makeFlattenedKey(namespace, fmt.Sprintf("%s-%d", ingressName, routeIndex))
}
