package constants

import (
	"context"
	"encoding/hex"
	"strings"
)

// ContextKey is a typed key for context values to prevent collisions.
type ContextKey string

const (
	ContextKeyRequestID           ContextKey = "request_id"
	ContextKeyConnectorSyncOrigin ContextKey = "connector_sync_origin"
	ContextKeyPerfTiming          ContextKey = "perf_timing"
)

// SecurityContext controls which security checks are bypassed.
type SecurityContext int

const (
	UserContext     SecurityContext = 0
	ServiceContext  SecurityContext = 10
	ElevatedContext SecurityContext = 20
	SystemContext   SecurityContext = 30
)

func (sc SecurityContext) BypassesProcessFields() bool        { return sc >= ServiceContext }
func (sc SecurityContext) BypassesProtectedFieldWrites() bool { return sc >= ServiceContext }
func (sc SecurityContext) BypassesReferenceTargetWriteRestrictions() bool {
	return sc >= ServiceContext
}
func (sc SecurityContext) BypassesCRUDandFLS() bool { return sc >= ElevatedContext }
func (sc SecurityContext) BypassesRLS() bool        { return sc >= SystemContext }

type securityContextKey string

const (
	contextKeySecurityLevel  securityContextKey = "security_level"
	contextKeySecurityReason securityContextKey = "security_reason"
)

func WithSecurityContext(ctx context.Context, level SecurityContext, reason string) context.Context {
	ctx = context.WithValue(ctx, contextKeySecurityLevel, level)
	ctx = context.WithValue(ctx, contextKeySecurityReason, reason)
	return ctx
}

func GetSecurityContext(ctx context.Context) SecurityContext {
	if v, ok := ctx.Value(contextKeySecurityLevel).(SecurityContext); ok {
		return v
	}
	return UserContext
}

type flowContextKey string

const (
	contextKeyConnectorInboundRootFlow         flowContextKey = "connector_inbound_root_flow"
	contextKeyConnectorInboundNamespace        flowContextKey = "connector_inbound_namespace"
	contextKeyConnectorInboundCompanionVersion flowContextKey = "connector_inbound_companion_flow_version_id"
	contextKeyConnectorInboundTrace            flowContextKey = "connector_inbound_trace"
)

// ConnectorInboundTrace is a redacted execution-correlation envelope shared
// by the connector worker and Flow runtime. Hash fields must already be
// one-way digests; the contract intentionally has no field for raw source IDs,
// hydrated records, payloads, or person attributes.
type ConnectorInboundTrace struct {
	WorkItemID         string                      `json:"-"`
	EventID            string                      `json:"-"`
	Attempt            int                         `json:"-"`
	Namespace          string                      `json:"-"`
	ObjectKind         string                      `json:"-"`
	SourceIDHash       string                      `json:"-"`
	HydratedRecordHash string                      `json:"-"`
	HydratedFieldCount int                         `json:"-"`
	Related            []ConnectorInboundTraceItem `json:"-"`
}

// ConnectorInboundTraceItem carries additional event/work-item lineage when
// one batch Flow execution represents multiple connector inputs.
type ConnectorInboundTraceItem struct {
	WorkItemID         string `json:"-"`
	EventID            string `json:"-"`
	Attempt            int    `json:"-"`
	ObjectKind         string `json:"-"`
	SourceIDHash       string `json:"-"`
	HydratedRecordHash string `json:"-"`
	HydratedFieldCount int    `json:"-"`
}

