// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 translator

import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"

	"github.com/pkg/errors"
	corev1 "k8s.io/api/core/v1"
	discoveryv1 "k8s.io/api/discovery/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"

	adctypes "github.com/apache/apisix-ingress-controller/api/adc"
	"github.com/apache/apisix-ingress-controller/api/v1alpha1"
	apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
	"github.com/apache/apisix-ingress-controller/internal/controller/label"
	"github.com/apache/apisix-ingress-controller/internal/id"
	"github.com/apache/apisix-ingress-controller/internal/provider"
	sslutils "github.com/apache/apisix-ingress-controller/internal/ssl"
	internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
)

func (t *Translator) fillPluginsFromHTTPRouteFilters(
	plugins adctypes.Plugins,
	namespace string,
	filters []gatewayv1.HTTPRouteFilter,
	matches []gatewayv1.HTTPRouteMatch,
	tctx *provider.TranslateContext,
) {
	for _, filter := range filters {
		switch filter.Type {
		case gatewayv1.HTTPRouteFilterRequestHeaderModifier:
			t.fillPluginFromHTTPRequestHeaderFilter(plugins, filter.RequestHeaderModifier)
		case gatewayv1.HTTPRouteFilterRequestRedirect:
			t.fillPluginFromHTTPRequestRedirectFilter(plugins, filter.RequestRedirect)
		case gatewayv1.HTTPRouteFilterRequestMirror:
			t.fillPluginFromHTTPRequestMirrorFilter(plugins, namespace, filter.RequestMirror, apiv2.SchemeHTTP)
		case gatewayv1.HTTPRouteFilterURLRewrite:
			t.fillPluginFromURLRewriteFilter(plugins, filter.URLRewrite, matches)
		case gatewayv1.HTTPRouteFilterResponseHeaderModifier:
			t.fillPluginFromHTTPResponseHeaderFilter(plugins, filter.ResponseHeaderModifier)
		case gatewayv1.HTTPRouteFilterExtensionRef:
			t.fillPluginFromExtensionRef(plugins, namespace, filter.ExtensionRef, tctx)
		case gatewayv1.HTTPRouteFilterCORS:
			t.fillPluginFromHTTPCORSFilter(plugins, filter.CORS)
		}
	}
}

func (t *Translator) fillPluginFromExtensionRef(plugins adctypes.Plugins, namespace string, extensionRef *gatewayv1.LocalObjectReference, tctx *provider.TranslateContext) {
	if extensionRef == nil {
		return
	}
	if extensionRef.Kind == internaltypes.KindPluginConfig {
		pluginconfig := tctx.PluginConfigs[types.NamespacedName{
			Namespace: namespace,
			Name:      string(extensionRef.Name),
		}]
		if pluginconfig == nil {
			return
		}
		for _, plugin := range pluginconfig.Spec.Plugins {
			pluginName := plugin.Name
			pluginconfig := make(map[string]any)
			if len(plugin.Config.Raw) > 0 {
				if err := json.Unmarshal(plugin.Config.Raw, &pluginconfig); err != nil {
					t.Log.Error(err, "plugin config unmarshal failed", "plugin", plugin.Name)
					continue
				}
			}
			plugins[pluginName] = pluginconfig
		}
		t.Log.V(1).Info("fill plugin from extension ref", "plugins", plugins)
	}
}

