package records

import (
	"context"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/fieldpermissions"
	"github.com/nexuscrm/shared/pkg/models"
)

const (
	securityWriteValidateObjectPermissionsCapability              = "platform.security.write.object-permissions"
	securityWriteValidateFieldPermissionsCapability               = "platform.security.write.field-permissions"
	securityWriteValidatePermissionSetAssignmentTargetCapability  = "platform.security.write.permission-set-assignment-target"
	securityWriteValidateGroupUserRoleReferenceCapability         = "platform.security.write.group-user-role-reference"
	securityWriteValidateRoleBackedGroupMemberCapability          = "platform.security.write.role-backed-group-member"
	securityWriteValidateSharingRuleOwnerReferencesCapability     = "platform.security.write.sharing-rule-owner-references"
	securityWriteValidateSharingRuleShapeCapability               = "platform.security.write.sharing-rule-shape"
	securityWriteCanonicalizeSharingRuleCapability                = "platform.security.write.sharing-rule-canonicalization"
	securityWriteCanonicalizeConnectedAppPolicyCapability         = "platform.security.write.connected-app-policy-canonicalization"
	securityWriteValidateConnectedAppPolicyCapability             = "platform.security.write.connected-app-policy"
	securityWriteValidateUserRoleParentCapability                 = "platform.security.write.user-role-parent"
	securityWriteValidateUserDeleteLifecycleCapability            = constants.RecordWriteValidationUserDeleteLifecycle
	securityWriteValidateSoftDeleteReferenceGuardCapability       = "platform.security.write.soft-delete-reference-guard"
	securityWriteValidateSystemProfileOwnedImmutableCapability    = "platform.security.write.system-profile-owned-immutable"
	securityWriteValidateManagedPermissionSetDefinitionCapability = "platform.security.write.managed-permission-set-definition"
)

var (
	securityWriteValidationMetadataRequiredOnce               sync.Once
	securityWriteValidationMetadataRequiredRequirements       map[securityWriteValidationMetadataRequirement]struct{}
	securityWriteCanonicalizationMetadataRequiredOnce         sync.Once
	securityWriteCanonicalizationMetadataRequiredRequirements map[securityWriteValidationMetadataRequirement]struct{}
)

type securityWriteValidationMetadataRequirement struct {
	objectName string
	phase      string
	capability string
}

