package records

import (
	"context"
	"strings"
	"sync"

	"github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/shared/pkg/constants"
)

const (
	securityCacheInvalidatePermissionsCapability              = "platform.security.cache.permissions"
	securityCacheInvalidateSharingRulesCapability             = "platform.security.cache.sharing-rules"
	securityCacheRefreshRoleHierarchyCapability               = "platform.security.cache.role-hierarchy"
	securityCacheUserProfileRoleCapability                    = "platform.security.cache.user-profile-role"
	securityCachePermissionSetAssignmentUsersCapability       = "platform.security.cache.permission-set-assignment-users"
	securityCachePermissionSetGroupAssignmentUsersCapability  = "platform.security.cache.permission-set-group-assignment-users"
	securityCachePermissionSetGroupComponentUsersCapability   = "platform.security.cache.permission-set-group-component-users"
	securityCacheMutingPermissionSetUsersCapability           = "platform.security.cache.muting-permission-set-users"
	securityCacheMutingPermissionSetPermissionUsersCapability = "platform.security.cache.muting-permission-set-permission-users"
)

var (
	securityCacheInvalidationMetadataRequiredOnce    sync.Once
	securityCacheInvalidationMetadataRequiredObjects map[string]struct{}
)

func (ps *PersistenceService) registerBuiltInSecurityCacheInvalidations() {
	mustRegisterSecurityCacheInvalidation(ps, securityCacheInvalidatePermissionsCapability, func(_ context.Context, _ recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		plan.invalidatePermissions = true
		return nil
	})
	mustRegisterSecurityCacheInvalidation(ps, securityCacheInvalidateSharingRulesCapability, func(_ context.Context, _ recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		plan.invalidateSharing = true
		return nil
	})
	mustRegisterSecurityCacheInvalidation(ps, securityCacheRefreshRoleHierarchyCapability, func(_ context.Context, _ recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		plan.refreshRoleHierarchy = true
		return nil
	})
	mustRegisterSecurityCacheInvalidation(ps, securityCacheUserProfileRoleCapability, func(_ context.Context, event recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		for _, change := range event.Changes {
			recordID := securityRecordID(change.before, change.after)
			if recordID != "" && (fieldChanged(change.before, change.after, constants.FieldProfileID) || fieldChanged(change.before, change.after, constants.FieldUserRoleID)) {
				addUserID(plan.userIDs, recordID)
			}
		}
		return nil
	})
	mustRegisterSecurityCacheInvalidation(ps, securityCachePermissionSetAssignmentUsersCapability, func(_ context.Context, event recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		for _, change := range event.Changes {
			addUserID(plan.userIDs, stringField(change.before, constants.FieldPermissionSetAssignment_AssigneeID))
			addUserID(plan.userIDs, stringField(change.after, constants.FieldPermissionSetAssignment_AssigneeID))
		}
		return nil
	})
	mustRegisterSecurityCacheInvalidation(ps, securityCachePermissionSetGroupAssignmentUsersCapability, func(_ context.Context, event recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		for _, change := range event.Changes {
			addUserID(plan.userIDs, stringField(change.before, constants.FieldPermissionSetGroupAssignment_AssigneeID))
			addUserID(plan.userIDs, stringField(change.after, constants.FieldPermissionSetGroupAssignment_AssigneeID))
		}
		return nil
	})
	mustRegisterSecurityCacheInvalidation(ps, securityCachePermissionSetGroupComponentUsersCapability, func(ctx context.Context, event recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		groupIDs := collectChangedStringFields(event.Changes, constants.FieldPermissionSetGroupComponent_GroupID)
		return ps.addAssignedUsersForGroups(ctx, plan.userIDs, groupIDs...)
	})
	mustRegisterSecurityCacheInvalidation(ps, securityCacheMutingPermissionSetUsersCapability, func(ctx context.Context, event recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		groupIDs := collectChangedStringFields(event.Changes, constants.FieldMutingPermissionSet_GroupID)
		return ps.addAssignedUsersForGroups(ctx, plan.userIDs, groupIDs...)
	})
	mustRegisterSecurityCacheInvalidation(ps, securityCacheMutingPermissionSetPermissionUsersCapability, func(ctx context.Context, event recordCacheInvalidationEvent, plan *securityInvalidationPlan) error {
		mutingPermissionSetIDs := collectChangedStringFields(event.Changes, constants.FieldSysMutingPermissionSetPermission_MutingPermissionSetID)
		groupIDs, err := ps.groupIDsForMutingPermissionSetIDs(ctx, mutingPermissionSetIDs)
		if err != nil {
			return err
		}
		return ps.addAssignedUsersForGroups(ctx, plan.userIDs, groupIDs...)
	})
}

