package constants

import (
	"context"
	"strings"

	contractconstants "github.com/pingcap-inc/nexuscrm-contract/pkg/constants"
)

type standardActionContextKey string

const (
	contextKeyStandardActionID             standardActionContextKey = "standard_action_id"
	contextKeyStandardActionRef            standardActionContextKey = "standard_action_ref"
	contextKeyStandardActionRootFlow       standardActionContextKey = "standard_action_root_flow"
	contextKeyStandardActionProtectedRoot  standardActionContextKey = "standard_action_protected_root"
	contextKeyStandardActionAccessScopeSet standardActionContextKey = "standard_action_access_scope_set"
)

// StandardActionInternalAccessScope is a declarative permission exception for
// access owned by a standard action. It can widen read/RLS visibility or bypass
// user object CRUD/FLS only for the matching action, object, and operation; it
// does not grant broad system context or direct-SQL privileges.
type StandardActionInternalAccessScope struct {
	StandardActionID string
	ObjectAPIName    string
	Operation        string
	EnforceFLS       bool
	// IgnoreImplicitFieldDenials narrows EnforceFLS to EXPLICIT field
	// permissions only. Field visibility normally falls back to object-level
	// read when a user has no FieldPermissions row for a field; an engine read
	// that must not inherit the actor's object grants can set this so that a
	// missing row reads as visible while an explicit PermissionsRead=false row
	// still hides the field. Meaningless unless EnforceFLS is set.
	IgnoreImplicitFieldDenials bool
}

type standardActionScopePolicy struct {
	enforceFLS                 bool
	ignoreImplicitFieldDenials bool
}

// WithStandardActionInternalAccessScopes stamps the active standard action and
// its metadata-owned internal access scopes onto ctx for downstream query and
// persistence calls made by Flow and subflows.
func WithStandardActionInternalAccessScopes(ctx context.Context, actionID string, scopes []StandardActionInternalAccessScope) context.Context {
	actionID = strings.TrimSpace(actionID)
	if actionID == "" {
		return ctx
	}
	ctx = WithStandardActionExecutionContext(ctx, actionID)
	if len(scopes) == 0 {
		return ctx
	}
	scopeSet := make(map[string]standardActionScopePolicy, len(scopes))
	for _, scope := range scopes {
		if !strings.EqualFold(strings.TrimSpace(scope.StandardActionID), actionID) {
			continue
		}
		objectName := strings.TrimSpace(scope.ObjectAPIName)
		operation := strings.TrimSpace(scope.Operation)
		if objectName == "" || operation == "" {
			continue
		}
		key := standardActionScopeKey(actionID, objectName, operation)
		policy := scopeSet[key]
		policy.enforceFLS = policy.enforceFLS || scope.EnforceFLS
		policy.ignoreImplicitFieldDenials = policy.ignoreImplicitFieldDenials || scope.IgnoreImplicitFieldDenials
		scopeSet[key] = policy
	}
	if len(scopeSet) == 0 {
		return ctx
	}
	return context.WithValue(ctx, contextKeyStandardActionAccessScopeSet, scopeSet)
}

func WithStandardActionExecutionContext(ctx context.Context, actionID string) context.Context {
	actionID = strings.TrimSpace(actionID)
	if actionID == "" {
		return ctx
	}
	return context.WithValue(ctx, contextKeyStandardActionID, actionID)
}

// WithStandardActionExecutionRef stamps the neutral Catalog action reference
// selected from immutable App metadata. It complements the repository-local
// action ID so revision-bound Catalog actions can satisfy guards derived from
// legacy metadata identities without pretending the two IDs are equal.
func WithStandardActionExecutionRef(ctx context.Context, actionRef string) context.Context {
	actionRef = strings.TrimSpace(actionRef)
	if actionRef == "" {
		return ctx
	}
	return context.WithValue(ctx, contextKeyStandardActionRef, actionRef)
}

func StandardActionExecutionRef(ctx context.Context) string {
	actionRef, _ := ctx.Value(contextKeyStandardActionRef).(string)
	return strings.TrimSpace(actionRef)
}