func (ps *PersistenceService) registerBuiltInSecurityWriteValidators() {
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateObjectPermissionsCapability, contract.RecordWriteValidationBeforeCreate, ps.validateObjectPermissionsWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateObjectPermissionsCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateObjectPermissionsWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateObjectPermissionsCapability, contract.RecordWriteValidationBeforeRestore, ps.validateObjectPermissionsWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateFieldPermissionsCapability, contract.RecordWriteValidationBeforeCreate, ps.validateFieldPermissionsWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateFieldPermissionsCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateFieldPermissionsWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateFieldPermissionsCapability, contract.RecordWriteValidationBeforeRestore, ps.validateFieldPermissionsWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidatePermissionSetAssignmentTargetCapability, contract.RecordWriteValidationBeforeCreate, ps.validatePermissionSetAssignmentTargetWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidatePermissionSetAssignmentTargetCapability, contract.RecordWriteValidationBeforeUpdate, ps.validatePermissionSetAssignmentTargetWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidatePermissionSetAssignmentTargetCapability, contract.RecordWriteValidationBeforeDelete, ps.validatePermissionSetAssignmentCleanupWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidatePermissionSetAssignmentTargetCapability, contract.RecordWriteValidationBeforeRestore, ps.validatePermissionSetAssignmentTargetWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidatePermissionSetAssignmentTargetCapability, contract.RecordWriteValidationBeforePurge, ps.validatePermissionSetAssignmentCleanupWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateGroupUserRoleReferenceCapability, contract.RecordWriteValidationBeforeCreate, ps.validateGroupUserRoleReferenceWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateGroupUserRoleReferenceCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateGroupUserRoleReferenceWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateRoleBackedGroupMemberCapability, contract.RecordWriteValidationBeforeCreate, ps.validateRoleBackedGroupMemberWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateRoleBackedGroupMemberCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateRoleBackedGroupMemberWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateRoleBackedGroupMemberCapability, contract.RecordWriteValidationBeforeDelete, ps.validateRoleBackedGroupMemberWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSharingRuleOwnerReferencesCapability, contract.RecordWriteValidationBeforeCreate, ps.validateSharingRuleOwnerReferencesWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSharingRuleOwnerReferencesCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateSharingRuleOwnerReferencesWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSharingRuleOwnerReferencesCapability, contract.RecordWriteValidationBeforeRestore, ps.validateSharingRuleOwnerReferencesWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSharingRuleShapeCapability, contract.RecordWriteValidationBeforeCreate, ps.validateSharingRuleShapeWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSharingRuleShapeCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateSharingRuleShapeWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSharingRuleShapeCapability, contract.RecordWriteValidationBeforeRestore, ps.validateSharingRuleShapeWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateUserRoleParentCapability, contract.RecordWriteValidationBeforeCreate, ps.validateUserRoleParentWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateUserRoleParentCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateUserRoleParentWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateUserRoleParentCapability, contract.RecordWriteValidationBeforeDelete, ps.validateUserRoleDeleteWriteContract)
	mustRegisterSecurityWriteValidator(ps, constants.RecordWriteValidationUserDeleteLifecycle, contract.RecordWriteValidationBeforeDelete, ps.validateUserDeleteLifecycleWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSoftDeleteReferenceGuardCapability, contract.RecordWriteValidationBeforeDelete, ps.validateSoftDeleteReferenceGuardWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSystemProfileOwnedImmutableCapability, contract.RecordWriteValidationBeforeCreate, ps.validateSystemProfileOwnedImmutableWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSystemProfileOwnedImmutableCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateSystemProfileOwnedImmutableWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSystemProfileOwnedImmutableCapability, contract.RecordWriteValidationBeforeDelete, ps.validateSystemProfileOwnedImmutableWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSystemProfileOwnedImmutableCapability, contract.RecordWriteValidationBeforeRestore, ps.validateSystemProfileOwnedImmutableWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateSystemProfileOwnedImmutableCapability, contract.RecordWriteValidationBeforePurge, ps.validateSystemProfileOwnedImmutableWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateManagedPermissionSetDefinitionCapability, contract.RecordWriteValidationBeforeCreate, ps.validateManagedPermissionSetDefinitionWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateManagedPermissionSetDefinitionCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateManagedPermissionSetDefinitionWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateManagedPermissionSetDefinitionCapability, contract.RecordWriteValidationBeforeDelete, ps.validateManagedPermissionSetDefinitionWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateManagedPermissionSetDefinitionCapability, contract.RecordWriteValidationBeforeRestore, ps.validateManagedPermissionSetDefinitionWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateManagedPermissionSetDefinitionCapability, contract.RecordWriteValidationBeforePurge, ps.validateManagedPermissionSetDefinitionWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateConnectedAppPolicyCapability, contract.RecordWriteValidationBeforeCreate, ps.validateConnectedAppPolicyWriteContract)
	mustRegisterSecurityWriteValidator(ps, securityWriteValidateConnectedAppPolicyCapability, contract.RecordWriteValidationBeforeUpdate, ps.validateConnectedAppPolicyWriteContract)
}

func (ps *PersistenceService) registerBuiltInSecurityWriteCanonicalizers() {
	mustRegisterSecurityWriteCanonicalizer(ps, securityWriteCanonicalizeSharingRuleCapability, contract.RecordWriteValidationBeforeCreate, ps.canonicalizeSharingRuleWriteContract)
	mustRegisterSecurityWriteCanonicalizer(ps, securityWriteCanonicalizeSharingRuleCapability, contract.RecordWriteValidationBeforeUpdate, ps.canonicalizeSharingRuleWriteContract)
	mustRegisterSecurityWriteCanonicalizer(ps, securityWriteCanonicalizeSharingRuleCapability, contract.RecordWriteValidationBeforeRestore, ps.canonicalizeSharingRuleWriteContract)
	mustRegisterSecurityWriteCanonicalizer(ps, securityWriteCanonicalizeConnectedAppPolicyCapability, contract.RecordWriteValidationBeforeCreate, ps.canonicalizeConnectedAppPolicyCreateWriteContract)
	mustRegisterSecurityWriteCanonicalizer(ps, securityWriteCanonicalizeConnectedAppPolicyCapability, contract.RecordWriteValidationBeforeUpdate, ps.canonicalizeConnectedAppPolicyUpdateWriteContract)
}

