# Generated by Claude Sonnet 4 (claude-sonnet-4@20250514)
import logging

from django.db import models

logger = logging.getLogger(__name__)

# This method has moved, and this is put here temporarily to make branch management easier
from ansible_base.rbac.management import create_dab_permissions as create_custom_permissions  # noqa


def give_permissions(apps, rd, users=(), teams=(), object_id=None, content_type_id=None):
    """
    Give user permission to an object, but for use in migrations
    rd - role definition to grant the user
    users - list of users to give this permission to
    teams - list of teams to give this permission to, can be objects or id list

    target object is implicitly specified by
    object_id - primary key of the object permission will apply to
    content_type_id - primary key of the content type for the object
    """
    ObjectRole = apps.get_model('dab_rbac', 'ObjectRole')

    # Create the object role and add users to it
    object_role_fields = dict(role_definition=rd, object_id=object_id, content_type_id=content_type_id)
    object_role, _ = ObjectRole.objects.get_or_create(**object_role_fields)

    if users:
        # Django seems to not process through_fields correctly in migrations
        # so it will use created_by as the target field name, which is incorrect, should be user
        # basically can not use object_role.users.add(actor)
        RoleUserAssignment = apps.get_model('dab_rbac', 'RoleUserAssignment')
        user_assignments = [
            RoleUserAssignment(object_role=object_role, user=user, **object_role_fields)
            for user in users
        ]
        RoleUserAssignment.objects.bulk_create(user_assignments, ignore_conflicts=True)
    if teams:
        RoleTeamAssignment = apps.get_model('dab_rbac', 'RoleTeamAssignment')
        # AWX has trouble getting the team object, conditionally accept team id list
        if isinstance(teams[0], models.Model):
            team_assignments = [
                RoleTeamAssignment(object_role=object_role, team=team, **object_role_fields)
                for team in teams
            ]
        else:
            team_assignments = [
                RoleTeamAssignment(object_role=object_role, team_id=team_id, **object_role_fields)
                for team_id in teams
            ]
        RoleTeamAssignment.objects.bulk_create(team_assignments, ignore_conflicts=True)


def cleanup_orphaned_permissions(apps):
    """
    Delete orphaned DABPermission objects for models no longer in the permission registry.

    This is used during migrations to clean up permissions for any previously-registered model
    that are no longer tracked by RBAC, but only if they are not referenced by any RoleDefinition.

    Args:
        apps: Django apps registry (from migration context)

    Returns:
        int: Number of permissions deleted
    """
    # Get model classes from apps registry
    permission_cls = apps.get_model('dab_rbac', 'DABPermission')
    role_definition_cls = apps.get_model('dab_rbac', 'RoleDefinition')

    # Get permission registry to check which models are registered
    from ansible_base.rbac import permission_registry
    registered_model_keys = set()
    for model in permission_registry._registry:
        registered_model_keys.add((model._meta.app_label, model._meta.model_name))

    # Find orphaned permissions
    orphaned_permissions = []
    for permission in permission_cls.objects.all():
        if permission.content_type:
            model_key = (permission.content_type.app_label, permission.content_type.model)
            if model_key not in registered_model_keys:
                # Check if this permission is referenced by any RoleDefinition
                referencing_roles = role_definition_cls.objects.filter(permissions=permission)
                if referencing_roles.exists():
                    # Log warning for unregistered model still referenced by role definitions
                    role_names = list(referencing_roles.values_list('name', flat=True))
                    logger.warning(
                        f'Permission {permission.codename} for unregistered model '
                        f'{permission.content_type.app_label}.{permission.content_type.model} '
                        f'is still referenced by role definitions: {role_names}'
                    )
                else:
                    logger.info(f'Deleting orphaned permission {permission.codename} for unregistered model {model_key}')
                    orphaned_permissions.append(permission)

    # Delete orphaned permissions
    deleted_count = 0
    if orphaned_permissions:
        deleted_count = len(orphaned_permissions)
        permission_cls.objects.filter(id__in=[p.id for p in orphaned_permissions]).delete()
        logger.info(f'Deleted {deleted_count} orphaned DABPermission objects for unregistered models')

    return deleted_count