func (t *Translator) fillPluginFromURLRewriteFilter(plugins adctypes.Plugins, urlRewrite *gatewayv1.HTTPURLRewriteFilter, matches []gatewayv1.HTTPRouteMatch) {
	pluginName := adctypes.PluginProxyRewrite
	obj := plugins[pluginName]
	var plugin *adctypes.RewriteConfig
	if obj == nil {
		plugin = &adctypes.RewriteConfig{}
		plugins[pluginName] = plugin
	} else {
		plugin = obj.(*adctypes.RewriteConfig)
	}
	if urlRewrite.Hostname != nil {
		plugin.Host = string(*urlRewrite.Hostname)
	}

	if urlRewrite.Path != nil {
		switch urlRewrite.Path.Type {
		case gatewayv1.FullPathHTTPPathModifier:
			plugin.RewriteTarget = *urlRewrite.Path.ReplaceFullPath
		case gatewayv1.PrefixMatchHTTPPathModifier:
			prefixPaths := make([]string, 0, len(matches))
			for _, match := range matches {
				if match.Path == nil || match.Path.Type == nil || *match.Path.Type != gatewayv1.PathMatchPathPrefix {
					continue
				}
				prefixPaths = append(prefixPaths, *match.Path.Value)
			}
			if len(prefixPaths) == 0 || urlRewrite.Path.ReplacePrefixMatch == nil {
				break
			}
			prefixGroup := "(" + strings.Join(prefixPaths, "|") + ")"
			replaceTarget := *urlRewrite.Path.ReplacePrefixMatch
			// Handle ReplacePrefixMatch path rewrite
			// If replaceTarget == "/", special handling is required to avoid
			// producing double slashes or empty paths.
			var regexPattern, regexTarget string
			if replaceTarget == "/" {
				// Match either "/prefix" or "/prefix/<remainder>"
				// Pattern captures the remainder (if any) without a leading slash.
				// Template reconstructs "/" + remainder, resulting in:
				//   /prefix/three → /three
				//   /prefix       → /
				regexPattern = "^" + prefixGroup + "(?:/(.*))?$"
				regexTarget = "/" + "$2"
			} else {
				// Match either "/prefix" or "/prefix/<remainder>"
				// Pattern captures the remainder (including leading slash) as $2.
				// Template appends it to replaceTarget:
				//   /prefix/one/two → /one/two
				//   /prefix/one     → /one
				regexPattern = "^" + prefixGroup + "(/.*)?$"
				regexTarget = replaceTarget + "$2"
			}
			plugin.RewriteTargetRegex = []string{regexPattern, regexTarget}
		}
	}
}

func (t *Translator) fillPluginFromHTTPCORSFilter(plugins adctypes.Plugins, cors *gatewayv1.HTTPCORSFilter) {
	pluginName := adctypes.PluginCORS
	obj := plugins[pluginName]
	var plugin *adctypes.CorsConfig
	if obj == nil {
		plugin = &adctypes.CorsConfig{}
		plugins[pluginName] = plugin
	} else {
		plugin = obj.(*adctypes.CorsConfig)
	}

	if len(cors.AllowOrigins) > 0 {
		origins := make([]string, len(cors.AllowOrigins))
		for i, allowOrigin := range cors.AllowOrigins {
			origins[i] = string(allowOrigin)
		}
		plugin.AllowOrigins = strings.Join(origins, ",")
	}

	if len(cors.AllowHeaders) > 0 {
		headers := make([]string, len(cors.AllowHeaders))
		for i, allowHeader := range cors.AllowHeaders {
			headers[i] = string(allowHeader)
		}
		plugin.AllowHeaders = strings.Join(headers, ",")
	}

	if len(cors.AllowMethods) > 0 {
		methods := make([]string, len(cors.AllowMethods))
		for i, allowMethod := range cors.AllowMethods {
			methods[i] = string(allowMethod)
		}
		plugin.AllowMethods = strings.Join(methods, ",")
	}
	if len(cors.ExposeHeaders) > 0 {
		exposeHeaders := make([]string, len(cors.ExposeHeaders))
		for i, exposeHeader := range cors.ExposeHeaders {
			exposeHeaders[i] = string(exposeHeader)
		}
		plugin.ExposeHeaders = strings.Join(exposeHeaders, ",")
	}
	if cors.AllowCredentials != nil {
		plugin.AllowCredential = *cors.AllowCredentials
	}
}

func (t *Translator) fillPluginFromHTTPRequestHeaderFilter(plugins adctypes.Plugins, reqHeaderModifier *gatewayv1.HTTPHeaderFilter) {
	pluginName := adctypes.PluginProxyRewrite
	obj := plugins[pluginName]
	var plugin *adctypes.RewriteConfig
	if obj == nil {
		plugin = &adctypes.RewriteConfig{}
		plugins[pluginName] = plugin
	} else {
		plugin = obj.(*adctypes.RewriteConfig)
	}
	if plugin.Headers == nil {
		plugin.Headers = &adctypes.Headers{
			Add:    make(map[string]string, len(reqHeaderModifier.Add)),
			Set:    make(map[string]string, len(reqHeaderModifier.Set)),
			Remove: make([]string, 0, len(reqHeaderModifier.Remove)),
		}
	}

	for _, header := range reqHeaderModifier.Add {
		val := plugin.Headers.Add[string(header.Name)]
		if val != "" {
			val += ", " + header.Value
		} else {
			val = header.Value
		}
		plugin.Headers.Add[string(header.Name)] = val
	}
	for _, header := range reqHeaderModifier.Set {
		plugin.Headers.Set[string(header.Name)] = header.Value
	}
	plugin.Headers.Remove = append(plugin.Headers.Remove, reqHeaderModifier.Remove...)
}