func WithStandardActionExecutionRootFlow(ctx context.Context, actionID string, rootFlowName string) context.Context {
	ctx = WithStandardActionExecutionContext(ctx, actionID)
	rootFlowName = strings.TrimSpace(rootFlowName)
	if rootFlowName == "" {
		return ctx
	}
	return context.WithValue(ctx, contextKeyStandardActionRootFlow, rootFlowName)
}

func HasStandardActionExecutionContext(ctx context.Context) bool {
	actionID, _ := ctx.Value(contextKeyStandardActionID).(string)
	return strings.TrimSpace(actionID) != ""
}

// StandardActionExecutionActionID returns the standard-action id stamped on
// ctx by WithStandardActionExecutionContext / WithStandardActionExecutionRootFlow,
// trimmed. Returns "" when no action context is active. Mirrors the read shape
// of HasStandardActionExecutionContext / StandardActionExecutionOwnsRootFlow,
// exposed publicly for the create-from-source guard in
// backend/internal/features/records that needs the literal id (not just a
// boolean) to compare against owning-action entries.
func StandardActionExecutionActionID(ctx context.Context) string {
	actionID, _ := ctx.Value(contextKeyStandardActionID).(string)
	return strings.TrimSpace(actionID)
}

func WithoutStandardActionExecutionContext(ctx context.Context) context.Context {
	ctx = context.WithValue(ctx, contextKeyStandardActionID, "")
	ctx = context.WithValue(ctx, contextKeyStandardActionRef, "")
	return context.WithValue(ctx, contextKeyStandardActionRootFlow, "")
}

func StandardActionExecutionOwnsRootFlow(ctx context.Context, flowName string) bool {
	if !HasStandardActionExecutionContext(ctx) {
		return false
	}
	rootFlowName, _ := ctx.Value(contextKeyStandardActionRootFlow).(string)
	return strings.EqualFold(strings.TrimSpace(rootFlowName), strings.TrimSpace(flowName)) &&
		strings.TrimSpace(flowName) != ""
}

func WithStandardActionProtectedFlowExecution(ctx context.Context, rootFlowName string) context.Context {
	rootFlowName = strings.TrimSpace(rootFlowName)
	if rootFlowName == "" {
		return ctx
	}
	return context.WithValue(ctx, contextKeyStandardActionProtectedRoot, rootFlowName)
}

func HasStandardActionProtectedFlowExecution(ctx context.Context) bool {
	rootFlowName, _ := ctx.Value(contextKeyStandardActionProtectedRoot).(string)
	return strings.TrimSpace(rootFlowName) != ""
}

func WithoutStandardActionProtectedFlowExecution(ctx context.Context) context.Context {
	return context.WithValue(ctx, contextKeyStandardActionProtectedRoot, "")
}

func HasStandardActionInternalAccessScopes(ctx context.Context) bool {
	actionID, _ := ctx.Value(contextKeyStandardActionID).(string)
	if strings.TrimSpace(actionID) == "" {
		return false
	}
	scopeSet, _ := ctx.Value(contextKeyStandardActionAccessScopeSet).(map[string]standardActionScopePolicy)
	return len(scopeSet) > 0
}

func WithoutStandardActionInternalAccessScopes(ctx context.Context) context.Context {
	return context.WithValue(ctx, contextKeyStandardActionAccessScopeSet, map[string]standardActionScopePolicy{})
}

// IsScopedInternalAccess returns true only when ctx carries an active
// standard-action id and a matching metadata-owned access scope.
func IsScopedInternalAccess(ctx context.Context, objectAPIName string, operation string) bool {
	_, ok := standardActionScopePolicyFor(ctx, objectAPIName, operation)
	return ok
}

// ScopedInternalAccessEnforcesFLS reports whether a matching scoped access
// entry preserves field-level security for this object and operation.
func ScopedInternalAccessEnforcesFLS(ctx context.Context, objectAPIName string, operation string) bool {
	policy, ok := standardActionScopePolicyFor(ctx, objectAPIName, operation)
	return ok && policy.enforceFLS
}

// ScopedInternalAccessIgnoresImplicitFieldDenials reports whether a matching
// scoped access entry narrows field-level security to explicit
// FieldPermissions rows, treating a missing row as visible instead of
// deferring to object-level read.
func ScopedInternalAccessIgnoresImplicitFieldDenials(ctx context.Context, objectAPIName string, operation string) bool {
	policy, ok := standardActionScopePolicyFor(ctx, objectAPIName, operation)
	return ok && policy.ignoreImplicitFieldDenials
}

