package proxy

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"strings"
	"sync"
	"time"

	"github.com/sandbox0-ai/sandbox0/infra-operator/api/config"
	"github.com/sandbox0-ai/sandbox0/manager/pkg/apis/sandbox0/v1alpha1"
	"github.com/sandbox0-ai/sandbox0/netd/pkg/policy"
	"github.com/sandbox0-ai/sandbox0/pkg/egressauth"
)

type egressAuthContext struct {
	Rule                             *policy.CompiledEgressAuthRule
	CandidateRules                   []*policy.CompiledEgressAuthRule
	RequestMatch                     bool
	ResolveOnHTTPRequest             bool
	Resolved                         *egressauth.ResolveResponse
	ResolvedHeaders                  map[string]string
	ResolvedPlaceholderSubstitutions []resolvedPlaceholderSubstitution
	ResolvedTLSClientCertificate     *resolvedTLSClientCertificate
	ResolvedUsernamePassword         *resolvedUsernamePassword
	ResolvedSSHProxy                 *resolvedSSHProxy
	CacheHit                         bool
	ResolveAttempt                   bool
	ResolveError                     error
	FailurePolicy                    string
	BypassReason                     string
	EnforcementReason                string
}

type resolvedTLSClientCertificate struct {
	Certificate tls.Certificate
	RootCAs     *x509.CertPool
}

type resolvedUsernamePassword struct {
	Username string
	Password string
}

type resolvedSSHProxy struct {
	SandboxPublicKeys []string
	UpstreamUsername  string
	PrivateKeyPEM     string
	Passphrase        string
	KnownHosts        []string
}

type resolvedPlaceholderSubstitution struct {
	Placeholder string
	Value       string
	Locations   []egressauth.PlaceholderSubstitutionLocation
}

type egressAuthResolver interface {
	Resolve(context.Context, *egressauth.ResolveRequest) (*egressauth.ResolveResponse, error)
}

type egressAuthCache interface {
	Get(egressAuthCacheKey) (*egressauth.ResolveResponse, bool)
	Put(egressAuthCacheKey, *egressauth.ResolveResponse)
	IsStale(*egressauth.ResolveResponse) bool
	InvalidateSource(teamID string, sourceID int64, sourceRef string, sourceVersion int64) int
}

type egressAuthCacheKey struct {
	SandboxID       string
	AuthRef         string
	Destination     string
	DestinationPort int
	Transport       string
	Protocol        string
}

type egressAuthSourceKey struct {
	TeamID    string
	SourceID  int64
	SourceRef string
}

type memoryEgressAuthCache struct {
	mu               sync.RWMutex
	entries          map[egressAuthCacheKey]*egressauth.ResolveResponse
	sourceWatermarks map[egressAuthSourceKey]int64
	now              func() time.Time
}

type noopEgressAuthResolver struct{}

type ServerOption func(*Server)

var errEgressAuthResolverUnconfigured = errors.New("egress auth resolver is not configured")
var errEgressAuthDirectiveUnsupported = errors.New("egress auth directives unsupported by adapter")
var errEgressAuthMaterialUnavailable = errors.New("egress auth material unavailable")
var errEgressAuthDirectiveInvalid = errors.New("egress auth directive invalid")
var errEgressAuthStaleResolvedMaterial = errors.New("egress auth resolved material is older than known credential source version")

func WithEgressAuthResolver(resolver egressAuthResolver) ServerOption {
	return func(s *Server) {
		if s != nil {
			s.authResolver = resolver
		}
	}
}

func WithEgressAuthCache(cache egressAuthCache) ServerOption {
	return func(s *Server) {
		if s != nil {
			s.authCache = cache
		}
	}
}

func WithTLSInterceptAuthority(authority tlsInterceptAuthority) ServerOption {
	return func(s *Server) {
		if s != nil {
			s.tlsAuthority = authority
		}
	}
}

func WithUpstreamTLSConfig(cfg *tls.Config) ServerOption {
	return func(s *Server) {
		if s != nil {
			s.upstreamTLSConfig = cloneTLSConfig(cfg)
		}
	}
}