func mustRegisterSecurityWriteCanonicalizer(ps *PersistenceService, capability string, phase contract.RecordWriteValidationPhase, canonicalizer contract.RecordWriteCanonicalizer) {
	if ps == nil {
		return
	}
	if err := ps.RegisterRecordWriteCanonicalizer(contract.RecordWriteCanonicalizerRegistration{
		Capability:    capability,
		Phase:         phase,
		Canonicalizer: canonicalizer,
	}); err != nil {
		panic(err)
	}
}

func mustRegisterSecurityWriteValidator(ps *PersistenceService, capability string, phase contract.RecordWriteValidationPhase, validator contract.RecordWriteValidator) {
	if ps == nil {
		return
	}
	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: capability,
		Phase:      phase,
		Validator:  validator,
	}); err != nil {
		panic(err)
	}
}

func (ps *PersistenceService) validateObjectPermissionsWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	if !strings.EqualFold(event.ObjectName, constants.TableObjectPermissions) {
		return nil
	}
	record := contractRecordToSObject(event.Record)
	if err := validateObjectSecurityContract(event.ObjectName, record); err != nil {
		return err
	}
	if ps == nil || ps.permissions == nil {
		return nil
	}
	return ps.permissions.RejectParentControlledChildWrite(ctx, stringField(record, constants.FieldObjectPermissions_SobjectType))
}

func (ps *PersistenceService) validateFieldPermissionsWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	if !strings.EqualFold(event.ObjectName, constants.TableFieldPermissions) {
		return nil
	}
	record := contractRecordToSObject(event.Record)
	if err := validateObjectSecurityContract(event.ObjectName, record); err != nil {
		return err
	}
	if ps == nil || ps.metadata == nil {
		return nil
	}
	sobjectType := strings.TrimSpace(stringField(record, constants.FieldFieldPermissions_SobjectType))
	rawField := strings.TrimSpace(stringField(record, constants.FieldFieldPermissions_Field))
	if sobjectType == "" || rawField == "" {
		// Required-field and type validation own malformed record shapes.
		return nil
	}
	schema := ps.metadata.GetSchema(ctx, sobjectType)
	if schema == nil {
		return appErrors.NewValidationError(
			constants.FieldFieldPermissions_SobjectType,
			"FieldPermissions row references unknown SobjectType "+sobjectType,
		)
	}
	fieldObject, fieldAPIName := fieldpermissions.SplitFieldReference(sobjectType, rawField)
	if !strings.EqualFold(fieldObject, sobjectType) {
		return appErrors.NewValidationError(
			constants.FieldFieldPermissions_Field,
			"FieldPermissions field "+rawField+" does not belong to "+sobjectType,
		)
	}
	for index := range schema.Fields {
		if strings.EqualFold(strings.TrimSpace(schema.Fields[index].APIName), fieldAPIName) {
			return nil
		}
	}
	return appErrors.NewValidationError(
		constants.FieldFieldPermissions_Field,
		"FieldPermissions row references unknown field "+fieldpermissions.QualifiedField(sobjectType, fieldAPIName),
	)
}

func (ps *PersistenceService) canonicalizeSharingRuleWriteContract(_ context.Context, event contract.RecordWriteCanonicalizationEvent) error {
	if !strings.EqualFold(event.ObjectName, constants.TableSharingRule) {
		return nil
	}
	if event.Record == nil {
		return nil
	}
	record := contractRecordToSObject(event.Record)
	oldRecord := contractRecordToSObject(event.OldRecord)
	effectiveUpdates := contractRecordToSObject(event.EffectiveUpdates)
	normalizeObjectSecurityContract(event.ObjectName, record)
	if oldRecord != nil && effectiveUpdates != nil {
		materializeObjectSecurityContractClears(event.ObjectName, record, oldRecord, effectiveUpdates)
	}
	for key := range event.Record {
		delete(event.Record, key)
	}
	for key, value := range contractRecordFromSObject(record) {
		event.Record[key] = value
	}
	if event.EffectiveUpdates != nil {
		for key := range event.EffectiveUpdates {
			delete(event.EffectiveUpdates, key)
		}
		for key, value := range contractRecordFromSObject(effectiveUpdates) {
			event.EffectiveUpdates[key] = value
		}
	}
	return nil
}