// ScopedInternalAccessBypassesFLS reports whether a matching scoped access
// entry keeps the legacy full field-visibility bypass for this object and
// operation.
func ScopedInternalAccessBypassesFLS(ctx context.Context, objectAPIName string, operation string) bool {
	policy, ok := standardActionScopePolicyFor(ctx, objectAPIName, operation)
	return ok && !policy.enforceFLS
}

func standardActionScopePolicyFor(ctx context.Context, objectAPIName string, operation string) (standardActionScopePolicy, bool) {
	actionID, _ := ctx.Value(contextKeyStandardActionID).(string)
	if strings.TrimSpace(actionID) == "" {
		return standardActionScopePolicy{}, false
	}
	scopeSet, _ := ctx.Value(contextKeyStandardActionAccessScopeSet).(map[string]standardActionScopePolicy)
	if len(scopeSet) == 0 {
		return standardActionScopePolicy{}, false
	}
	policy, ok := scopeSet[standardActionScopeKey(actionID, objectAPIName, operation)]
	return policy, ok
}

// WithConnectorInboundFlowExecution marks ctx as a connector-inbound worker
// execution of exactly rootFlowName (the route-resolved root flow), routed by
// the connector pack identified by namespace. It authorizes direct execution of
// that one pack-owned protected standard flow WITHOUT granting standard-action
// execution context or unlocking standard-action-only actions. Authorization
// requires BOTH name match (root-flow-scoped) AND same-pack ownership: a flow
// owned by a different pack is not authorized even if names match. Protected
// pack-owned standard SUBflows invoked under the same ctx are NOT authorized.
//
// If either rootFlowName or namespace is empty the ctx is returned unchanged
// (fail-safe: the protected flow will be rejected at authorize time).
func WithConnectorInboundFlowExecution(ctx context.Context, rootFlowName, namespace string) context.Context {
	return contractconstants.WithConnectorInboundFlowExecution(ctx, rootFlowName, namespace)
}

// ConnectorInboundExecutionAuthorizesFlow reports whether ctx was stamped by the
// connector-inbound worker for exactly flowName as the routed root flow
// (case-insensitive) AND flowOwnerPack matches the routing connector namespace
// (same-pack, case-insensitive). Returns false for:
//   - a different flow name (descendant/subflow protection)
//   - a flow owned by a different pack than the routing connector (cross-pack isolation)
//   - an empty flowName or flowOwnerPack (fail-safe)
func ConnectorInboundExecutionAuthorizesFlow(ctx context.Context, flowName, flowOwnerPack string) bool {
	return contractconstants.ConnectorInboundExecutionAuthorizesFlow(ctx, flowName, flowOwnerPack)
}

func standardActionScopeKey(actionID string, objectAPIName string, operation string) string {
	return strings.ToLower(strings.TrimSpace(actionID)) + "|" +
		strings.ToLower(strings.TrimSpace(objectAPIName)) + "|" +
		strings.ToLower(strings.TrimSpace(operation))
}

// WithConnectorInboundCompanionFlowExecution stamps ctx with the frozen companion
// flow version id for a cross-pack connector-inbound event. The worker stamps this
// ONLY after the drain-time incarnation re-check passes (§5.2 of the design).
// If companionFlowVersionID is empty, ctx is returned unchanged (fail-safe).
func WithConnectorInboundCompanionFlowExecution(ctx context.Context, companionFlowVersionID string) context.Context {
	return contractconstants.WithConnectorInboundCompanionFlowExecution(ctx, companionFlowVersionID)
}

// ConnectorInboundCompanionExecutionAuthorizesVersion reports whether ctx was
// stamped by the connector-inbound worker for a cross-pack companion flow AND
// the executing flow version id matches the frozen companion version id
// (case-insensitive). Returns false when:
//   - ctx carries no companion marker
//   - executingFlowVersionID is empty (fail-safe)
//   - frozen id does not match executingFlowVersionID
func ConnectorInboundCompanionExecutionAuthorizesVersion(ctx context.Context, executingFlowVersionID string) bool {
	return contractconstants.ConnectorInboundCompanionExecutionAuthorizesVersion(ctx, executingFlowVersionID)
}