func newMemoryEgressAuthCache() *memoryEgressAuthCache {
	return &memoryEgressAuthCache{
		entries:          make(map[egressAuthCacheKey]*egressauth.ResolveResponse),
		sourceWatermarks: make(map[egressAuthSourceKey]int64),
		now: func() time.Time {
			return time.Now().UTC()
		},
	}
}

func (c *memoryEgressAuthCache) Get(key egressAuthCacheKey) (*egressauth.ResolveResponse, bool) {
	if c == nil {
		return nil, false
	}
	c.mu.Lock()
	entry := c.entries[key]
	if entry == nil {
		c.mu.Unlock()
		return nil, false
	}
	if egressAuthCacheEntryExpired(entry, c.now()) || c.responseStaleLocked(entry) {
		delete(c.entries, key)
		proxyMetrics.SetEgressAuthCacheEntries(len(c.entries))
		c.mu.Unlock()
		return nil, false
	}
	cloned := cloneResolveResponse(entry)
	c.mu.Unlock()
	return cloned, true
}

func (c *memoryEgressAuthCache) Put(key egressAuthCacheKey, value *egressauth.ResolveResponse) {
	if c == nil || value == nil {
		return
	}
	c.mu.Lock()
	if c.responseStaleLocked(value) {
		c.mu.Unlock()
		return
	}
	c.entries[key] = cloneResolveResponse(value)
	proxyMetrics.SetEgressAuthCacheEntries(len(c.entries))
	c.mu.Unlock()
}

func (c *memoryEgressAuthCache) IsStale(value *egressauth.ResolveResponse) bool {
	if c == nil || value == nil {
		return false
	}
	c.mu.RLock()
	stale := c.responseStaleLocked(value)
	c.mu.RUnlock()
	return stale
}

func (c *memoryEgressAuthCache) InvalidateSource(teamID string, sourceID int64, sourceRef string, sourceVersion int64) int {
	if c == nil {
		return 0
	}
	sourceKey, ok := egressAuthSourceKeyFromValues(teamID, sourceID, sourceRef)
	if !ok {
		return 0
	}

	c.mu.Lock()
	if sourceVersion > c.sourceWatermarks[sourceKey] {
		c.sourceWatermarks[sourceKey] = sourceVersion
	}
	removed := 0
	for key, entry := range c.entries {
		if !egressAuthSourceMatches(entry.Source, sourceKey) {
			continue
		}
		if egressAuthSourceVersionBefore(entry.Source, sourceVersion) {
			delete(c.entries, key)
			removed++
		}
	}
	if removed > 0 {
		proxyMetrics.SetEgressAuthCacheEntries(len(c.entries))
	}
	c.mu.Unlock()
	return removed
}

func egressAuthCacheEntryExpired(entry *egressauth.ResolveResponse, now time.Time) bool {
	return entry != nil && entry.ExpiresAt != nil && !entry.ExpiresAt.After(now)
}

func (c *memoryEgressAuthCache) responseStaleLocked(value *egressauth.ResolveResponse) bool {
	sourceKey, ok := egressAuthSourceKeyFromResolveSource(valueSource(value))
	if !ok {
		return false
	}
	watermark := c.sourceWatermarks[sourceKey]
	return watermark > 0 && egressAuthSourceVersionBefore(value.Source, watermark)
}

func valueSource(value *egressauth.ResolveResponse) *egressauth.ResolveSource {
	if value == nil {
		return nil
	}
	return value.Source
}

func egressAuthSourceKeyFromResolveSource(source *egressauth.ResolveSource) (egressAuthSourceKey, bool) {
	if source == nil {
		return egressAuthSourceKey{}, false
	}
	return egressAuthSourceKeyFromValues(source.TeamID, source.SourceID, source.SourceRef)
}

func egressAuthSourceKeyFromValues(teamID string, sourceID int64, sourceRef string) (egressAuthSourceKey, bool) {
	teamID = strings.TrimSpace(teamID)
	sourceRef = strings.TrimSpace(sourceRef)
	if sourceID <= 0 && sourceRef == "" {
		return egressAuthSourceKey{}, false
	}
	if sourceID > 0 {
		return egressAuthSourceKey{
			TeamID:   teamID,
			SourceID: sourceID,
		}, true
	}
	return egressAuthSourceKey{
		TeamID:    teamID,
		SourceRef: sourceRef,
	}, true
}