func (ps *PersistenceService) canonicalizeConnectedAppPolicyCreateWriteContract(ctx context.Context, event contract.RecordWriteCanonicalizationEvent) error {
	if !strings.EqualFold(event.ObjectName, constants.TableConnectedApp) {
		return nil
	}
	if event.Record == nil {
		return nil
	}
	record := contractRecordToSObject(event.Record)
	if err := canonicalizeConnectedAppCreateWithContext(ctx, record); err != nil {
		return err
	}
	replaceContractRecordFromSObject(event.Record, record)
	return nil
}

func (ps *PersistenceService) canonicalizeConnectedAppPolicyUpdateWriteContract(ctx context.Context, event contract.RecordWriteCanonicalizationEvent) error {
	if !strings.EqualFold(event.ObjectName, constants.TableConnectedApp) {
		return nil
	}
	if event.Record == nil || event.OldRecord == nil || event.EffectiveUpdates == nil {
		return nil
	}
	record := contractRecordToSObject(event.Record)
	effectiveUpdates := contractRecordToSObject(event.EffectiveUpdates)
	if err := canonicalizeConnectedAppUpdateWithContext(ctx, record, effectiveUpdates); err != nil {
		return err
	}
	replaceContractRecordFromSObject(event.Record, record)
	replaceContractRecordFromSObject(event.EffectiveUpdates, effectiveUpdates)
	return nil
}

func replaceContractRecordFromSObject(dst contract.Record, src models.SObject) {
	if dst == nil || src == nil {
		return
	}
	for key := range dst {
		delete(dst, key)
	}
	for key, value := range contractRecordFromSObject(src) {
		dst[key] = value
	}
}

func (ps *PersistenceService) validateConnectedAppPolicyWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	if !strings.EqualFold(event.ObjectName, constants.TableConnectedApp) {
		return nil
	}
	if connectedAppPolicyFieldsUnchanged(contractRecordToSObject(event.OldRecord), contractRecordToSObject(event.Record)) {
		// A source-owned app's scope binding is immutable L1/L2 authorization
		// state. Disabling or revoking that app must not re-evaluate unchanged
		// scopes through an ownerless update request.
		return nil
	}
	policy, err := ps.connectedAppPublicScopePolicyForRequest(ctx)
	if err != nil {
		return err
	}
	return validateConnectedAppSecurityPolicyForPack(
		contractRecordToSObject(event.Record),
		policy,
		connectedAppScopeOwnerPack(ctx),
	)
}

func connectedAppPolicyFieldsUnchanged(oldRecord, record models.SObject) bool {
	if oldRecord == nil || record == nil {
		return false
	}
	for _, field := range []string{
		constants.ColSysConnectedApp_ClientType,
		constants.ColSysConnectedApp_ConsumerSecret,
		constants.ColSysConnectedApp_ConsumerSecretHash,
		constants.ColSysConnectedApp_CallbackURL,
		constants.ColSysConnectedApp_RedirectURIs,
		constants.ColSysConnectedApp_Scopes,
		constants.ColSysConnectedApp_RequirePkce,
		constants.ColSysConnectedApp_TokenEndpointAuthMethod,
		constants.ColSysConnectedApp_AllowedGrantTypes,
		constants.ColSysConnectedApp_ConsentPolicy,
		constants.ColSysConnectedApp_IssueRefreshTokens,
		constants.ColSysConnectedApp_RunAsUserID,
		constants.ColSysConnectedApp_PermittedUsers,
		constants.ColSysConnectedApp_SessionIssuerClass,
	} {
		if !reflect.DeepEqual(oldRecord[field], record[field]) {
			return false
		}
	}
	return true
}

