// Package controlplane provides utilities for constructing Envoy xDS resources,
// including clusters, endpoints, routes, listeners, and TLS transport sockets.
// It facilitates dynamic configuration of Envoy proxies for service discovery,
// load balancing, routing, and secure communication.
//
// Functions:
//
//   - MakeCluster: Creates an Envoy Cluster resource with optional TLS configuration and endpoints.
//   - makeUpstreamTLS: Generates an upstream TLS transport socket configuration for secure connections.
//   - makeDownstreamTLS: Generates a downstream TLS transport socket configuration for secure client connections.
//   - makeEndpoint: Constructs a ClusterLoadAssignment for load balancing across specified endpoints.
//   - MakeRoute: Builds a RouteConfiguration with CORS policy and host header rewriting.
//   - MakeHTTPListener: Configures an HTTP listener with filters for gRPC-Web, CORS, and routing, with optional TLS.
//   - makeConfigSource: Creates a ConfigSource for xDS gRPC API configuration.
//
// The package leverages Envoy's go-control-plane APIs and protobuf types to
// programmatically generate configuration resources for Envoy proxies.
package controlplane

import (
	"fmt"
	"net"
	"os"
	"time"

	cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
	core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
	listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
	route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
	cors_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cors/v3"
	grpc_web_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_web/v3"
	router "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
	hcm_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
	tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
	http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3"
	matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
	resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
	"github.com/golang/protobuf/ptypes/any"
	structpb_v3 "github.com/golang/protobuf/ptypes/struct"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/known/anypb"
	"google.golang.org/protobuf/types/known/durationpb"
	"google.golang.org/protobuf/types/known/wrapperspb"
)

// Builds: typed_extension_protocol_options["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"]
func h2TypedOption() (map[string]*anypb.Any, error) {
	hp := &http_v3.HttpProtocolOptions{
		// Optional but useful:
		// CommonHttpProtocolOptions:    &corev3.HttpProtocolOptions{ /* idle_timeout, etc. */ },
		// UpstreamHttpProtocolOptions:  &corev3.UpstreamHttpProtocolOptions{ /* auto_sni, etc. */ },

		// *** This is the important part: choose the oneof = explicit_http_config
		UpstreamProtocolOptions: &http_v3.HttpProtocolOptions_ExplicitHttpConfig_{
			ExplicitHttpConfig: &http_v3.HttpProtocolOptions_ExplicitHttpConfig{
				// Inside that msg, choose protocol_config = http2_protocol_options
				ProtocolConfig: &http_v3.
					HttpProtocolOptions_ExplicitHttpConfig_Http2ProtocolOptions{
					Http2ProtocolOptions: &core_v3.Http2ProtocolOptions{},
				},
			},
		},
	}

	any, err := anypb.New(hp)
	if err != nil {
		return nil, err
	}
	return map[string]*anypb.Any{
		"envoy.extensions.upstreams.http.v3.HttpProtocolOptions": any,
	}, nil
}

// MakeCluster function constructs a cluster using the STRICT_DNS discovery type, sets a 5-second connection timeout,
// configures round-robin load balancing, and enables HTTP/2 protocol options. If a key file path is provided,
// it configures the cluster to use TLS for upstream connections by setting the transport socket.
//
// Parameters:
//   - clusterName:      The name of the Envoy cluster.
//   - certFilePath:     Path to the TLS certificate file (optional; required for TLS).
//   - keyFilePath:      Path to the TLS private key file (optional; required for TLS).
//   - caFilePath:       Path to the CA certificate file (optional; required for TLS).
//   - endPoints:        Slice of EndPoint structs specifying the cluster's endpoints.
//
// Returns:
//   - *cluster_v3.Cluster: A pointer to the constructed Envoy Cluster resource.
//
// MakeCluster builds an upstream cluster. If caFilePath != "" OR both cert/key are set,
// Envoy will use TLS (and HTTP/2) to the upstream. Pass SNI = the DNS name on the
// Globular 8181 certificate (e.g., "globular.io" or your host FQDN).
func MakeCluster(
	clusterName, certFilePath, keyFilePath, caFilePath, sni string,
	endPoints []EndPoint,
) *cluster_v3.Cluster {
	typed, _ := h2TypedOption() // HTTP/2 upstream

	// STATIC for IPs, STRICT_DNS for hostnames. (Never EDS here.)
	useStrictDNS := false
	for _, ep := range endPoints {
		if net.ParseIP(ep.Host) == nil {
			useStrictDNS = true
			break
		}
	}
	discoveryType := cluster_v3.Cluster_STATIC
	if useStrictDNS {
		discoveryType = cluster_v3.Cluster_STRICT_DNS
	}

	c := &cluster_v3.Cluster{
		Name:                          clusterName,
		ConnectTimeout:                durationpb.New(5 * time.Second),
		ClusterDiscoveryType:          &cluster_v3.Cluster_Type{Type: discoveryType},
		LbPolicy:                      cluster_v3.Cluster_ROUND_ROBIN,
		LoadAssignment:                makeEndpoint(clusterName, endPoints),
		DnsLookupFamily:               cluster_v3.Cluster_V4_ONLY,
		TypedExtensionProtocolOptions: typed, // HTTP/2 upstream
		HealthChecks:                  makeGRPCHealthChecks(),
	}

	// Enable TLS if we have any trust material (CA) or mTLS (cert+key).
	if caFilePath != "" || (certFilePath != "" && keyFilePath != "") {
		c.TransportSocket = makeUpstreamTLS(certFilePath, keyFilePath, caFilePath, sni)
	}

	return c
}