func (t *Translator) fillPluginFromHTTPResponseHeaderFilter(plugins adctypes.Plugins, respHeaderModifier *gatewayv1.HTTPHeaderFilter) {
	pluginName := adctypes.PluginResponseRewrite
	obj := plugins[pluginName]
	var plugin *adctypes.ResponseRewriteConfig
	if obj == nil {
		plugin = &adctypes.ResponseRewriteConfig{
			Headers: &adctypes.ResponseHeaders{
				Add:    make([]string, 0, len(respHeaderModifier.Add)),
				Set:    make(map[string]string, len(respHeaderModifier.Set)),
				Remove: make([]string, 0, len(respHeaderModifier.Remove)),
			},
		}
		plugins[pluginName] = plugin
	} else {
		plugin = obj.(*adctypes.ResponseRewriteConfig)
	}
	for _, header := range respHeaderModifier.Add {
		plugin.Headers.Add = append(plugin.Headers.Add, fmt.Sprintf("%s: %s", header.Name, header.Value))
	}
	for _, header := range respHeaderModifier.Set {
		plugin.Headers.Set[string(header.Name)] = header.Value
	}
	plugin.Headers.Remove = append(plugin.Headers.Remove, respHeaderModifier.Remove...)
}

func (t *Translator) fillPluginFromHTTPRequestMirrorFilter(plugins adctypes.Plugins, namespace string, reqMirror *gatewayv1.HTTPRequestMirrorFilter, scheme string) {
	pluginName := adctypes.PluginProxyMirror
	obj := plugins[pluginName]

	var plugin *adctypes.RequestMirror
	if obj == nil {
		plugin = &adctypes.RequestMirror{}
		plugins[pluginName] = plugin
	} else {
		plugin = obj.(*adctypes.RequestMirror)
	}

	var (
		port = 80
		ns   = namespace
	)
	if reqMirror.BackendRef.Port != nil {
		port = int(*reqMirror.BackendRef.Port)
	}
	if reqMirror.BackendRef.Namespace != nil {
		ns = string(*reqMirror.BackendRef.Namespace)
	}

	host := fmt.Sprintf("%s://%s.%s.svc.cluster.local:%d", scheme, reqMirror.BackendRef.Name, ns, port)

	plugin.Host = host
}

func (t *Translator) fillPluginFromHTTPRequestRedirectFilter(plugins adctypes.Plugins, reqRedirect *gatewayv1.HTTPRequestRedirectFilter) {
	pluginName := adctypes.PluginRedirect
	obj := plugins[pluginName]

	var plugin *adctypes.RedirectConfig
	if obj == nil {
		plugin = &adctypes.RedirectConfig{}
		plugins[pluginName] = plugin
	} else {
		plugin = obj.(*adctypes.RedirectConfig)
	}
	var uri string

	code := 302
	if reqRedirect.StatusCode != nil {
		code = *reqRedirect.StatusCode
	}

	hostname := "$host"
	if reqRedirect.Hostname != nil {
		hostname = string(*reqRedirect.Hostname)
	}

	scheme := "$scheme"
	if reqRedirect.Scheme != nil {
		scheme = *reqRedirect.Scheme
	}

	if reqRedirect.Port != nil {
		uri = fmt.Sprintf("%s://%s:%d$request_uri", scheme, hostname, int(*reqRedirect.Port))
	} else {
		uri = fmt.Sprintf("%s://%s$request_uri", scheme, hostname)
	}
	plugin.RetCode = code
	plugin.URI = uri
}

func (t *Translator) fillHTTPRoutePoliciesForHTTPRoute(tctx *provider.TranslateContext, routes []*adctypes.Route, rule gatewayv1.HTTPRouteRule) {
	var policies []v1alpha1.HTTPRoutePolicy
	for _, policy := range tctx.HTTPRoutePolicies {
		for _, ref := range policy.Spec.TargetRefs {
			if string(ref.Kind) == internaltypes.KindHTTPRoute && (ref.SectionName == nil || *ref.SectionName == "" || ptr.Equal(ref.SectionName, rule.Name)) {
				policies = append(policies, policy)
				break
			}
		}
	}

	t.fillHTTPRoutePolicies(routes, policies)
}