func egressAuthSourceMatches(source *egressauth.ResolveSource, target egressAuthSourceKey) bool {
	sourceKey, ok := egressAuthSourceKeyFromResolveSource(source)
	if !ok {
		return false
	}
	if target.TeamID != "" && sourceKey.TeamID != "" && target.TeamID != sourceKey.TeamID {
		return false
	}
	if target.SourceID > 0 && sourceKey.SourceID > 0 {
		return target.SourceID == sourceKey.SourceID
	}
	return target.SourceRef != "" && sourceKey.SourceRef != "" && target.SourceRef == sourceKey.SourceRef
}

func egressAuthSourceVersionBefore(source *egressauth.ResolveSource, sourceVersion int64) bool {
	if sourceVersion <= 0 {
		return true
	}
	if source == nil || source.SourceVersion <= 0 {
		return true
	}
	return source.SourceVersion < sourceVersion
}

func (s *Server) InvalidateEgressAuthSource(teamID string, sourceID int64, sourceRef string, sourceVersion int64) int {
	if s == nil || s.authCache == nil {
		return 0
	}
	return s.authCache.InvalidateSource(teamID, sourceID, sourceRef, sourceVersion)
}

func (noopEgressAuthResolver) Resolve(_ context.Context, _ *egressauth.ResolveRequest) (*egressauth.ResolveResponse, error) {
	return nil, nil
}

func cloneResolveResponse(in *egressauth.ResolveResponse) *egressauth.ResolveResponse {
	return egressauth.CloneResolveResponse(in)
}

func cloneResolvedHeaders(in map[string]string) map[string]string {
	if len(in) == 0 {
		return nil
	}
	out := make(map[string]string, len(in))
	for key, value := range in {
		out[key] = value
	}
	return out
}

func hasResolvedHTTPAuthMaterial(ctx *egressAuthContext) bool {
	return ctx != nil &&
		ctx.Resolved != nil &&
		(len(ctx.ResolvedHeaders) > 0 || len(ctx.ResolvedPlaceholderSubstitutions) > 0)
}

func egressAuthRuleRef(ctx *egressAuthContext) string {
	if ctx == nil || ctx.Rule == nil {
		return ""
	}
	return ctx.Rule.AuthRef
}

func resolveHTTPDirectivesForAdapter(ctx *egressAuthContext, allowHTTPDirectives bool) (map[string]string, []resolvedPlaceholderSubstitution, error) {
	if ctx == nil || ctx.Resolved == nil {
		return nil, nil, errEgressAuthMaterialUnavailable
	}

	if len(ctx.Resolved.Directives) == 0 && len(ctx.Resolved.Headers) > 0 {
		if !allowHTTPDirectives {
			return nil, nil, errEgressAuthDirectiveUnsupported
		}
		return cloneResolvedHeaders(ctx.Resolved.Headers), nil, nil
	}

	if !allowHTTPDirectives {
		if len(ctx.Resolved.Directives) > 0 || len(ctx.Resolved.Headers) > 0 {
			return nil, nil, errEgressAuthDirectiveUnsupported
		}
		return nil, nil, errEgressAuthMaterialUnavailable
	}

	headers := map[string]string{}
	replacements := []resolvedPlaceholderSubstitution{}
	for _, directive := range ctx.Resolved.Directives {
		switch directive.Kind {
		case egressauth.ResolveDirectiveKindHTTPHeaders:
			if directive.HTTPHeaders == nil {
				continue
			}
			for key, value := range directive.HTTPHeaders.Headers {
				headers[key] = value
			}
		case egressauth.ResolveDirectiveKindPlaceholderSubstitution:
			if directive.PlaceholderSubstitution == nil {
				continue
			}
			for _, replacement := range directive.PlaceholderSubstitution.Replacements {
				if strings.TrimSpace(replacement.Placeholder) == "" || len(replacement.Locations) == 0 {
					return nil, nil, errEgressAuthDirectiveInvalid
				}
				replacements = append(replacements, resolvedPlaceholderSubstitution{
					Placeholder: replacement.Placeholder,
					Value:       replacement.Value,
					Locations:   append([]egressauth.PlaceholderSubstitutionLocation(nil), replacement.Locations...),
				})
			}
		default:
			return nil, nil, errEgressAuthDirectiveUnsupported
		}
	}
	if len(headers) == 0 && len(replacements) == 0 {
		return nil, nil, errEgressAuthMaterialUnavailable
	}
	if len(headers) == 0 {
		headers = nil
	}
	return headers, replacements, nil
}