// MakeClusterWithSDS creates an Envoy Cluster using SDS for TLS certificate delivery.
// Instead of reading certificates from files, Envoy fetches them dynamically via SDS.
//
// Parameters:
//   - clusterName: Name of the cluster
//   - caSecretName: SDS secret name for CA bundle (e.g., "internal-ca-bundle")
//   - clientCertSecretName: SDS secret name for client cert (optional, for mTLS)
//   - sni: Server Name Indication for TLS handshake
//   - endPoints: List of upstream endpoints
//
// Returns:
//   - *cluster_v3.Cluster configured with SDS for TLS
func MakeClusterWithSDS(
	clusterName, caSecretName, clientCertSecretName, sni string,
	endPoints []EndPoint,
) *cluster_v3.Cluster {
	typed, _ := h2TypedOption() // HTTP/2 upstream

	// STATIC for IPs, STRICT_DNS for hostnames
	useStrictDNS := false
	for _, ep := range endPoints {
		if net.ParseIP(ep.Host) == nil {
			useStrictDNS = true
			break
		}
	}
	discoveryType := cluster_v3.Cluster_STATIC
	if useStrictDNS {
		discoveryType = cluster_v3.Cluster_STRICT_DNS
	}

	c := &cluster_v3.Cluster{
		Name:                          clusterName,
		ConnectTimeout:                durationpb.New(5 * time.Second),
		ClusterDiscoveryType:          &cluster_v3.Cluster_Type{Type: discoveryType},
		LbPolicy:                      cluster_v3.Cluster_ROUND_ROBIN,
		LoadAssignment:                makeEndpoint(clusterName, endPoints),
		DnsLookupFamily:               cluster_v3.Cluster_V4_ONLY,
		TypedExtensionProtocolOptions: typed,
		HealthChecks:                  makeGRPCHealthChecks(),
	}

	// Enable TLS via SDS if CA secret provided
	if caSecretName != "" {
		c.TransportSocket = makeUpstreamTLSWithSDS(caSecretName, clientCertSecretName, sni)
	}

	return c
}

func toAny(msg protoreflect.ProtoMessage) *any.Any {
	anyMsg, err := anypb.New(msg)
	if err != nil {
		panic(err)
	}
	return anyMsg
}

// makeUpstreamTLS creates an Envoy TransportSocket configuration for upstream TLS connections.
// It sets up the TLS context using the provided certificate, private key, and CA file paths.
// The function configures ALPN protocols for HTTP/2 and HTTP/1.1, and enforces TLS 1.3 as both
// the minimum and maximum protocol version.
//
// Parameters:
//
//	certFilePath - Path to the TLS certificate file.
//	keyFilePath  - Path to the TLS private key file.
//	caFilePath   - Path to the CA certificate file for validation.
//
// Returns:
//
//	*core_v3.TransportSocket - A pointer to the configured TransportSocket for TLS.
func makeUpstreamTLS(certFilePath, keyFilePath, caFilePath, sni string) *core_v3.TransportSocket {
	utls := &tls_v3.UpstreamTlsContext{
		Sni: sni,
		CommonTlsContext: &tls_v3.CommonTlsContext{
			AlpnProtocols: []string{"h2"}, // gRPC over TLS
		},
	}

	// Validate server (Globular) with CA if provided.
	// Use an empty MatchTypedSubjectAltNames list to skip SAN-vs-SNI
	// verification — internal cluster services share the same CA but may
	// present certs whose SANs don't match the xDS-visible hostname.
	if caFilePath != "" {
		utls.CommonTlsContext.ValidationContextType = &tls_v3.CommonTlsContext_ValidationContext{
			ValidationContext: &tls_v3.CertificateValidationContext{
				TrustedCa: &core_v3.DataSource{
					Specifier: &core_v3.DataSource_Filename{Filename: caFilePath},
				},
				MatchTypedSubjectAltNames: []*tls_v3.SubjectAltNameMatcher{},
			},
		}
	}

	// Optional mTLS: client cert/key from Envoy to Globular
	if certFilePath != "" && keyFilePath != "" {
		utls.CommonTlsContext.TlsCertificates = []*tls_v3.TlsCertificate{{
			CertificateChain: &core_v3.DataSource{
				Specifier: &core_v3.DataSource_Filename{Filename: certFilePath},
			},
			PrivateKey: &core_v3.DataSource{
				Specifier: &core_v3.DataSource_Filename{Filename: keyFilePath},
			},
		}}
	}

	return &core_v3.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &core_v3.TransportSocket_TypedConfig{
			TypedConfig: toAny(utls),
		},
	}
}