func (t *Translator) fillHTTPRoutePoliciesForIngress(tctx *provider.TranslateContext, routes []*adctypes.Route) {
	t.fillHTTPRoutePolicies(routes, tctx.HTTPRoutePolicies)
}

func (t *Translator) fillHTTPRoutePolicies(routes []*adctypes.Route, policies []v1alpha1.HTTPRoutePolicy) {
	for _, policy := range policies {
		for _, route := range routes {
			route.Priority = policy.Spec.Priority
			for _, data := range policy.Spec.Vars {
				var v []adctypes.StringOrSlice
				if err := json.Unmarshal(data.Raw, &v); err != nil {
					t.Log.Error(err, "failed to unmarshal spec.Vars item to []StringOrSlice", "data", string(data.Raw))
					// todo: update status
					continue
				}
				route.Vars = append(route.Vars, v)
			}
		}
	}
}

func (t *Translator) translateEndpointSlice(portName *string, weight int, endpointSlices []discoveryv1.EndpointSlice, endpointFilter func(*discoveryv1.Endpoint) bool) adctypes.UpstreamNodes {
	nodes := adctypes.UpstreamNodes{}
	if len(endpointSlices) == 0 {
		return nodes
	}
	for _, endpointSlice := range endpointSlices {
		for _, port := range endpointSlice.Ports {
			if portName != nil && !ptr.Equal(portName, port.Name) {
				continue
			}
			for _, endpoint := range endpointSlice.Endpoints {
				if endpointFilter != nil && !endpointFilter(&endpoint) {
					t.Log.V(1).Info("skip endpoint by filter", "endpoint", endpoint)
					continue
				}
				for _, addr := range endpoint.Addresses {
					node := adctypes.UpstreamNode{
						Host:   addr,
						Port:   int(*port.Port),
						Weight: weight,
					}
					nodes = append(nodes, node)
				}
			}
			if portName != nil {
				break
			}
		}
	}

	return nodes
}

func DefaultEndpointFilter(endpoint *discoveryv1.Endpoint) bool {
	if endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready {
		return false
	}
	return true
}

func (t *Translator) TranslateBackendRefWithFilter(tctx *provider.TranslateContext, ref gatewayv1.BackendRef, endpointFilter func(*discoveryv1.Endpoint) bool) (adctypes.UpstreamNodes, string, error) {
	return t.translateBackendRef(tctx, ref, endpointFilter)
}

func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref gatewayv1.BackendRef, endpointFilter func(*discoveryv1.Endpoint) bool) (adctypes.UpstreamNodes, string, error) {
	nodes := adctypes.UpstreamNodes{}
	var protocol string
	if ref.Kind != nil && *ref.Kind != internaltypes.KindService {
		return nodes, protocol, fmt.Errorf("kind %s is not supported", *ref.Kind)
	}

	key := types.NamespacedName{
		Namespace: string(*ref.Namespace),
		Name:      string(ref.Name),
	}
	service, ok := tctx.Services[key]
	if !ok {
		return nodes, protocol, fmt.Errorf("service %s not found", key)
	}

	weight := 1
	if ref.Weight != nil {
		weight = int(*ref.Weight)
	}

	if service.Spec.Type == corev1.ServiceTypeExternalName {
		port := 80
		if ref.Port != nil {
			port = int(*ref.Port)
			for _, p := range service.Spec.Ports {
				if int(p.Port) == port {
					protocol = ptr.Deref(p.AppProtocol, "")
					break
				}
			}
		}
		return adctypes.UpstreamNodes{
			{
				Host:   service.Spec.ExternalName,
				Port:   port,
				Weight: weight,
			},
		}, protocol, nil
	}

	var portName *string
	if ref.Port != nil {
		for _, p := range service.Spec.Ports {
			if int(p.Port) == int(*ref.Port) {
				portName = ptr.To(p.Name)
				protocol = ptr.Deref(p.AppProtocol, "")
				break
			}
		}
		if portName == nil {
			return adctypes.UpstreamNodes{}, protocol, nil
		}
	}

	endpointSlices := tctx.EndpointSlices[key]
	nodes = t.translateEndpointSlice(portName, weight, endpointSlices, endpointFilter)
	return nodes, protocol, nil
}

