// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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 telemetry

import (
	"strconv"
	"strings"

	"istio.io/istio/pilot/pkg/model"
	"istio.io/istio/pilot/pkg/networking/util"
	"istio.io/istio/pilot/pkg/serviceregistry/provider"
	"istio.io/istio/pkg/config/host"
)

var (
	// StatName patterns
	serviceStatPattern           = "%SERVICE%"
	serviceNameStatPattern       = "%SERVICE_NAME%"
	serviceFQDNStatPattern       = "%SERVICE_FQDN%"
	servicePortStatPattern       = "%SERVICE_PORT%"
	serviceTargetPortStatPattern = "%TARGET_PORT%"
	servicePortNameStatPattern   = "%SERVICE_PORT_NAME%"
	subsetNameStatPattern        = "%SUBSET_NAME%"
)

// BuildStatPrefix builds a stat prefix based on the stat pattern.
func BuildStatPrefix(statPattern string, host string, subset string, port *model.Port, targetPort int, attributes *model.ServiceAttributes) string {
	prefix := strings.ReplaceAll(statPattern, serviceStatPattern, shortHostName(host, attributes))
	prefix = strings.ReplaceAll(prefix, serviceNameStatPattern, serviceName(host, attributes))
	prefix = strings.ReplaceAll(prefix, serviceFQDNStatPattern, host)
	prefix = strings.ReplaceAll(prefix, subsetNameStatPattern, subset)
	prefix = strings.ReplaceAll(prefix, serviceTargetPortStatPattern, strconv.Itoa(targetPort))
	prefix = strings.ReplaceAll(prefix, servicePortStatPattern, strconv.Itoa(port.Port))
	prefix = strings.ReplaceAll(prefix, servicePortNameStatPattern, port.Name)
	return prefix
}

// BuildInboundStatPrefix builds a stat prefix based on the stat pattern and filter chain telemetry data.
func BuildInboundStatPrefix(statPattern string, tm FilterChainMetadata, subset string, port uint32, portName string) string {
	prefix := strings.ReplaceAll(statPattern, serviceStatPattern, tm.ShortHostname())
	prefix = strings.ReplaceAll(prefix, serviceNameStatPattern, tm.ServiceName())
	prefix = strings.ReplaceAll(prefix, serviceFQDNStatPattern, tm.InstanceHostname.String())
	prefix = strings.ReplaceAll(prefix, subsetNameStatPattern, subset)
	prefix = strings.ReplaceAll(prefix, servicePortStatPattern, strconv.Itoa(int(port)))
	prefix = strings.ReplaceAll(prefix, servicePortNameStatPattern, portName)
	return prefix
}

// shortHostName constructs the short hostname from kubernetes hosts based on attributes (name and namespace).
// For other hosts like VMs, this method does not do any thing - just returns the passed in host as is.
func shortHostName(host string, attributes *model.ServiceAttributes) string {
	if attributes.ServiceRegistry == provider.Kubernetes {
		return attributes.Name + "." + attributes.Namespace
	}
	return host
}

// serviceName constructs the service name from kubernetes hosts based on attributes (name).
// For other hosts like VMs, this method does not do any thing - just returns the passed in host as is.
func serviceName(host string, attributes *model.ServiceAttributes) string {
	if attributes.ServiceRegistry == provider.Kubernetes {
		return attributes.Name
	}
	return host
}

// TraceOperation builds the string format: "%s:%d/*" for a given host and port
func TraceOperation(host string, port int) string {
	// Format : "%s:%d/*"
	return util.DomainName(host, port) + "/*"
}

// FilterChainMetadata defines additional metadata for telemetry use for a filter chain.
type FilterChainMetadata struct {
	// InstanceHostname defines the hostname of the service this filter chain is built for.
	// Note: This is best effort; this may be empty if generated by Sidecar config, and there may be multiple
	// Services that make up the filter chain.
	InstanceHostname host.Name
	// KubernetesServiceNamespace is the namespace the service is defined in, if it is for a Kubernetes Service.
	// Note: This is best effort; this may be empty if generated by Sidecar config, and there may be multiple
	// Services that make up the filter chain.
	KubernetesServiceNamespace string
	// KubernetesServiceName is the name of service, if it is for a Kubernetes Service.
	// Note: This is best effort; this may be empty if generated by Sidecar config, and there may be multiple
	// Services that make up the filter chain.
	KubernetesServiceName string
}

// ShortHostname constructs the short hostname from kubernetes service name if available or just uses instance hostname.
func (tm FilterChainMetadata) ShortHostname() string {
	if tm.KubernetesServiceName != "" {
		return tm.KubernetesServiceName + "." + tm.KubernetesServiceNamespace
	}
	return tm.InstanceHostname.String()
}

// ServiceName constructs the service name from kubernetes service name if available or just uses instance host name.
func (tm FilterChainMetadata) ServiceName() string {
	if tm.KubernetesServiceName != "" {
		return tm.KubernetesServiceName
	}
	return tm.InstanceHostname.String()
}