// makeDownstreamTLS creates an Envoy TransportSocket configuration for downstream TLS connections.
// It sets up the TLS context using the provided certificate, private key, and CA file paths.
// The function configures ALPN protocols for HTTP/2 and HTTP/1.1, and enforces TLS 1.3 as both
// the minimum and maximum protocol version.
//
// Parameters:
//
//	certFilePath - Path to the TLS certificate file.
//	keyFilePath  - Path to the TLS private key file.
//	caFilePath   - Path to the CA certificate file for client certificate validation.
//
// Returns:
//
//	*core_v3.TransportSocket - A pointer to the configured TransportSocket for downstream TLS.
func makeDownstreamTLS(certFilePath, keyFilePath, caFilePath string) *core_v3.TransportSocket {
	common := &tls_v3.CommonTlsContext{
		TlsCertificates: []*tls_v3.TlsCertificate{{
			CertificateChain: &core_v3.DataSource{
				Specifier: &core_v3.DataSource_Filename{
					Filename: certFilePath,
				},
			},
			PrivateKey: &core_v3.DataSource{
				Specifier: &core_v3.DataSource_Filename{
					Filename: keyFilePath,
				},
			},
		}},
		AlpnProtocols: []string{"h2", "http/1.1"},
		TlsParams: &tls_v3.TlsParameters{
			TlsMinimumProtocolVersion: tls_v3.TlsParameters_TLSv1_3,
			TlsMaximumProtocolVersion: tls_v3.TlsParameters_TLSv1_3,
		},
	}

	if caFilePath != "" {
		common.ValidationContextType = &tls_v3.CommonTlsContext_ValidationContext{
			ValidationContext: &tls_v3.CertificateValidationContext{
				TrustedCa: &core_v3.DataSource{
					Specifier: &core_v3.DataSource_Filename{
						Filename: caFilePath,
					},
				},
			},
		}
	}

	return &core_v3.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &core_v3.TransportSocket_TypedConfig{
			TypedConfig: toAny(&tls_v3.DownstreamTlsContext{
				CommonTlsContext: common,
			}),
		},
	}
}

// makeDownstreamTLSWithSDS creates a downstream TLS transport socket using SDS for certificate delivery.
// Instead of reading certificates from files, Envoy fetches them dynamically via the Secret Discovery Service.
//
// Parameters:
//   - serverCertSecretName: Name of the SDS secret containing server cert/key (e.g., "internal-server-cert")
//   - caSecretName: Name of the SDS secret containing CA bundle for client validation (optional)
//
// Returns:
//   - *core_v3.TransportSocket configured for downstream TLS with SDS
func makeDownstreamTLSWithSDS(serverCertSecretName, caSecretName string) *core_v3.TransportSocket {
	common := &tls_v3.CommonTlsContext{
		TlsCertificateSdsSecretConfigs: []*tls_v3.SdsSecretConfig{{
			Name: serverCertSecretName,
			SdsConfig: &core_v3.ConfigSource{
				ResourceApiVersion: core_v3.ApiVersion_V3,
				ConfigSourceSpecifier: &core_v3.ConfigSource_Ads{
					Ads: &core_v3.AggregatedConfigSource{},
				},
			},
		}},
		AlpnProtocols: []string{"h2", "http/1.1"},
		TlsParams: &tls_v3.TlsParameters{
			TlsMinimumProtocolVersion: tls_v3.TlsParameters_TLSv1_3,
			TlsMaximumProtocolVersion: tls_v3.TlsParameters_TLSv1_3,
		},
	}

	// Optional CA validation for client certificates
	if caSecretName != "" {
		common.ValidationContextType = &tls_v3.CommonTlsContext_ValidationContextSdsSecretConfig{
			ValidationContextSdsSecretConfig: &tls_v3.SdsSecretConfig{
				Name: caSecretName,
				SdsConfig: &core_v3.ConfigSource{
					ResourceApiVersion: core_v3.ApiVersion_V3,
					ConfigSourceSpecifier: &core_v3.ConfigSource_Ads{
						Ads: &core_v3.AggregatedConfigSource{},
					},
				},
			},
		}
	}

	return &core_v3.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &core_v3.TransportSocket_TypedConfig{
			TypedConfig: toAny(&tls_v3.DownstreamTlsContext{
				CommonTlsContext: common,
			}),
		},
	}
}

