/*
 *
 * Copyright 2021 gRPC 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 xdsresource

import (
	"regexp"
	"time"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/internal/xds/matcher"
	"google.golang.org/grpc/xds/internal/clusterspecifier"
	"google.golang.org/grpc/xds/internal/httpfilter"
	"google.golang.org/protobuf/types/known/anypb"
)

// RouteConfigUpdate contains information received in an RDS response, which is
// of interest to the registered RDS watcher.
type RouteConfigUpdate struct {
	VirtualHosts []*VirtualHost
	// ClusterSpecifierPlugins are the LB Configurations for any
	// ClusterSpecifierPlugins referenced by the Route Table.
	ClusterSpecifierPlugins map[string]clusterspecifier.BalancerConfig
	// Raw is the resource from the xds response.
	Raw *anypb.Any
}

// VirtualHost contains the routes for a list of Domains.
//
// Note that the domains in this slice can be a wildcard, not an exact string.
// The consumer of this struct needs to find the best match for its hostname.
type VirtualHost struct {
	Domains []string
	// Routes contains a list of routes, each containing matchers and
	// corresponding action.
	Routes []*Route
	// HTTPFilterConfigOverride contains any HTTP filter config overrides for
	// the virtual host which may be present.  An individual filter's override
	// may be unused if the matching Route contains an override for that
	// filter.
	HTTPFilterConfigOverride map[string]httpfilter.FilterConfig
	RetryConfig              *RetryConfig
}

// RetryConfig contains all retry-related configuration in either a VirtualHost
// or Route.
type RetryConfig struct {
	// RetryOn is a set of status codes on which to retry.  Only Canceled,
	// DeadlineExceeded, Internal, ResourceExhausted, and Unavailable are
	// supported; any other values will be omitted.
	RetryOn      map[codes.Code]bool
	NumRetries   uint32       // maximum number of retry attempts
	RetryBackoff RetryBackoff // retry backoff policy
}

// RetryBackoff describes the backoff policy for retries.
type RetryBackoff struct {
	BaseInterval time.Duration // initial backoff duration between attempts
	MaxInterval  time.Duration // maximum backoff duration
}

// HashPolicyType specifies the type of HashPolicy from a received RDS Response.
type HashPolicyType int

const (
	// HashPolicyTypeHeader specifies to hash a Header in the incoming request.
	HashPolicyTypeHeader HashPolicyType = iota
	// HashPolicyTypeChannelID specifies to hash a unique Identifier of the
	// Channel. In grpc-go, this will be done using the ClientConn pointer.
	HashPolicyTypeChannelID
)

// HashPolicy specifies the HashPolicy if the upstream cluster uses a hashing
// load balancer.
type HashPolicy struct {
	HashPolicyType HashPolicyType
	Terminal       bool
	// Fields used for type HEADER.
	HeaderName        string
	Regex             *regexp.Regexp
	RegexSubstitution string
}

// RouteActionType is the action of the route from a received RDS response.
type RouteActionType int

const (
	// RouteActionUnsupported are routing types currently unsupported by grpc.
	// According to A36, "A Route with an inappropriate action causes RPCs
	// matching that route to fail."
	RouteActionUnsupported RouteActionType = iota
	// RouteActionRoute is the expected route type on the client side. Route
	// represents routing a request to some upstream cluster. On the client
	// side, if an RPC matches to a route that is not RouteActionRoute, the RPC
	// will fail according to A36.
	RouteActionRoute
	// RouteActionNonForwardingAction is the expected route type on the server
	// side. NonForwardingAction represents when a route will generate a
	// response directly, without forwarding to an upstream host.
	RouteActionNonForwardingAction
)

// Route is both a specification of how to match a request as well as an
// indication of the action to take upon match.
type Route struct {
	Path   *string
	Prefix *string
	Regex  *regexp.Regexp
	// Indicates if prefix/path matching should be case insensitive. The default
	// is false (case sensitive).
	CaseInsensitive bool
	Headers         []*HeaderMatcher
	Fraction        *uint32

	HashPolicies []*HashPolicy

	// If the matchers above indicate a match, the below configuration is used.
	// If MaxStreamDuration is nil, it indicates neither of the route action's
	// max_stream_duration fields (grpc_timeout_header_max nor
	// max_stream_duration) were set.  In this case, the ListenerUpdate's
	// MaxStreamDuration field should be used.  If MaxStreamDuration is set to
	// an explicit zero duration, the application's deadline should be used.
	MaxStreamDuration *time.Duration
	// HTTPFilterConfigOverride contains any HTTP filter config overrides for
	// the route which may be present.  An individual filter's override may be
	// unused if the matching WeightedCluster contains an override for that
	// filter.
	HTTPFilterConfigOverride map[string]httpfilter.FilterConfig
	RetryConfig              *RetryConfig

	ActionType RouteActionType

	// Only one of the following fields (WeightedClusters or
	// ClusterSpecifierPlugin) will be set for a route.
	WeightedClusters map[string]WeightedCluster
	// ClusterSpecifierPlugin is the name of the Cluster Specifier Plugin that
	// this Route is linked to, if specified by xDS.
	ClusterSpecifierPlugin string
}

// WeightedCluster contains settings for an xds ActionType.WeightedCluster.
type WeightedCluster struct {
	// Weight is the relative weight of the cluster.  It will never be zero.
	Weight uint32
	// HTTPFilterConfigOverride contains any HTTP filter config overrides for
	// the weighted cluster which may be present.
	HTTPFilterConfigOverride map[string]httpfilter.FilterConfig
}

// HeaderMatcher represents header matchers.
type HeaderMatcher struct {
	Name         string
	InvertMatch  *bool
	ExactMatch   *string
	RegexMatch   *regexp.Regexp
	PrefixMatch  *string
	SuffixMatch  *string
	RangeMatch   *Int64Range
	PresentMatch *bool
}

// Int64Range is a range for header range match.
type Int64Range struct {
	Start int64
	End   int64
}

// SecurityConfig contains the security configuration received as part of the
// Cluster resource on the client-side, and as part of the Listener resource on
// the server-side.
type SecurityConfig struct {
	// RootInstanceName identifies the certProvider plugin to be used to fetch
	// root certificates. This instance name will be resolved to the plugin name
	// and its associated configuration from the certificate_providers field of
	// the bootstrap file.
	RootInstanceName string
	// RootCertName is the certificate name to be passed to the plugin (looked
	// up from the bootstrap file) while fetching root certificates.
	RootCertName string
	// IdentityInstanceName identifies the certProvider plugin to be used to
	// fetch identity certificates. This instance name will be resolved to the
	// plugin name and its associated configuration from the
	// certificate_providers field of the bootstrap file.
	IdentityInstanceName string
	// IdentityCertName is the certificate name to be passed to the plugin
	// (looked up from the bootstrap file) while fetching identity certificates.
	IdentityCertName string
	// SubjectAltNameMatchers is an optional list of match criteria for SANs
	// specified on the peer certificate. Used only on the client-side.
	//
	// Some intricacies:
	// - If this field is empty, then any peer certificate is accepted.
	// - If the peer certificate contains a wildcard DNS SAN, and an `exact`
	//   matcher is configured, a wildcard DNS match is performed instead of a
	//   regular string comparison.
	SubjectAltNameMatchers []matcher.StringMatcher
	// RequireClientCert indicates if the server handshake process expects the
	// client to present a certificate. Set to true when performing mTLS. Used
	// only on the server-side.
	RequireClientCert bool
}

// Equal returns true if sc is equal to other.
func (sc *SecurityConfig) Equal(other *SecurityConfig) bool {
	switch {
	case sc == nil && other == nil:
		return true
	case (sc != nil) != (other != nil):
		return false
	}
	switch {
	case sc.RootInstanceName != other.RootInstanceName:
		return false
	case sc.RootCertName != other.RootCertName:
		return false
	case sc.IdentityInstanceName != other.IdentityInstanceName:
		return false
	case sc.IdentityCertName != other.IdentityCertName:
		return false
	case sc.RequireClientCert != other.RequireClientCert:
		return false
	default:
		if len(sc.SubjectAltNameMatchers) != len(other.SubjectAltNameMatchers) {
			return false
		}
		for i := 0; i < len(sc.SubjectAltNameMatchers); i++ {
			if !sc.SubjectAltNameMatchers[i].Equal(other.SubjectAltNameMatchers[i]) {
				return false
			}
		}
	}
	return true
}

// RouteConfigUpdateErrTuple is a tuple with the update and error. It contains
// the results from unmarshal functions. It's used to pass unmarshal results of
// multiple resources together, e.g. in maps like `map[string]{Update,error}`.
type RouteConfigUpdateErrTuple struct {
	Update RouteConfigUpdate
	Err    error
}