func (ps *PersistenceService) connectedAppPublicScopePolicyForRequest(ctx context.Context) (ConnectedAppPublicScopePolicyProvider, error) {
	if ps == nil {
		return nil, fmt.Errorf("Connected App public-scope policy owner is unavailable")
	}
	if ps.oauthPublicScopePolicyResolver == nil {
		return ps.oauthPublicScopePolicy, nil
	}
	policy, err := ps.oauthPublicScopePolicyResolver(ctx)
	if err != nil {
		return nil, fmt.Errorf("resolve request-bound Connected App public-scope policy: %w", err)
	}
	if policy == nil {
		return nil, fmt.Errorf("request-bound Connected App public-scope policy is unavailable")
	}
	return policy, nil
}

func (ps *PersistenceService) validateSystemProfileOwnedImmutableWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	existingRecord := contractRecordToSObject(event.OldRecord)
	finalRecord := contractRecordToSObject(event.Record)
	// Objects whose persisted runtime schema declares an OwnerResolutionSpec are
	// enforced by the generic metadata-driven resolver. Objects whose managed
	// definition declares ownerResolution but whose persisted schema does not
	// carry the subfact are stale (auto-start skips setup apply on completed
	// installs, so an org upgraded from a pre-ownerResolution binary can serve
	// writes before the subfact is reconciled); these fail closed — consistent
	// with the (object, phase, capability) required-set. All 13 system-security
	// tables have been migrated to metadata-driven specs; objects without an
	// ownerResolution declaration pass through (no enforcement).
	if spec := ps.systemManagedOwnerResolutionSpec(ctx, event.ObjectName); spec != nil {
		return ps.rejectSystemManagedOwnerWrite(ctx, spec, existingRecord, finalRecord)
	}
	if managedDefinitionDeclaresOwnerResolution(event.ObjectName) {
		return fmt.Errorf("owner-resolution metadata not reconciled for system-profile-owned object %s: managed definition declares ownerResolution but the persisted schema does not carry it; run setup apply to reconcile", event.ObjectName)
	}
	return nil
}

// systemManagedOwnerResolutionSpec returns the object's authored owner-
// resolution spec, or nil when the object has not opted into metadata-driven
// system-managed-owner immutability.
func (ps *PersistenceService) systemManagedOwnerResolutionSpec(ctx context.Context, objectName string) *models.OwnerResolutionSpec {
	if ps.metadata == nil {
		return nil
	}
	schema := ps.metadata.GetSchema(ctx, objectName)
	if schema == nil || schema.AccessPolicy == nil || schema.AccessPolicy.WriteValidation == nil {
		return nil
	}
	return schema.AccessPolicy.WriteValidation.OwnerResolution
}

var (
	managedOwnerResolutionObjectsOnce sync.Once
	managedOwnerResolutionObjects     map[string]struct{}
)

// managedDefinitionDeclaresOwnerResolution reports whether the object's managed
// definition (embedded in the binary) authors an ownerResolution spec. It is the
// "required" side of the ownerResolution reconciliation contract, mirroring the
// (object, phase, capability) required-set: when this is true but the persisted
// runtime schema lacks the subfact, the schema is stale and the immutability
// guard must fail closed instead of silently allowing writes.
func managedDefinitionDeclaresOwnerResolution(objectName string) bool {
	managedOwnerResolutionObjectsOnce.Do(func() {
		managedOwnerResolutionObjects = map[string]struct{}{}
		for _, definition := range managedTableDefinitions() {
			if definition.AccessPolicy == nil || definition.AccessPolicy.WriteValidation == nil {
				continue
			}
			spec := definition.AccessPolicy.WriteValidation.OwnerResolution
			if spec == nil || len(spec.Paths) == 0 {
				continue
			}
			managedOwnerResolutionObjects[securityWriteValidationObjectKey(definition.TableName)] = struct{}{}
		}
	})
	_, ok := managedOwnerResolutionObjects[securityWriteValidationObjectKey(objectName)]
	return ok
}