// makeUpstreamTLSWithSDS creates an upstream TLS transport socket using SDS for certificate delivery.
// Envoy fetches upstream TLS certificates dynamically via the Secret Discovery Service.
//
// Parameters:
//   - caSecretName: Name of the SDS secret containing CA bundle for server validation (required)
//   - clientCertSecretName: Name of the SDS secret containing client cert/key for mTLS (optional)
//   - sni: Server Name Indication value for TLS handshake
//
// Returns:
//   - *core_v3.TransportSocket configured for upstream TLS with SDS
func makeUpstreamTLSWithSDS(caSecretName, clientCertSecretName, sni string) *core_v3.TransportSocket {
	utls := &tls_v3.UpstreamTlsContext{
		Sni: sni,
		CommonTlsContext: &tls_v3.CommonTlsContext{
			AlpnProtocols: []string{"h2"}, // gRPC over TLS
		},
	}

	// CA validation (required for upstream)
	if caSecretName != "" {
		utls.CommonTlsContext.ValidationContextType = &tls_v3.CommonTlsContext_ValidationContextSdsSecretConfig{
			ValidationContextSdsSecretConfig: &tls_v3.SdsSecretConfig{
				Name: caSecretName,
				SdsConfig: &core_v3.ConfigSource{
					ResourceApiVersion: core_v3.ApiVersion_V3,
					ConfigSourceSpecifier: &core_v3.ConfigSource_Ads{
						Ads: &core_v3.AggregatedConfigSource{},
					},
				},
			},
		}
	}

	// Optional mTLS client certificate
	if clientCertSecretName != "" {
		utls.CommonTlsContext.TlsCertificateSdsSecretConfigs = []*tls_v3.SdsSecretConfig{{
			Name: clientCertSecretName,
			SdsConfig: &core_v3.ConfigSource{
				ResourceApiVersion: core_v3.ApiVersion_V3,
				ConfigSourceSpecifier: &core_v3.ConfigSource_Ads{
					Ads: &core_v3.AggregatedConfigSource{},
				},
			},
		}}
	}

	return &core_v3.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &core_v3.TransportSocket_TypedConfig{
			TypedConfig: toAny(utls),
		},
	}
}

// makeEndpoint constructs an Envoy ClusterLoadAssignment for the given cluster name and endpoints.
// It creates a list of LbEndpoint objects, each representing an endpoint with its address, port, and priority metadata.
// The resulting ClusterLoadAssignment can be used to configure Envoy's load balancing for the specified cluster.
//
// Parameters:
//   - clusterName: The name of the cluster for which the load assignment is created.
//   - endPoints: A slice of EndPoint structs, each containing host, port, and priority information.
//
// Returns:
//   - A pointer to an endpoint.ClusterLoadAssignment populated with the provided endpoints.
func makeEndpoint(clusterName string, endPoints []EndPoint) *endpoint_v3.ClusterLoadAssignment {
	var lbEndpoints []*endpoint_v3.LbEndpoint

	for _, endPoint := range endPoints {

		lbEndpoint := &endpoint_v3.LbEndpoint{
			HostIdentifier: &endpoint_v3.LbEndpoint_Endpoint{
				Endpoint: &endpoint_v3.Endpoint{
					Address: &core_v3.Address{
						Address: &core_v3.Address_SocketAddress{
							SocketAddress: &core_v3.SocketAddress{
								Protocol: core_v3.SocketAddress_TCP,
								Address:  endPoint.Host,
								PortSpecifier: &core_v3.SocketAddress_PortValue{
									PortValue: endPoint.Port,
								},
							},
						},
					},
				},
			},
			Metadata: &core_v3.Metadata{
				FilterMetadata: map[string]*structpb_v3.Struct{
					"envoy.lb": {
						Fields: map[string]*structpb_v3.Value{
							"priority": {
								Kind: &structpb_v3.Value_NumberValue{
									NumberValue: float64(endPoint.Priority),
								},
							},
						},
					},
				},
			},
		}

		// Apply AI Router weight if set (1-100). Weight 0 = use Envoy default (equal).
		if endPoint.Weight > 0 {
			lbEndpoint.LoadBalancingWeight = wrapperspb.UInt32(endPoint.Weight)
		}

		lbEndpoints = append(lbEndpoints, lbEndpoint)
	}

	return &endpoint_v3.ClusterLoadAssignment{
		ClusterName: clusterName,
		Endpoints: []*endpoint_v3.LocalityLbEndpoints{{
			LbEndpoints: lbEndpoints,
		}},
	}
}