// WithConnectorInboundTrace stamps a validated, whitespace-normalized trace.
// EventID and Namespace form the minimum immutable connector lineage. A blank
// minimum shadows any parent trace so downstream readers fail closed.
func WithConnectorInboundTrace(ctx context.Context, trace ConnectorInboundTrace) context.Context {
	trace.Related = append([]ConnectorInboundTraceItem(nil), trace.Related...)
	trace.WorkItemID = strings.TrimSpace(trace.WorkItemID)
	trace.EventID = strings.TrimSpace(trace.EventID)
	if trace.Attempt < 0 {
		trace.Attempt = 0
	}
	trace.Namespace = strings.TrimSpace(trace.Namespace)
	trace.ObjectKind = strings.TrimSpace(trace.ObjectKind)
	trace.SourceIDHash = canonicalConnectorInboundTraceHash(trace.SourceIDHash)
	trace.HydratedRecordHash = canonicalConnectorInboundTraceHash(trace.HydratedRecordHash)
	if trace.HydratedFieldCount < 0 {
		trace.HydratedFieldCount = 0
	}
	for i := range trace.Related {
		trace.Related[i].WorkItemID = strings.TrimSpace(trace.Related[i].WorkItemID)
		trace.Related[i].EventID = strings.TrimSpace(trace.Related[i].EventID)
		if trace.Related[i].Attempt < 0 {
			trace.Related[i].Attempt = 0
		}
		trace.Related[i].ObjectKind = strings.TrimSpace(trace.Related[i].ObjectKind)
		trace.Related[i].SourceIDHash = canonicalConnectorInboundTraceHash(trace.Related[i].SourceIDHash)
		trace.Related[i].HydratedRecordHash = canonicalConnectorInboundTraceHash(trace.Related[i].HydratedRecordHash)
		if trace.Related[i].HydratedFieldCount < 0 {
			trace.Related[i].HydratedFieldCount = 0
		}
	}
	if trace.EventID == "" || trace.Namespace == "" {
		// Shadow any parent value so an invalid child attempt cannot be
		// attributed to an earlier event in the same context chain.
		return context.WithValue(ctx, contextKeyConnectorInboundTrace, ConnectorInboundTrace{})
	}
	return context.WithValue(ctx, contextKeyConnectorInboundTrace, trace)
}

func canonicalConnectorInboundTraceHash(value string) string {
	const prefix = "sha256:"
	value = strings.TrimSpace(value)
	if !strings.HasPrefix(strings.ToLower(value), prefix) {
		return ""
	}
	hexValue := value[len(prefix):]
	if len(hexValue) != 64 {
		return ""
	}
	if _, err := hex.DecodeString(hexValue); err != nil {
		return ""
	}
	return prefix + strings.ToLower(hexValue)
}

// ConnectorInboundTraceFromContext returns the redacted connector lineage.
func ConnectorInboundTraceFromContext(ctx context.Context) (ConnectorInboundTrace, bool) {
	if ctx == nil {
		return ConnectorInboundTrace{}, false
	}
	trace, ok := ctx.Value(contextKeyConnectorInboundTrace).(ConnectorInboundTrace)
	if !ok || trace.EventID == "" || trace.Namespace == "" {
		return ConnectorInboundTrace{}, false
	}
	trace.Related = append([]ConnectorInboundTraceItem(nil), trace.Related...)
	return trace, true
}

func WithConnectorInboundFlowExecution(ctx context.Context, rootFlowName, namespace string) context.Context {
	rootFlowName = strings.TrimSpace(rootFlowName)
	namespace = strings.TrimSpace(namespace)
	if rootFlowName == "" || namespace == "" {
		return ctx
	}
	ctx = context.WithValue(ctx, contextKeyConnectorInboundRootFlow, rootFlowName)
	return context.WithValue(ctx, contextKeyConnectorInboundNamespace, namespace)
}

func ConnectorInboundExecutionAuthorizesFlow(ctx context.Context, flowName, flowOwnerPack string) bool {
	rootFlow, _ := ctx.Value(contextKeyConnectorInboundRootFlow).(string)
	ns, _ := ctx.Value(contextKeyConnectorInboundNamespace).(string)
	flowName = strings.TrimSpace(flowName)
	flowOwnerPack = strings.TrimSpace(flowOwnerPack)
	return flowName != "" && flowOwnerPack != "" &&
		strings.EqualFold(strings.TrimSpace(rootFlow), flowName) &&
		strings.EqualFold(strings.TrimSpace(ns), flowOwnerPack)
}

func WithConnectorInboundCompanionFlowExecution(ctx context.Context, companionFlowVersionID string) context.Context {
	companionFlowVersionID = strings.TrimSpace(companionFlowVersionID)
	if companionFlowVersionID == "" {
		return ctx
	}
	return context.WithValue(ctx, contextKeyConnectorInboundCompanionVersion, companionFlowVersionID)
}

func ConnectorInboundCompanionExecutionAuthorizesVersion(ctx context.Context, executingFlowVersionID string) bool {
	frozen, _ := ctx.Value(contextKeyConnectorInboundCompanionVersion).(string)
	executingFlowVersionID = strings.TrimSpace(executingFlowVersionID)
	return frozen != "" && executingFlowVersionID != "" &&
		strings.EqualFold(strings.TrimSpace(frozen), executingFlowVersionID)
}