func prepareHTTPHeaderDirectives(ctx *egressAuthContext, protocol string, allowHTTPHeaders bool) error {
	if ctx == nil || ctx.Rule == nil {
		return nil
	}
	if ctx.ShouldBypass() {
		return nil
	}
	if ctx.ResolveError != nil {
		return ctx.ResolveError
	}
	if ctx.Resolved == nil {
		applyEgressAuthFailurePolicy(ctx, protocol, "material_unavailable")
		return errEgressAuthMaterialUnavailable
	}

	headers, replacements, err := resolveHTTPDirectivesForAdapter(ctx, allowHTTPHeaders)
	if err == nil {
		ctx.ResolvedHeaders = headers
		ctx.ResolvedPlaceholderSubstitutions = replacements
		return nil
	}
	applyEgressAuthDirectiveError(ctx, protocol, err)
	return err
}

func resolveTLSClientCertificateForAdapter(ctx *egressAuthContext, allowTLSClientCertificate bool) (*resolvedTLSClientCertificate, error) {
	if ctx == nil || ctx.Resolved == nil {
		return nil, errEgressAuthMaterialUnavailable
	}
	if !allowTLSClientCertificate {
		if len(ctx.Resolved.Directives) > 0 || len(ctx.Resolved.Headers) > 0 {
			return nil, errEgressAuthDirectiveUnsupported
		}
		return nil, errEgressAuthMaterialUnavailable
	}
	for _, directive := range ctx.Resolved.Directives {
		switch directive.Kind {
		case egressauth.ResolveDirectiveKindTLSClientCertificate:
			if directive.TLSClientCertificate == nil {
				continue
			}
			keyPair, err := tls.X509KeyPair(
				[]byte(directive.TLSClientCertificate.CertificatePEM),
				[]byte(directive.TLSClientCertificate.PrivateKeyPEM),
			)
			if err != nil {
				return nil, errEgressAuthDirectiveInvalid
			}
			var rootCAs *x509.CertPool
			if caPEM := strings.TrimSpace(directive.TLSClientCertificate.CAPEM); caPEM != "" {
				rootCAs = x509.NewCertPool()
				if !rootCAs.AppendCertsFromPEM([]byte(caPEM)) {
					return nil, errEgressAuthDirectiveInvalid
				}
			}
			return &resolvedTLSClientCertificate{
				Certificate: keyPair,
				RootCAs:     rootCAs,
			}, nil
		default:
			return nil, errEgressAuthDirectiveUnsupported
		}
	}
	if len(ctx.Resolved.Headers) > 0 {
		return nil, errEgressAuthDirectiveUnsupported
	}
	return nil, errEgressAuthMaterialUnavailable
}

func prepareTLSClientCertificateDirectives(ctx *egressAuthContext, protocol string, allowTLSClientCertificate bool) error {
	if ctx == nil || ctx.Rule == nil {
		return nil
	}
	if ctx.ShouldBypass() {
		return nil
	}
	if ctx.ResolveError != nil {
		return ctx.ResolveError
	}
	if ctx.Resolved == nil {
		applyEgressAuthFailurePolicy(ctx, protocol, "material_unavailable")
		return errEgressAuthMaterialUnavailable
	}

	material, err := resolveTLSClientCertificateForAdapter(ctx, allowTLSClientCertificate)
	if err == nil {
		ctx.ResolvedTLSClientCertificate = material
		return nil
	}
	applyEgressAuthDirectiveError(ctx, protocol, err)
	return err
}