func (ps *PersistenceService) validatePermissionSetAssignmentTargetWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	if err := ps.validatePermissionSetAssignmentTarget(ctx, event.ObjectName, contractRecordToSObject(event.Record), true); err != nil {
		return err
	}
	oldTarget, _ := event.OldRecord[constants.FieldPermissionSetAssignment_PermissionSetID].(string)
	newTarget, _ := event.Record[constants.FieldPermissionSetAssignment_PermissionSetID].(string)
	if strings.TrimSpace(oldTarget) == "" || strings.EqualFold(oldTarget, newTarget) {
		return nil
	}
	return ps.validatePermissionSetAssignmentTarget(ctx, event.ObjectName, contractRecordToSObject(event.OldRecord), false)
}

func (ps *PersistenceService) validatePermissionSetAssignmentCleanupWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	return ps.validatePermissionSetAssignmentTarget(ctx, event.ObjectName, contractRecordToSObject(event.Record), false)
}

func (ps *PersistenceService) validateGroupUserRoleReferenceWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	return ps.validateGroupUserRoleReferenceContract(ctx, event.ObjectName, event.RecordID, contractRecordToSObject(event.Record))
}

func (ps *PersistenceService) validateRoleBackedGroupMemberWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	return ps.validateRoleBackedGroupMemberMutation(ctx, event.ObjectName, contractRecordToSObject(event.Record))
}

func (ps *PersistenceService) validateSharingRuleOwnerReferencesWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	return ps.validateSharingRuleOwnerReferences(ctx, event.ObjectName, contractRecordToSObject(event.Record))
}

func (ps *PersistenceService) validateSharingRuleShapeWriteContract(_ context.Context, event contract.RecordWriteValidationEvent) error {
	if !strings.EqualFold(event.ObjectName, constants.TableSharingRule) {
		return nil
	}
	return validateSharingRuleShapeContract(contractRecordToSObject(event.Record))
}

func (ps *PersistenceService) validateUserRoleParentWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	return ps.validateUserRoleParentRole(ctx, event.ObjectName, event.RecordID, contractRecordToSObject(event.Record), userRolePendingParentsFromWriteValidationEvent(event))
}

func (ps *PersistenceService) validateUserRoleDeleteWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	return ps.validateUserRoleDeleteReferences(ctx, event.ObjectName, event.RecordID)
}

func (ps *PersistenceService) validateUserDeleteLifecycleWriteContract(_ context.Context, event contract.RecordWriteValidationEvent) error {
	return validateUserDeleteLifecycle(event.ObjectName)
}

func (ps *PersistenceService) validateSoftDeleteReferenceGuardWriteContract(ctx context.Context, event contract.RecordWriteValidationEvent) error {
	return ps.validateSoftDeleteReferenceGuard(ctx, event.ObjectName, event.RecordID)
}

func userRolePendingParentsFromWriteValidationEvent(event contract.RecordWriteValidationEvent) map[string]string {
	if !strings.EqualFold(event.ObjectName, constants.TableUserRole) || len(event.PendingCreates) == 0 {
		return nil
	}
	pendingParents := make(map[string]string, len(event.PendingCreates))
	for _, pending := range event.PendingCreates {
		record := contractRecordToSObject(pending.Record)
		roleID := strings.TrimSpace(pending.RecordID)
		if roleID == "" {
			roleID = strings.TrimSpace(stringField(record, constants.FieldID))
		}
		if roleID == "" {
			continue
		}
		pendingParents[roleID] = strings.TrimSpace(stringField(record, constants.FieldUserRole_ParentRoleID))
	}
	if len(pendingParents) == 0 {
		return nil
	}
	return pendingParents
}