// MakeRoute creates an Envoy RouteConfiguration with a single virtual host and route.
// The route matches all paths ("/") and forwards requests to the specified cluster,
// rewriting the host header to the provided host value. CORS policy is configured to
// allow all origins, specific HTTP methods, and headers. The route has an infinite timeout.
//
// Parameters:
//   - routeName:     Name of the route configuration.
//   - clusterName:   Name of the target cluster for routing.
//   - host:          Host value to rewrite in the request.
//
// Returns:
//   - *route_v3.RouteConfiguration: Pointer to the constructed RouteConfiguration.
func MakeRoute(routeName string, clusterName, host string) *route_v3.RouteConfiguration {
	allowedOrigins := []*matcher_v3.StringMatcher{
		// List each origin that may send credentials
		{MatchPattern: &matcher_v3.StringMatcher_Exact{Exact: "http://localhost:5173"}},
		{MatchPattern: &matcher_v3.StringMatcher_Exact{Exact: "https://globule-ryzen.globular.io"}},
		// add more exact origins here if needed
	}

	return &route_v3.RouteConfiguration{
		Name: routeName,
		VirtualHosts: []*route_v3.VirtualHost{{
			Name:    "local_service",
			Domains: []string{"*"},
			Routes: []*route_v3.Route{{
				Match: &route_v3.RouteMatch{
					PathSpecifier: &route_v3.RouteMatch_Prefix{Prefix: "/"},
				},
				Action: &route_v3.Route_Route{
					Route: &route_v3.RouteAction{
						ClusterSpecifier: &route_v3.RouteAction_Cluster{Cluster: clusterName},
						HostRewriteSpecifier: &route_v3.RouteAction_HostRewriteLiteral{
							HostRewriteLiteral: host,
						},
						Timeout: durationpb.New(time.Duration(0)), // no per-route timeout
					},
				},
			}},
			TypedPerFilterConfig: map[string]*anypb.Any{
				"envoy.filters.http.cors": toAny(&cors_v3.CorsPolicy{
					AllowOriginStringMatch: allowedOrigins,
					// Must be true when the browser sends credentials (cookies, auth headers)
					AllowCredentials: wrapperspb.Bool(true),

					AllowMethods:  "GET, PUT, DELETE, POST, OPTIONS",
					AllowHeaders:  "keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout,domain,address,token,application,path,routing,authorization",
					ExposeHeaders: "custom-header-1,grpc-status,grpc-message",
					MaxAge:        "1728000",
				}),
			},
		}},
	}
}

// MakeHTTPListener creates and configures an Envoy HTTP listener with the specified parameters.
// It sets up HTTP filters for gRPC-Web, CORS, and routing, and optionally configures TLS if certificate and key file paths are provided.
//
// Parameters:
//
//	listenerHost   - The host address to bind the listener.
//	listenerPort   - The port number to bind the listener.
//	listenerName   - The name of the listener.
//	routeName      - The name of the route configuration.
//	certFilePath   - Path to the TLS certificate file (optional).
//	keyFilePath    - Path to the TLS key file (optional).
//	caFilePath     - Path to the CA certificate file (optional).
//
// Returns:
//
//	*listener.Listener - A pointer to the configured Envoy listener.
func MakeHTTPListener(listenerHost string, listenerPort uint32, listenerName, routeName, certFilePath, keyFilePath, caFilePath string) *listener_v3.Listener {

	// HTTP filter configuration
	manager := &hcm_v3.HttpConnectionManager{
		CodecType:  hcm_v3.HttpConnectionManager_AUTO,
		StatPrefix: "http",
		RouteSpecifier: &hcm_v3.HttpConnectionManager_Rds{
			Rds: &hcm_v3.Rds{
				ConfigSource:    makeConfigSource(), // TODO: ---> xds_cluster
				RouteConfigName: routeName,
			},
		},
		// Had necessary filters...
		HttpFilters: []*hcm_v3.HttpFilter{
			{
				Name: "envoy.filters.http.grpc_web",
				ConfigType: &hcm_v3.HttpFilter_TypedConfig{
					TypedConfig: toAny(&grpc_web_v3.GrpcWeb{}),
				},
			},
			{
				Name: "envoy.filters.http.cors",
				ConfigType: &hcm_v3.HttpFilter_TypedConfig{
					TypedConfig: toAny(&cors_v3.Cors{}),
				},
			},
			{
				Name: "envoy.filters.http.router",
				ConfigType: &hcm_v3.HttpFilter_TypedConfig{
					TypedConfig: toAny(&router.Router{}),
				},
			},
		},
	}

	// Add GrpcWeb filter if needed
	pbst, err := anypb.New(manager)
	if err != nil {
		panic(err)
	}

	l := &listener_v3.Listener{
		Name: listenerName,
		Address: &core_v3.Address{
			Address: &core_v3.Address_SocketAddress{
				SocketAddress: &core_v3.SocketAddress{
					Protocol: core_v3.SocketAddress_TCP,
					Address:  listenerHost,
					PortSpecifier: &core_v3.SocketAddress_PortValue{
						PortValue: listenerPort,
					},
				},
			},
		},
		FilterChains: []*listener_v3.FilterChain{{
			Filters: []*listener_v3.Filter{{
				Name: "envoy.filters.network.http_connection_manager",
				ConfigType: &listener_v3.Filter_TypedConfig{
					TypedConfig: pbst,
				},
			}},
		}},
	}

	// I case of TLS, we need to set the transport socket
	if len(keyFilePath) > 0 {
		l.FilterChains[0].TransportSocket = makeDownstreamTLS(certFilePath, keyFilePath, caFilePath)
	}

	return l
}