func resolveUsernamePasswordForAdapter(ctx *egressAuthContext, allowUsernamePassword bool) (*resolvedUsernamePassword, error) {
	if ctx == nil || ctx.Resolved == nil {
		return nil, errEgressAuthMaterialUnavailable
	}
	if !allowUsernamePassword {
		if len(ctx.Resolved.Directives) > 0 || len(ctx.Resolved.Headers) > 0 {
			return nil, errEgressAuthDirectiveUnsupported
		}
		return nil, errEgressAuthMaterialUnavailable
	}
	for _, directive := range ctx.Resolved.Directives {
		switch directive.Kind {
		case egressauth.ResolveDirectiveKindUsernamePassword:
			if directive.UsernamePassword == nil {
				continue
			}
			username := strings.TrimSpace(directive.UsernamePassword.Username)
			password := strings.TrimSpace(directive.UsernamePassword.Password)
			if username == "" || password == "" {
				return nil, errEgressAuthDirectiveInvalid
			}
			return &resolvedUsernamePassword{
				Username: username,
				Password: password,
			}, nil
		default:
			return nil, errEgressAuthDirectiveUnsupported
		}
	}
	if len(ctx.Resolved.Headers) > 0 {
		return nil, errEgressAuthDirectiveUnsupported
	}
	return nil, errEgressAuthMaterialUnavailable
}

func prepareUsernamePasswordDirectives(ctx *egressAuthContext, protocol string, allowUsernamePassword bool) error {
	if ctx == nil || ctx.Rule == nil {
		return nil
	}
	if ctx.ShouldBypass() {
		return nil
	}
	if ctx.ResolveError != nil {
		return ctx.ResolveError
	}
	if ctx.Resolved == nil {
		applyEgressAuthFailurePolicy(ctx, protocol, "material_unavailable")
		return errEgressAuthMaterialUnavailable
	}

	material, err := resolveUsernamePasswordForAdapter(ctx, allowUsernamePassword)
	if err == nil {
		ctx.ResolvedUsernamePassword = material
		return nil
	}
	applyEgressAuthDirectiveError(ctx, protocol, err)
	return err
}

func resolveSSHProxyForAdapter(ctx *egressAuthContext, allowSSHProxy bool) (*resolvedSSHProxy, error) {
	if ctx == nil || ctx.Resolved == nil {
		return nil, errEgressAuthMaterialUnavailable
	}
	if !allowSSHProxy {
		if len(ctx.Resolved.Directives) > 0 || len(ctx.Resolved.Headers) > 0 {
			return nil, errEgressAuthDirectiveUnsupported
		}
		return nil, errEgressAuthMaterialUnavailable
	}
	for _, directive := range ctx.Resolved.Directives {
		switch directive.Kind {
		case egressauth.ResolveDirectiveKindSSHProxy:
			if directive.SSHProxy == nil {
				continue
			}
			upstreamUsername := strings.TrimSpace(directive.SSHProxy.UpstreamUsername)
			privateKeyPEM := strings.TrimSpace(directive.SSHProxy.PrivateKeyPEM)
			if upstreamUsername == "" || privateKeyPEM == "" || len(directive.SSHProxy.SandboxPublicKeys) == 0 {
				return nil, errEgressAuthDirectiveInvalid
			}
			return &resolvedSSHProxy{
				SandboxPublicKeys: append([]string(nil), directive.SSHProxy.SandboxPublicKeys...),
				UpstreamUsername:  upstreamUsername,
				PrivateKeyPEM:     privateKeyPEM,
				Passphrase:        directive.SSHProxy.Passphrase,
				KnownHosts:        append([]string(nil), directive.SSHProxy.KnownHosts...),
			}, nil
		default:
			return nil, errEgressAuthDirectiveUnsupported
		}
	}
	if len(ctx.Resolved.Headers) > 0 {
		return nil, errEgressAuthDirectiveUnsupported
	}
	return nil, errEgressAuthMaterialUnavailable
}

func prepareSSHProxyDirectives(ctx *egressAuthContext, protocol string, allowSSHProxy bool) error {
	if ctx == nil || ctx.Rule == nil {
		return nil
	}
	if ctx.ShouldBypass() {
		return nil
	}
	if ctx.ResolveError != nil {
		return ctx.ResolveError
	}
	if ctx.Resolved == nil {
		applyEgressAuthFailurePolicy(ctx, protocol, "material_unavailable")
		return errEgressAuthMaterialUnavailable
	}

	material, err := resolveSSHProxyForAdapter(ctx, allowSSHProxy)
	if err == nil {
		ctx.ResolvedSSHProxy = material
		return nil
	}
	applyEgressAuthDirectiveError(ctx, protocol, err)
	return err
}