// calculateHTTPRoutePriority calculates the priority of the HTTP route.
// ref: https://github.com/Kong/kubernetes-ingress-controller/blob/57472721319e2c63e56cb8540425257e8e02520f/internal/dataplane/translator/subtranslator/httproute_atc.go#L279-L296
func calculateHTTPRoutePriority(match *gatewayv1.HTTPRouteMatch, ruleIndex int, hosts []string) uint64 {
	const (
		// PreciseHostnameShiftBits assigns bit 31-38 for the length of hostname(max length=253).
		// which has 8 bits, so the max length of hostname is 2^8-1 = 255.
		PreciseHostnameShiftBits = 31

		// HostnameLengthShiftBits assigns bits 23-30 for the length of hostname(max length=253).
		// which has 8 bits, so the max length of hostname is 2^8-1 = 255.
		HostnameLengthShiftBits = 23

		// MethodMatchShiftBits assigns bit 22 to mark if method is specified.
		MethodMatchShiftBits = 22
		// HeaderNumberShiftBits assign bits 17-21 to number of headers. (max number of headers = 16)
		HeaderNumberShiftBits = 17
		// QueryParamNumberShiftBits makes bits 12-16 used for number of query params (max number of query params = 16)
		QueryParamNumberShiftBits = 12
		// RuleIndexShiftBits assigns bits 7-11 to rule index. (max number of rules = 16)
		RuleIndexShiftBits = 7
	)

	var (
		priority uint64 = 0
		// Handle hostname priority
		// 1. Non-wildcard hostname priority
		// 2. Hostname length priority
		maxNonWildcardLength = 0
		maxHostnameLength    = 0
	)

	for _, host := range hosts {
		isNonWildcard := !strings.Contains(host, "*")

		if isNonWildcard && len(host) > maxNonWildcardLength {
			maxNonWildcardLength = len(host)
		}

		if len(host) > maxHostnameLength {
			maxHostnameLength = len(host)
		}
	}

	// If there is a non-wildcard hostname, set the PreciseHostnameShiftBits bit
	if maxNonWildcardLength > 0 {
		priority |= (uint64(maxNonWildcardLength) << PreciseHostnameShiftBits)
	}

	if maxHostnameLength > 0 {
		priority |= (uint64(maxHostnameLength) << HostnameLengthShiftBits)
	}

	// MethodMatchShiftBits
	if match.Method != nil {
		priority |= (1 << MethodMatchShiftBits)
	}

	// HeaderNumberShiftBits
	headerCount := len(match.Headers)
	priority |= (uint64(headerCount) << HeaderNumberShiftBits)

	// QueryParamNumberShiftBits
	queryParamCount := len(match.QueryParams)
	priority |= (uint64(queryParamCount) << QueryParamNumberShiftBits)

	// RuleIndexShiftBits
	index := 16 - ruleIndex
	if index < 0 {
		index = 0
	}
	priority |= (uint64(index) << RuleIndexShiftBits)

	return priority
}

// ruleProducesResponse reports whether the rule answers requests on its own,
// without a backend — currently a RequestRedirect filter. Such a rule is valid
// with no backendRefs and must not be turned into a fault-injection 500.
func ruleProducesResponse(rule gatewayv1.HTTPRouteRule) bool {
	for _, f := range rule.Filters {
		if f.Type == gatewayv1.HTTPRouteFilterRequestRedirect {
			return true
		}
	}
	return false
}