// Metadata owns the normal write-validation policy; this guard only fails closed
// when core security-object metadata has not been reconciled with writeValidation.
// The required-set is keyed at (object, phase, capability) granularity so a
// partial metadata drop (a single capability removed while other validators
// remain on the same phase) hard-errors instead of silently bypassing the
// dropped guard.
func securityWriteValidationMissingRequiredCapabilities(objectName string, phase contract.RecordWriteValidationPhase, effective []string) []string {
	securityWriteValidationMetadataRequiredOnce.Do(func() {
		securityWriteValidationMetadataRequiredRequirements = securityWriteValidationMetadataRequiredObjectsFromDefinitions(managedTableDefinitions())
	})
	objectKey := securityWriteValidationObjectKey(objectName)
	phaseKey := strings.ToLower(strings.TrimSpace(string(phase)))
	present := make(map[string]struct{}, len(effective))
	for _, cap := range effective {
		present[recordWriteValidatorCapabilityKey(cap)] = struct{}{}
	}
	var missing []string
	for req := range securityWriteValidationMetadataRequiredRequirements {
		if req.objectName != objectKey || req.phase != phaseKey {
			continue
		}
		if _, ok := present[req.capability]; !ok {
			missing = append(missing, req.capability)
		}
	}
	sort.Strings(missing)
	return missing
}

func securityWriteCanonicalizationMissingRequiredCapabilities(objectName string, phase contract.RecordWriteValidationPhase, effective []string) []string {
	securityWriteCanonicalizationMetadataRequiredOnce.Do(func() {
		securityWriteCanonicalizationMetadataRequiredRequirements = securityWriteCanonicalizationMetadataRequiredObjectsFromDefinitions(managedTableDefinitions())
	})
	objectKey := securityWriteValidationObjectKey(objectName)
	phaseKey := strings.ToLower(strings.TrimSpace(string(phase)))
	present := make(map[string]struct{}, len(effective))
	for _, cap := range effective {
		present[recordWriteCanonicalizerCapabilityKey(cap)] = struct{}{}
	}
	var missing []string
	for req := range securityWriteCanonicalizationMetadataRequiredRequirements {
		if req.objectName != objectKey || req.phase != phaseKey {
			continue
		}
		if _, ok := present[req.capability]; !ok {
			missing = append(missing, req.capability)
		}
	}
	sort.Strings(missing)
	return missing
}

func securityWriteValidationMetadataRequiredObjectsFromDefinitions(definitions []schema.TableDefinition) map[securityWriteValidationMetadataRequirement]struct{} {
	required := make(map[securityWriteValidationMetadataRequirement]struct{})
	for _, definition := range definitions {
		if definition.AccessPolicy == nil || definition.AccessPolicy.WriteValidation == nil {
			continue
		}
		for _, validator := range definition.AccessPolicy.WriteValidation.Validators {
			if strings.TrimSpace(validator.Capability) == "" {
				continue
			}
			key, ok := securityWriteValidationMetadataRequirementKey(
				definition.TableName,
				contract.RecordWriteValidationPhase(validator.Phase),
				validator.Capability,
			)
			if ok {
				required[key] = struct{}{}
			}
		}
	}
	return required
}

func securityWriteCanonicalizationMetadataRequiredObjectsFromDefinitions(definitions []schema.TableDefinition) map[securityWriteValidationMetadataRequirement]struct{} {
	required := make(map[securityWriteValidationMetadataRequirement]struct{})
	for _, definition := range definitions {
		if definition.AccessPolicy == nil || definition.AccessPolicy.WriteValidation == nil {
			continue
		}
		for _, canonicalizer := range definition.AccessPolicy.WriteValidation.Canonicalizers {
			if req, ok := securityWriteValidationMetadataRequirementKey(definition.TableName, contract.RecordWriteValidationPhase(canonicalizer.Phase), canonicalizer.Capability); ok {
				required[req] = struct{}{}
			}
		}
	}
	return required
}

func securityWriteValidationObjectKey(objectName string) string {
	return strings.ToLower(strings.TrimSpace(objectName))
}

func securityWriteValidationMetadataRequirementKey(objectName string, phase contract.RecordWriteValidationPhase, capability string) (securityWriteValidationMetadataRequirement, bool) {
	objectKey := securityWriteValidationObjectKey(objectName)
	phaseKey := strings.ToLower(strings.TrimSpace(string(phase)))
	capKey := recordWriteValidatorCapabilityKey(capability)
	if objectKey == "" || phaseKey == "" || capKey == "" {
		return securityWriteValidationMetadataRequirement{}, false
	}
	return securityWriteValidationMetadataRequirement{
		objectName: objectKey,
		phase:      phaseKey,
		capability: capKey,
	}, true
}