// makeHTTPConnectionManager creates an HTTP Connection Manager configuration
// that can be reused across filter chains. This helper prevents code duplication
// when creating multiple filter chains with the same HCM config.
func makeHTTPConnectionManager(routeName string) *hcm_v3.HttpConnectionManager {
	return &hcm_v3.HttpConnectionManager{
		CodecType:  hcm_v3.HttpConnectionManager_AUTO,
		StatPrefix: "http",
		// Disable stream idle timeout (default 5min) — gRPC streaming RPCs
		// like artifact downloads can take longer and are not truly idle.
		StreamIdleTimeout: durationpb.New(0),
		RouteSpecifier: &hcm_v3.HttpConnectionManager_Rds{
			Rds: &hcm_v3.Rds{
				ConfigSource:    makeConfigSource(),
				RouteConfigName: routeName,
			},
		},
		HttpFilters: []*hcm_v3.HttpFilter{
			{
				Name: "envoy.filters.http.grpc_web",
				ConfigType: &hcm_v3.HttpFilter_TypedConfig{
					TypedConfig: toAny(&grpc_web_v3.GrpcWeb{}),
				},
			},
			{
				Name: "envoy.filters.http.cors",
				ConfigType: &hcm_v3.HttpFilter_TypedConfig{
					TypedConfig: toAny(&cors_v3.Cors{}),
				},
			},
			{
				Name: "envoy.filters.http.router",
				ConfigType: &hcm_v3.HttpFilter_TypedConfig{
					TypedConfig: toAny(&router.Router{}),
				},
			},
		},
	}
}

// MakeSNIHTTPFilterChainWithSDS creates a FilterChain with SNI matching and SDS-based TLS.
// This is used for per-domain certificate routing in Envoy listeners.
//
// Parameters:
//   - routeName: Name of the route configuration to use
//   - serverNames: List of SNI hostnames to match (e.g., ["globule-ryzen.globular.cloud"])
//   - serverCertSecretName: SDS secret name for this domain's certificate (e.g., "ext-cert/globule-ryzen.globular.cloud")
//   - caSecretName: SDS secret name for CA bundle (optional, for client validation)
//
// Returns:
//   - *listener_v3.FilterChain configured with SNI matching and SDS TLS
//   - error if configuration fails
func MakeSNIHTTPFilterChainWithSDS(
	routeName string,
	serverNames []string,
	serverCertSecretName string,
	caSecretName string,
) (*listener_v3.FilterChain, error) {
	// Build HCM config
	manager := makeHTTPConnectionManager(routeName)

	pbst, err := anypb.New(manager)
	if err != nil {
		return nil, err
	}

	fc := &listener_v3.FilterChain{
		FilterChainMatch: &listener_v3.FilterChainMatch{
			ServerNames: serverNames,
		},
		Filters: []*listener_v3.Filter{{
			Name: "envoy.filters.network.http_connection_manager",
			ConfigType: &listener_v3.Filter_TypedConfig{
				TypedConfig: pbst,
			},
		}},
	}

	// Add TLS transport socket with SDS
	if serverCertSecretName != "" {
		fc.TransportSocket = makeDownstreamTLSWithSDS(serverCertSecretName, caSecretName)
	}

	return fc, nil
}