// translateBackendsToUpstreams processes the BackendRefs of an HTTPRouteRule,
// builds upstreams, assigns them to the service (single upstream or traffic-split
// plugin for multiple), and injects fault-injection on backend errors.
func (t *Translator) translateBackendsToUpstreams(
	tctx *provider.TranslateContext,
	rule gatewayv1.HTTPRouteRule,
	httpRoute *gatewayv1.HTTPRoute,
	service *adctypes.Service,
) (enableWebsocket *bool, backendErr error) {
	upstreams := make([]*adctypes.Upstream, 0)
	weightedUpstreams := make([]adctypes.TrafficSplitConfigRuleWeightedUpstream, 0)

	for _, backend := range rule.BackendRefs {
		if backend.Namespace == nil {
			namespace := gatewayv1.Namespace(httpRoute.Namespace)
			backend.Namespace = &namespace
		}
		upstream := adctypes.NewDefaultUpstream()
		upNodes, protocol, err := t.translateBackendRef(tctx, backend.BackendRef, DefaultEndpointFilter)
		if err != nil {
			backendErr = err
			continue
		}
		if len(upNodes) == 0 {
			continue
		}
		if protocol == internaltypes.AppProtocolWS || protocol == internaltypes.AppProtocolWSS {
			enableWebsocket = ptr.To(true)
		}

		t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, tctx.BackendTrafficPolicies, upstream, tctx.Services)
		upstream.Nodes = upNodes
		if upstream.Scheme == "" {
			upstream.Scheme = appProtocolToUpstreamScheme(protocol)
		}
		var (
			kind string
			port int32
		)
		if backend.Kind == nil {
			kind = internaltypes.KindService
		} else {
			kind = string(*backend.Kind)
		}
		if backend.Port != nil {
			port = *backend.Port
		}
		namespace := string(*backend.Namespace)
		name := string(backend.Name)
		upstreamName := adctypes.ComposeUpstreamNameForBackendRef(kind, namespace, name, port)
		upstream.Name = upstreamName
		upstream.ID = id.GenID(upstreamName)
		upstreams = append(upstreams, upstream)
	}

	// Handle multiple backends with traffic-split plugin
	if len(upstreams) == 0 {
		// Create a default upstream if no valid backends
		service.Upstream = adctypes.NewDefaultUpstream()
	} else if len(upstreams) == 1 {
		// Single backend - use directly as service upstream
		service.Upstream = upstreams[0]
		// remove the id and name of the service.upstream, adc schema does not need id and name for it
		service.Upstream.ID = ""
		service.Upstream.Name = ""
	} else {
		// Multiple backends - use traffic-split plugin
		service.Upstream = upstreams[0]
		// remove the id and name of the service.upstream, adc schema does not need id and name for it
		service.Upstream.ID = ""
		service.Upstream.Name = ""

		upstreams = upstreams[1:]

		if len(upstreams) > 0 {
			service.Upstreams = upstreams
		}

		// Set weight in traffic-split for the default upstream
		weight := apiv2.DefaultWeight
		if rule.BackendRefs[0].Weight != nil {
			weight = int(*rule.BackendRefs[0].Weight)
		}
		weightedUpstreams = append(weightedUpstreams, adctypes.TrafficSplitConfigRuleWeightedUpstream{
			Weight: weight,
		})

		// Set other upstreams in traffic-split using upstream_id
		for i, upstream := range upstreams {
			weight := apiv2.DefaultWeight
			// get weight from the backend refs starting from the second backend
			if i+1 < len(rule.BackendRefs) && rule.BackendRefs[i+1].Weight != nil {
				weight = int(*rule.BackendRefs[i+1].Weight)
			}
			weightedUpstreams = append(weightedUpstreams, adctypes.TrafficSplitConfigRuleWeightedUpstream{
				UpstreamID: upstream.ID,
				Weight:     weight,
			})
		}

		if len(weightedUpstreams) > 0 {
			if service.Plugins == nil {
				service.Plugins = make(map[string]any)
			}
			service.Plugins["traffic-split"] = &adctypes.TrafficSplitConfig{
				Rules: []adctypes.TrafficSplitConfigRule{
					{
						WeightedUpstreams: weightedUpstreams,
					},
				},
			}
		}
	}

	// A rule whose backendRefs are omitted/empty, or whose backendRefs all fail
	// to resolve, must explicitly respond with 500 (Gateway API). Exceptions:
	//   - a rule that produces its own response (e.g. a RequestRedirect filter)
	//     is valid without backendRefs; the filter answers, so it must not be
	//     turned into a 500.
	//   - a backend that resolves but currently has no ready endpoints is left to
	//     the upstream's own "no healthy nodes" handling (503).
	noUsableBackend := backendErr != nil || (len(rule.BackendRefs) == 0 && !ruleProducesResponse(rule))
	if noUsableBackend && (service.Upstream == nil || len(service.Upstream.Nodes) == 0) {
		if service.Plugins == nil {
			service.Plugins = make(map[string]any)
		}
		service.Plugins["fault-injection"] = map[string]any{
			"abort": map[string]any{
				"http_status": 500,
				"body":        "No existing backendRef provided",
			},
		}
	}

	return enableWebsocket, backendErr
}