func applyEgressAuthDirectiveError(ctx *egressAuthContext, protocol string, err error) {
	switch {
	case errors.Is(err, errEgressAuthDirectiveUnsupported):
		applyEgressAuthFailurePolicy(ctx, protocol, "unsupported_directive")
	case errors.Is(err, errEgressAuthMaterialUnavailable):
		applyEgressAuthFailurePolicy(ctx, protocol, "material_unavailable")
	case errors.Is(err, errEgressAuthDirectiveInvalid):
		applyEgressAuthFailurePolicy(ctx, protocol, "invalid_directive")
	default:
		applyEgressAuthFailurePolicy(ctx, protocol, "resolve_error")
	}
}

func (s *Server) attachEgressAuth(req *adapterRequest, decision trafficDecision) {
	candidates := egressAuthRuleCandidates(decision)
	if req == nil || len(candidates) == 0 {
		return
	}
	rule := candidates[0]
	ctx := &egressAuthContext{
		Rule:                 rule,
		CandidateRules:       append([]*policy.CompiledEgressAuthRule(nil), candidates...),
		RequestMatch:         rule.HTTPMatch != nil,
		ResolveOnHTTPRequest: deferEgressAuthResolveUntilHTTPRequest(decision, rule),
		FailurePolicy:        egressAuthFailurePolicy(s.cfg, rule),
	}
	req.EgressAuth = ctx
	if !egressAuthEnabled(s.cfg) {
		ctx.BypassReason = "cluster_disabled"
		proxyMetrics.RecordEgressAuthDecision(decision.Protocol, "bypassed", ctx.BypassReason)
		return
	}
	if ctx.RequestMatch || ctx.ResolveOnHTTPRequest {
		return
	}
	decision.MatchedAuthRule = rule
	s.resolveEgressAuth(req, decision)
}

func deferEgressAuthResolveUntilHTTPRequest(decision trafficDecision, rule *policy.CompiledEgressAuthRule) bool {
	return rule != nil &&
		rule.HTTPMatch == nil &&
		decision.Protocol == "tls" &&
		(rule.Protocol == "" || rule.Protocol == v1alpha1.EgressAuthProtocolHTTPS) &&
		rule.TLSMode == v1alpha1.EgressTLSModeTerminateReoriginate
}

func egressAuthRuleCandidates(decision trafficDecision) []*policy.CompiledEgressAuthRule {
	if len(decision.AuthRuleCandidates) > 0 {
		return decision.AuthRuleCandidates
	}
	if decision.MatchedAuthRule != nil {
		return []*policy.CompiledEgressAuthRule{decision.MatchedAuthRule}
	}
	return nil
}

func (s *Server) resolveEgressAuth(req *adapterRequest, decision trafficDecision) {
	if req == nil || req.EgressAuth == nil || decision.MatchedAuthRule == nil {
		return
	}
	ctx := req.EgressAuth
	if s == nil {
		return
	}

	key := buildEgressAuthCacheKey(req, decision)
	if s.authCache != nil {
		if resolved, ok := s.authCache.Get(key); ok {
			ctx.Resolved = resolved
			ctx.CacheHit = true
			ctx.EnforcementReason = "cache_hit"
			proxyMetrics.RecordEgressAuthResolve(decision.Protocol, "cache_hit", 0)
			proxyMetrics.RecordEgressAuthDecision(decision.Protocol, "enforced", ctx.EnforcementReason)
			return
		}
	}
	if s.authResolver == nil {
		ctx.ResolveError = errEgressAuthResolverUnconfigured
		applyEgressAuthFailurePolicy(ctx, decision.Protocol, "resolver_unconfigured")
		return
	}

	resolveReq := &egressauth.ResolveRequest{
		SandboxID:       compiledSandboxID(req.Compiled),
		TeamID:          compiledTeamID(req.Compiled),
		AuthRef:         decision.MatchedAuthRule.AuthRef,
		RuleName:        decision.MatchedAuthRule.Name,
		Destination:     authDestination(req),
		DestinationPort: req.DestPort,
		Transport:       decision.Transport,
		Protocol:        decision.Protocol,
	}
	ctx.ResolveAttempt = true
	start := time.Now()
	resolved, err := s.resolveFreshEgressAuth(context.Background(), resolveReq)
	duration := time.Since(start)
	if err != nil {
		ctx.ResolveError = err
		proxyMetrics.RecordEgressAuthResolve(decision.Protocol, "error", duration)
		applyEgressAuthFailurePolicy(ctx, decision.Protocol, "resolve_error")
		return
	}
	if resolved == nil {
		proxyMetrics.RecordEgressAuthResolve(decision.Protocol, "empty", duration)
		applyEgressAuthFailurePolicy(ctx, decision.Protocol, "material_unavailable")
		return
	}
	ctx.Resolved = resolved
	ctx.EnforcementReason = "resolved"
	if s.authCache != nil {
		s.authCache.Put(key, resolved)
	}
	proxyMetrics.RecordEgressAuthResolve(decision.Protocol, "resolved", duration)
	proxyMetrics.RecordEgressAuthDecision(decision.Protocol, "enforced", ctx.EnforcementReason)
}