// MakeHTTPListenerWithSDS creates an HTTP listener using SDS for TLS certificate delivery.
// Instead of reading certificates from files, Envoy fetches them dynamically via SDS.
//
// Parameters:
//   - listenerHost: Bind address (e.g., "0.0.0.0")
//   - listenerPort: Port to listen on (e.g., 443)
//   - listenerName: Logical name for the listener
//   - routeName: Name of the route configuration to use
//   - serverCertSecretName: SDS secret name for server cert (e.g., "internal-server-cert")
//   - caSecretName: SDS secret name for CA bundle (optional, for client validation)
//
// Returns:
//   - *listener_v3.Listener configured with SDS for TLS
func MakeHTTPListenerWithSDS(listenerHost string, listenerPort uint32, listenerName, routeName, serverCertSecretName, caSecretName string) *listener_v3.Listener {
	// HTTP filter configuration
	manager := makeHTTPConnectionManager(routeName)

	pbst, err := anypb.New(manager)
	if err != nil {
		panic(err)
	}

	l := &listener_v3.Listener{
		Name: listenerName,
		Address: &core_v3.Address{
			Address: &core_v3.Address_SocketAddress{
				SocketAddress: &core_v3.SocketAddress{
					Protocol: core_v3.SocketAddress_TCP,
					Address:  listenerHost,
					PortSpecifier: &core_v3.SocketAddress_PortValue{
						PortValue: listenerPort,
					},
				},
			},
		},
		FilterChains: []*listener_v3.FilterChain{{
			Filters: []*listener_v3.Filter{{
				Name: "envoy.filters.network.http_connection_manager",
				ConfigType: &listener_v3.Filter_TypedConfig{
					TypedConfig: pbst,
				},
			}},
		}},
	}

	// Configure TLS via SDS if server cert secret provided
	if serverCertSecretName != "" {
		l.FilterChains[0].TransportSocket = makeDownstreamTLSWithSDS(serverCertSecretName, caSecretName)
	}

	return l
}

// MakeHTTPListenerWithSDSFilterChains creates an HTTP listener with custom SNI filter chains.
// This allows per-domain certificate routing by prepending SNI-matched chains before the default chain.
//
// Parameters:
//   - listenerHost: Bind address (e.g., "0.0.0.0")
//   - listenerPort: Port to listen on (e.g., 443)
//   - listenerName: Logical name for the listener
//   - routeName: Name of the route configuration to use
//   - defaultServerCertSecretName: SDS secret for default/fallback certificate
//   - caSecretName: SDS secret name for CA bundle (optional)
//   - extraFilterChains: SNI-matched filter chains to prepend (matched before default)
//
// Returns:
//   - *listener_v3.Listener with SNI chains + default fallback chain
func MakeHTTPListenerWithSDSFilterChains(
	listenerHost string,
	listenerPort uint32,
	listenerName string,
	routeName string,
	defaultServerCertSecretName string,
	caSecretName string,
	extraFilterChains []*listener_v3.FilterChain,
) *listener_v3.Listener {
	fmt.Fprintf(os.Stderr, "[DEBUG] MakeHTTPListenerWithSDSFilterChains: name=%s, port=%d, extraChains=%d, DEFAULT_CERT=%s\n",
		listenerName, listenerPort, len(extraFilterChains), defaultServerCertSecretName)
	// Build default filter chain (fallback when no SNI match)
	manager := makeHTTPConnectionManager(routeName)

	pbst, err := anypb.New(manager)
	if err != nil {
		panic(err)
	}

	defaultChain := &listener_v3.FilterChain{
		Filters: []*listener_v3.Filter{{
			Name: "envoy.filters.network.http_connection_manager",
			ConfigType: &listener_v3.Filter_TypedConfig{
				TypedConfig: pbst,
			},
		}},
	}

	// Add TLS to default chain
	if defaultServerCertSecretName != "" {
		defaultChain.TransportSocket = makeDownstreamTLSWithSDS(defaultServerCertSecretName, caSecretName)
	}

	// Prepend SNI chains before default (Envoy matches in order)
	allChains := append(extraFilterChains, defaultChain)

	l := &listener_v3.Listener{
		Name: listenerName,
		Address: &core_v3.Address{
			Address: &core_v3.Address_SocketAddress{
				SocketAddress: &core_v3.SocketAddress{
					Protocol: core_v3.SocketAddress_TCP,
					Address:  listenerHost,
					PortSpecifier: &core_v3.SocketAddress_PortValue{
						PortValue: listenerPort,
					},
				},
			},
		},
		FilterChains: allChains,
		// Add TLS Inspector to enable SNI-based filter chain matching
		// The TLS Inspector must be configured to extract SNI for filter chain matching
		ListenerFilters: []*listener_v3.ListenerFilter{{
			Name: "envoy.filters.listener.tls_inspector",
			ConfigType: &listener_v3.ListenerFilter_TypedConfig{
				TypedConfig: &anypb.Any{
					TypeUrl: "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector",
				},
			},
		}},
	}

	fmt.Fprintf(os.Stderr, "[DEBUG] MakeHTTPListenerWithSDSFilterChains: Created listener with %d ListenerFilters\n", len(l.ListenerFilters))
	return l
}