func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRoute *gatewayv1.HTTPRoute) (*TranslateResult, error) {
	result := &TranslateResult{}

	hosts := make([]string, 0, len(httpRoute.Spec.Hostnames))
	for _, hostname := range httpRoute.Spec.Hostnames {
		hosts = append(hosts, string(hostname))
	}
	hosts = sslutils.NormalizeHosts(hosts)

	rules := httpRoute.Spec.Rules

	labels := label.GenLabel(httpRoute)

	for ruleIndex, rule := range rules {
		service := adctypes.NewDefaultService()
		service.Labels = labels

		service.Name = adctypes.ComposeServiceNameWithRule(httpRoute.Namespace, httpRoute.Name, fmt.Sprintf("%d", ruleIndex))
		service.ID = id.GenID(service.Name)
		service.Hosts = hosts

		enableWebsocket, _ := t.translateBackendsToUpstreams(tctx, rule, httpRoute, service)

		t.fillPluginsFromHTTPRouteFilters(service.Plugins, httpRoute.GetNamespace(), rule.Filters, rule.Matches, tctx)

		matches := rule.Matches
		if len(matches) == 0 {
			defaultType := gatewayv1.PathMatchPathPrefix
			defaultValue := "/"
			matches = []gatewayv1.HTTPRouteMatch{
				{
					Path: &gatewayv1.HTTPPathMatch{
						Type:  &defaultType,
						Value: &defaultValue,
					},
				},
			}
		}

		routes := []*adctypes.Route{}
		for j, match := range matches {
			route, err := t.translateGatewayHTTPRouteMatch(&match)
			if err != nil {
				return nil, err
			}

			name := adctypes.ComposeRouteName(httpRoute.Namespace, httpRoute.Name, fmt.Sprintf("%d-%d", ruleIndex, j))
			route.Name = name
			route.ID = id.GenID(name)
			route.Labels = labels
			route.EnableWebsocket = enableWebsocket

			// Set the route priority
			priority := calculateHTTPRoutePriority(&match, ruleIndex, hosts)
			route.Priority = ptr.To(int64(priority))

			routes = append(routes, route)
		}

		// Hostname-less listener ports decide whether a server_port var is needed;
		// hostname listeners are isolated by host, not port.
		listenerPorts := collectServerPortMatchPorts(tctx.Listeners)

		// Add server_port matching only when a route explicitly targets a listener
		// or when multiple listener ports need to be disambiguated. When it is added,
		// match on every targeted listener port so a route attached to both a
		// hostname-less and a hostname listener is not dropped on the hostname port.
		if t.shouldInjectServerPortVars(tctx.HasExplicitListenerMatch, listenerPorts) {
			matchPorts := allListenerPorts(tctx.Listeners)
			for _, route := range routes {
				addServerPortVars(route, matchPorts)
			}
		}

		t.fillHTTPRoutePoliciesForHTTPRoute(tctx, routes, rule)
		service.Routes = routes

		result.Services = append(result.Services, service)
	}

	return result, nil
}