def give_global_permission_to_actor(role_definition, actor, apps):
    """
    Migration-safe utility function to give global permission to an actor (user or team).

    This function replicates the logic from RoleDefinition.give_global_permission()
    but works with migration fake models that don't have custom methods.

    Args:
        role_definition: RoleDefinition instance (can be fake model from migration)
        actor: User or Team instance to grant permission to
        apps: Django apps registry from migration context
    """
    from django.conf import settings
    from rest_framework.exceptions import ValidationError

    if role_definition.content_type is not None:
        raise ValidationError('Role definition content type must be null to assign globally')

    # Get the assignment classes through apps registry (migration-safe)
    RoleUserAssignment = apps.get_model('dab_rbac', 'RoleUserAssignment')
    RoleTeamAssignment = apps.get_model('dab_rbac', 'RoleTeamAssignment')

    if actor._meta.model_name == 'user':
        if not settings.ANSIBLE_BASE_ALLOW_SINGLETON_USER_ROLES:
            raise ValidationError('Global roles are not enabled for users')
        kwargs = {'object_role': None, 'user': actor, 'role_definition': role_definition}
        cls = RoleUserAssignment
    elif hasattr(actor, '_meta') and actor._meta.model_name == 'team':
        # In migration context, check by model name since isinstance checks might fail
        if not settings.ANSIBLE_BASE_ALLOW_SINGLETON_TEAM_ROLES:
            raise ValidationError('Global roles are not enabled for teams')
        kwargs = {'object_role': None, 'team': actor, 'role_definition': role_definition}
        cls = RoleTeamAssignment
    else:
        raise RuntimeError(f'Cannot give permission for {actor} (type: {type(actor)}, model_name: {getattr(actor._meta, "model_name", "unknown")}), must be a user or team')

    assignment, _ = cls.objects.get_or_create(**kwargs)

    # Clear any cached permissions
    if actor._meta.model_name == 'user':
        if hasattr(actor, '_singleton_permissions'):
            delattr(actor, '_singleton_permissions')
    else:
        # when team permissions change, users in memory may be affected by this
        # but there is no way to know what users, so we use a global flag
        from ansible_base.rbac.evaluations import bound_singleton_permissions
        bound_singleton_permissions._team_clear_signal = True

    return assignment


def migrate_content_type(apps, schema_editor):
    """
    Migrate content type references from Django ContentType to DABContentType.

    This function handles the migration of content type references across all RBAC models
    from the old Django ContentType to the new DABContentType system.

    Args:
        apps: Django apps registry (from migration context)
        schema_editor: Django schema editor (unused but required for migration signature)
    """
    # Pre-check: Delete orphaned DABPermission objects before migration
    cleanup_orphaned_permissions(apps)

    ct_cls = apps.get_model('dab_rbac', 'DABContentType')
    ct_cls.objects.clear_cache()

    for model_name in ('dabpermission', 'objectrole', 'roledefinition', 'roleuserassignment', 'roleteamassignment'):
        cls = apps.get_model('dab_rbac', model_name)
        update_ct = 0
        for obj in cls.objects.all():
            old_ct = obj.content_type
            if old_ct:
                try:
                    # NOTE: could give duplicate normally, but that is impossible in migration path
                    obj.new_content_type = ct_cls.objects.get_by_natural_key(old_ct.app_label, old_ct.model)
                except Exception as e:
                    raise RuntimeError(
                        f"Failed to get new content type for a {model_name} pk={obj.pk}, obj={obj.__dict__}"
                    ) from e
                obj.save()
                update_ct += 1
        if update_ct:
            logger.info(f'Updated content_type reference to new model for {model_name} for {update_ct} entries')
    for model_name in ('roleevaluation', 'roleevaluationuuid'):
        cls = apps.get_model('dab_rbac', model_name)
        cls.objects.all().delete()

    # DABPermission model had api_slug added in last migration
    # if records existed before this point, it needs to be filled in
    mod_ct = 0
    permission_cls = apps.get_model('dab_rbac', 'DABPermission')
    for permission in permission_cls.objects.all():
        permission.api_slug = f'{permission.new_content_type.service}.{permission.codename}'
        permission.save()
        mod_ct += 1
    if mod_ct:
        logger.info(f'Set new field DABPermission.api_slug for {mod_ct} existing permissions')


def create_types_if_needed(apps, schema_editor):
    """
    Create DABContentType entries if needed before migration.

    Before we can migrate to the new DABContentType, entries in that table must be created.
    This method runs what is ordinarily the post_migrate logic, but in the migration case here.
    Only needed in the upgrade case, otherwise better to run at true post-migrate.

    Args:
        apps: Django apps registry (from migration context)
        schema_editor: Django schema editor (unused but required for migration signature)
    """
    permission_cls = apps.get_model('dab_rbac', 'DABPermission')
    rd_cls = apps.get_model('dab_rbac', 'RoleDefinition')
    if permission_cls.objects.exists() or rd_cls.objects.exists():
        logger.info('Running DABContentType creation script as part of 0005 migration')
        from ansible_base.rbac.management.create_types import create_DAB_contenttypes

        create_DAB_contenttypes(apps=apps)