func (s *Server) resolveFreshEgressAuth(ctx context.Context, req *egressauth.ResolveRequest) (*egressauth.ResolveResponse, error) {
	if s == nil || s.authResolver == nil {
		return nil, errEgressAuthResolverUnconfigured
	}
	resolved, err := s.authResolver.Resolve(ctx, req)
	if err != nil || resolved == nil || s.authCache == nil || !s.authCache.IsStale(resolved) {
		return resolved, err
	}
	resolved, err = s.authResolver.Resolve(ctx, req)
	if err != nil {
		return nil, err
	}
	if resolved != nil && s.authCache.IsStale(resolved) {
		return nil, errEgressAuthStaleResolvedMaterial
	}
	return resolved, nil
}

func (c *egressAuthContext) ShouldBypass() bool {
	return c != nil && c.BypassReason != ""
}

func (c *egressAuthContext) FailOpen() bool {
	return c != nil && c.FailurePolicy == string(policyFailureOpen())
}

func egressAuthEnabled(cfg *config.NetdConfig) bool {
	return cfg != nil && cfg.EgressAuthEnabled
}

func egressAuthFailurePolicy(cfg *config.NetdConfig, rule *policy.CompiledEgressAuthRule) string {
	if rule != nil && rule.FailurePolicy != "" {
		return string(rule.FailurePolicy)
	}
	if cfg != nil && strings.TrimSpace(cfg.EgressAuthFailurePolicy) != "" {
		return strings.TrimSpace(cfg.EgressAuthFailurePolicy)
	}
	return string(policyFailureClosed())
}

func applyEgressAuthFailurePolicy(ctx *egressAuthContext, protocol, reason string) {
	if ctx == nil {
		return
	}
	if ctx.FailOpen() {
		ctx.BypassReason = reason
		proxyMetrics.RecordEgressAuthDecision(protocol, "bypassed", reason)
		return
	}
	ctx.EnforcementReason = reason
	proxyMetrics.RecordEgressAuthDecision(protocol, "blocked", reason)
}

func policyFailureClosed() v1alpha1.EgressAuthFailurePolicy {
	return v1alpha1.EgressAuthFailurePolicyFailClosed
}

func policyFailureOpen() v1alpha1.EgressAuthFailurePolicy {
	return v1alpha1.EgressAuthFailurePolicyFailOpen
}

func buildEgressAuthCacheKey(req *adapterRequest, decision trafficDecision) egressAuthCacheKey {
	return egressAuthCacheKey{
		SandboxID:       compiledSandboxID(req.Compiled),
		AuthRef:         decision.MatchedAuthRule.AuthRef,
		Destination:     authDestination(req),
		DestinationPort: req.DestPort,
		Transport:       decision.Transport,
		Protocol:        decision.Protocol,
	}
}

func authDestination(req *adapterRequest) string {
	if req == nil {
		return ""
	}
	if host := strings.TrimSpace(req.Host); host != "" {
		return host
	}
	if req.DestIP != nil {
		return req.DestIP.String()
	}
	return ""
}

func compiledSandboxID(compiled *policy.CompiledPolicy) string {
	if compiled == nil {
		return ""
	}
	return compiled.SandboxID
}

func compiledTeamID(compiled *policy.CompiledPolicy) string {
	if compiled == nil {
		return ""
	}
	return compiled.TeamID
}