func (t *Translator) translateGatewayHTTPRouteMatch(match *gatewayv1.HTTPRouteMatch) (*adctypes.Route, error) {
	route := &adctypes.Route{}

	if match.Path != nil {
		switch *match.Path.Type {
		case gatewayv1.PathMatchExact:
			route.Uris = []string{*match.Path.Value}
		case gatewayv1.PathMatchPathPrefix:
			pathValue := *match.Path.Value
			route.Uris = []string{pathValue}

			if strings.HasSuffix(pathValue, "/") {
				route.Uris = append(route.Uris, pathValue+"*")
			} else {
				route.Uris = append(route.Uris, pathValue+"/*")
			}
		case gatewayv1.PathMatchRegularExpression:
			var this []adctypes.StringOrSlice
			this = append(this, adctypes.StringOrSlice{
				StrVal: "uri",
			})
			this = append(this, adctypes.StringOrSlice{
				StrVal: "~~",
			})
			this = append(this, adctypes.StringOrSlice{
				StrVal: *match.Path.Value,
			})

			route.Vars = append(route.Vars, this)
			// APISIX Admin API requires uris to be a non-null array. Use "/*"
			// as a catch-all so APISIX accepts the route; the vars entry above
			// performs the actual regex filtering.
			route.Uris = []string{"/*"}
		default:
			return nil, errors.New("unknown path match type " + string(*match.Path.Type))
		}
	} else {
		/* If no matches are specified, the default is a prefix
		path match on "/", which has the effect of matching every
		HTTP request. */
		route.Uris = []string{"/", "/*"}
	}

	if len(match.Headers) > 0 {
		for _, header := range match.Headers {
			this, err := t.translateHTTPRouteHeaderMatchToVars(header)
			if err != nil {
				return nil, err
			}
			route.Vars = append(route.Vars, this)
		}
	}

	if len(match.QueryParams) > 0 {
		for _, query := range match.QueryParams {
			var this []adctypes.StringOrSlice
			this = append(this, adctypes.StringOrSlice{
				StrVal: "arg_" + fmt.Sprintf("%v", query.Name),
			})

			queryType := gatewayv1.QueryParamMatchExact
			if query.Type != nil {
				queryType = *query.Type
			}

			switch queryType {
			case gatewayv1.QueryParamMatchExact:
				this = append(this, adctypes.StringOrSlice{
					StrVal: "==",
				})
			case gatewayv1.QueryParamMatchRegularExpression:
				this = append(this, adctypes.StringOrSlice{
					StrVal: "~~",
				})
			default:
				return nil, errors.New("unknown query match type " + string(queryType))
			}

			this = append(this, adctypes.StringOrSlice{
				StrVal: query.Value,
			})

			route.Vars = append(route.Vars, this)
		}
	}

	if match.Method != nil {
		route.Methods = []string{
			string(*match.Method),
		}
	}

	return route, nil
}

func HeaderMatchToVars(matchType, name, value string) ([]adctypes.StringOrSlice, error) {
	name = strings.ReplaceAll(name, "-", "_")

	var this []adctypes.StringOrSlice
	this = append(this, adctypes.StringOrSlice{
		StrVal: "http_" + name,
	})

	switch matchType {
	case string(gatewayv1.HeaderMatchExact):
		this = append(this, adctypes.StringOrSlice{
			StrVal: "==",
		})
	case string(gatewayv1.HeaderMatchRegularExpression):
		this = append(this, adctypes.StringOrSlice{
			StrVal: "~~",
		})
	default:
		return nil, errors.New("unknown header match type " + matchType)
	}

	this = append(this, adctypes.StringOrSlice{
		StrVal: value,
	})
	return this, nil
}

func (t *Translator) translateHTTPRouteHeaderMatchToVars(header gatewayv1.HTTPHeaderMatch) ([]adctypes.StringOrSlice, error) {
	var matchType string
	if header.Type != nil {
		matchType = string(*header.Type)
	}
	return HeaderMatchToVars(matchType, string(header.Name), header.Value)
}

func appProtocolToUpstreamScheme(appProtocol string) string {
	switch appProtocol {
	case internaltypes.AppProtocolHTTP:
		return apiv2.SchemeHTTP
	case internaltypes.AppProtocolHTTPS:
		return apiv2.SchemeHTTPS
	case internaltypes.AppProtocolWS:
		return apiv2.SchemeHTTP
	case internaltypes.AppProtocolWSS:
		return apiv2.SchemeHTTPS
	default:
		return ""
	}
}

func addServerPortVars(route *adctypes.Route, ports map[int32]struct{}) {
	if len(ports) == 0 {
		return
	}

	// For single port, use exact match
	if len(ports) == 1 {
		for port := range ports {
			portVar := []adctypes.StringOrSlice{
				{StrVal: "server_port"},
				{StrVal: "=="},
				{StrVal: fmt.Sprintf("%d", port)},
			}
			route.Vars = append(route.Vars, portVar)
			return
		}
	}

	// For multiple ports, use "in" operator
	// Sort ports for deterministic output
	sortedPorts := make([]int, 0, len(ports))
	for port := range ports {
		sortedPorts = append(sortedPorts, int(port))
	}
	sort.Ints(sortedPorts)

	portList := make([]adctypes.StringOrSlice, 0, len(ports))
	for _, port := range sortedPorts {
		portList = append(portList, adctypes.StringOrSlice{StrVal: fmt.Sprintf("%d", port)})
	}
	portVar := []adctypes.StringOrSlice{
		{StrVal: "server_port"},
		{StrVal: "in"},
		{SliceVal: portList},
	}
	route.Vars = append(route.Vars, portVar)
}