// !!!! here the clusterName must be the one in envoy.yml...
// ---> xds_cluster
func makeConfigSource() *core_v3.ConfigSource {
	source := &core_v3.ConfigSource{}
	source.ResourceApiVersion = resource_v3.DefaultAPIVersion
	source.ConfigSourceSpecifier = &core_v3.ConfigSource_ApiConfigSource{
		ApiConfigSource: &core_v3.ApiConfigSource{
			TransportApiVersion:       resource_v3.DefaultAPIVersion,
			ApiType:                   core_v3.ApiConfigSource_GRPC,
			SetNodeOnFirstMessageOnly: true,
			GrpcServices: []*core_v3.GrpcService{{
				TargetSpecifier: &core_v3.GrpcService_EnvoyGrpc_{
					EnvoyGrpc: &core_v3.GrpcService_EnvoyGrpc{ClusterName: "xds_cluster"},
				},
			}},
		},
	}
	return source
}

func fileDS(path string) *core_v3.DataSource {
	if path == "" {
		return nil
	}
	return &core_v3.DataSource{Specifier: &core_v3.DataSource_Filename{Filename: path}}
}

// makeGRPCHealthChecks returns the health check configuration for Envoy
// clusters. Uses the standard gRPC Health Checking Protocol which every
// Globular service implements via the grpc/health stdlib package.
//
// Configuration:
//   - Interval: 5 seconds
//   - Timeout: 3 seconds per check
//   - Unhealthy threshold: 3 consecutive failures (15s to remove)
//   - Healthy threshold: 1 success (immediate re-add)
//
// See docs/architecture/HA-control-plane-design.md §Class D.
func makeGRPCHealthChecks() []*core_v3.HealthCheck {
	grpcCheck := &core_v3.HealthCheck_GrpcHealthCheck_{
		GrpcHealthCheck: &core_v3.HealthCheck_GrpcHealthCheck{},
	}

	return []*core_v3.HealthCheck{
		{
			Timeout:            durationpb.New(3 * time.Second),
			Interval:           durationpb.New(5 * time.Second),
			UnhealthyThreshold: wrapperspb.UInt32(3),
			HealthyThreshold:   wrapperspb.UInt32(1),
			HealthChecker:      &core_v3.HealthCheck_GrpcHealthCheck_{GrpcHealthCheck: grpcCheck.GrpcHealthCheck},
		},
	}
}

// StripH2ALPN removes the "h2" ALPN protocol from a transport socket's TLS
// context. Used for HTTP/1.1-only upstream services (like the gateway) where
// h2 ALPN causes the TLS handshake to fail.
func StripH2ALPN(ts *core_v3.TransportSocket) {
	if ts == nil || ts.GetTypedConfig() == nil {
		return
	}
	utls := &tls_v3.UpstreamTlsContext{}
	if err := ts.GetTypedConfig().UnmarshalTo(utls); err != nil {
		return
	}
	if utls.CommonTlsContext == nil {
		return
	}
	// Remove h2 from ALPN, keep any other protocols.
	filtered := utls.CommonTlsContext.AlpnProtocols[:0]
	for _, p := range utls.CommonTlsContext.AlpnProtocols {
		if p != "h2" {
			filtered = append(filtered, p)
		}
	}
	utls.CommonTlsContext.AlpnProtocols = filtered

	// Re-marshal back into the transport socket.
	any, err := anypb.New(utls)
	if err != nil {
		return
	}
	ts.ConfigType = &core_v3.TransportSocket_TypedConfig{TypedConfig: any}
}

// MakeHTTPHealthChecks returns an HTTP health check for services that don't
// support gRPC health protocol (e.g. the gateway, which is HTTP-only).
func MakeHTTPHealthChecks(path string) []*core_v3.HealthCheck {
	return []*core_v3.HealthCheck{
		{
			Timeout:            durationpb.New(3 * time.Second),
			Interval:           durationpb.New(5 * time.Second),
			UnhealthyThreshold: wrapperspb.UInt32(3),
			HealthyThreshold:   wrapperspb.UInt32(1),
			HealthChecker: &core_v3.HealthCheck_HttpHealthCheck_{
				HttpHealthCheck: &core_v3.HealthCheck_HttpHealthCheck{
					Path: path,
				},
			},
		},
	}
}