func mustRegisterSecurityCacheInvalidation(ps *PersistenceService, capability string, handler recordCacheInvalidationHandler) {
	if ps == nil {
		return
	}
	if err := ps.registerRecordCacheInvalidation(capability, handler); err != nil {
		panic(err)
	}
}

// Metadata owns the normal invalidation plan; this guard only fails closed when
// core security-object metadata has not been reconciled with cacheInvalidation.
func securityCacheInvalidationMetadataRequired(objectName string) bool {
	key := securityCacheInvalidationObjectKey(objectName)
	if key == "" {
		return false
	}
	securityCacheInvalidationMetadataRequiredOnce.Do(func() {
		securityCacheInvalidationMetadataRequiredObjects = securityCacheInvalidationMetadataRequiredObjectsFromDefinitions(managedTableDefinitions())
	})
	_, ok := securityCacheInvalidationMetadataRequiredObjects[key]
	return ok
}

func securityCacheInvalidationMetadataRequiredObjectsFromDefinitions(definitions []schema.TableDefinition) map[string]struct{} {
	required := make(map[string]struct{})
	for _, definition := range definitions {
		if !tableDefinitionDeclaresCacheInvalidation(definition) {
			continue
		}
		key := securityCacheInvalidationObjectKey(definition.TableName)
		if key != "" {
			required[key] = struct{}{}
		}
	}
	return required
}

func tableDefinitionDeclaresCacheInvalidation(definition schema.TableDefinition) bool {
	if definition.AccessPolicy == nil || definition.AccessPolicy.CacheInvalidation == nil {
		return false
	}
	for _, handler := range definition.AccessPolicy.CacheInvalidation.Handlers {
		if strings.TrimSpace(handler.Capability) != "" {
			return true
		}
	}
	return false
}

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

func securityCacheInvalidationCapabilitiesFallbackEligible(capabilities []string) bool {
	if len(capabilities) == 0 {
		return false
	}
	for _, capability := range capabilities {
		if !securityCacheInvalidationCapabilityFallbackEligible(capability) {
			return false
		}
	}
	return true
}

func securityCacheInvalidationCapabilityFallbackEligible(capability string) bool {
	switch recordCacheInvalidationCapabilityKey(capability) {
	case recordCacheInvalidationCapabilityKey(securityCacheInvalidatePermissionsCapability),
		recordCacheInvalidationCapabilityKey(securityCacheInvalidateSharingRulesCapability),
		recordCacheInvalidationCapabilityKey(securityCacheRefreshRoleHierarchyCapability),
		recordCacheInvalidationCapabilityKey(securityCacheUserProfileRoleCapability),
		recordCacheInvalidationCapabilityKey(securityCachePermissionSetAssignmentUsersCapability),
		recordCacheInvalidationCapabilityKey(securityCachePermissionSetGroupAssignmentUsersCapability),
		recordCacheInvalidationCapabilityKey(securityCachePermissionSetGroupComponentUsersCapability),
		recordCacheInvalidationCapabilityKey(securityCacheMutingPermissionSetUsersCapability),
		recordCacheInvalidationCapabilityKey(securityCacheMutingPermissionSetPermissionUsersCapability):
		return true
	default:
		return false
	}
}
