"""
Smooth Schedule Custom User Model
Implements strict role hierarchy and multi-tenant user management
"""
import secrets
from datetime import timedelta
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _


class User(AbstractUser):
    """
    Custom User model with strict role-based access control.
    Replaces Django's default User model.
    """
    
    # Role Choices - Strict Hierarchy
    class Role(models.TextChoices):
        # Platform-level roles (access across all tenants)
        SUPERUSER = 'SUPERUSER', _('Platform Superuser')
        PLATFORM_MANAGER = 'PLATFORM_MANAGER', _('Platform Manager')
        PLATFORM_SALES = 'PLATFORM_SALES', _('Platform Sales')
        PLATFORM_SUPPORT = 'PLATFORM_SUPPORT', _('Platform Support')
        
        # Tenant-level roles (access within single tenant)
        TENANT_OWNER = 'TENANT_OWNER', _('Tenant Owner')
        TENANT_MANAGER = 'TENANT_MANAGER', _('Tenant Manager')
        TENANT_STAFF = 'TENANT_STAFF', _('Tenant Staff')
        
        # Customer role (end users of the tenant)
        CUSTOMER = 'CUSTOMER', _('Customer')
    
    # Core fields
    role = models.CharField(
        max_length=20,
        choices=Role.choices,
        default=Role.CUSTOMER,
        help_text="User's role in the system hierarchy"
    )
    
    # For multi-tenancy: link users to their tenant
    # Note: This is only for tenant-level users. Platform users can access all tenants.
    tenant = models.ForeignKey(
        'core.Tenant',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='users',
        help_text="Tenant this user belongs to (null for platform-level users)"
    )
    
    # Special flags
    is_temporary = models.BooleanField(
        default=False,
        help_text="True for sales demo accounts - can be masqueraded by Platform Sales"
    )
    email_verified = models.BooleanField(
        default=False,
        help_text="Whether user has verified their email address"
    )
    
    # Sandbox/Test mode flag
    is_sandbox = models.BooleanField(
        default=False,
        help_text="True for sandbox/test mode users - isolated from live data"
    )

    # Quota overage archiving
    is_archived_by_quota = models.BooleanField(
        default=False,
        help_text="True if this user was archived due to quota overage. Cannot log in while archived."
    )
    archived_by_quota_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When this user was archived due to quota overage"
    )

    # Stripe customer fields (for customers to save payment methods)
    stripe_customer_id = models.CharField(
        max_length=255,
        blank=True,
        default='',
        help_text="Stripe Customer ID on the tenant's connected account"
    )
    default_payment_method_id = models.CharField(
        max_length=255,
        blank=True,
        default='',
        help_text="Default Stripe PaymentMethod ID for this customer"
    )

    # Additional profile fields
    phone = models.CharField(max_length=20, blank=True)
    phone_verified = models.BooleanField(
        default=False,
        help_text="Whether user has verified their phone number"
    )
    job_title = models.CharField(max_length=100, blank=True)

    # Two-Factor Authentication (2FA/MFA) fields
    mfa_enabled = models.BooleanField(
        default=False,
        help_text="Whether two-factor authentication is enabled for this user"
    )
    mfa_method = models.CharField(
        max_length=20,
        choices=[
            ('NONE', 'None'),
            ('SMS', 'SMS (Twilio)'),
            ('TOTP', 'Authenticator App'),
            ('BOTH', 'Both SMS and Authenticator'),
        ],
        default='NONE',
        help_text="Preferred 2FA method"
    )
    totp_secret = models.CharField(
        max_length=64,
        blank=True,
        help_text="Encrypted TOTP secret for authenticator apps"
    )
    totp_verified = models.BooleanField(
        default=False,
        help_text="Whether TOTP setup has been verified"
    )
    mfa_backup_codes = models.JSONField(
        default=list,
        blank=True,
        help_text="List of hashed backup codes for account recovery"
    )
    mfa_backup_codes_generated_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When backup codes were last generated"
    )

    # Role-specific permissions (stored as JSON for flexibility)
    permissions = models.JSONField(
        default=dict,
        blank=True,
        help_text="Role-specific permissions like can_invite_staff for managers"
    )

    # Staff role assignment (for TENANT_STAFF users)
    staff_role = models.ForeignKey(
        'StaffRole',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='staff_members',
        help_text="Assigned staff role (for TENANT_STAFF only)"
    )

    # Metadata
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    last_login_ip = models.GenericIPAddressField(null=True, blank=True)

    class Meta:
        app_label = 'users'
        ordering = ['email']
        indexes = [
            models.Index(fields=['role', 'tenant']),
            models.Index(fields=['email', 'is_active']),
        ]
    
    def __str__(self):
        return f"{self.email} ({self.get_role_display()})"

    @property
    def full_name(self):
        """Return the user's full name"""
        if self.first_name and self.last_name:
            return f"{self.first_name} {self.last_name}".strip()
        elif self.first_name:
            return self.first_name
        elif self.last_name:
            return self.last_name
        else:
            return self.username or self.email

    def is_platform_user(self):
        """Check if user has platform-level access"""
        return self.role in [
            self.Role.SUPERUSER,
            self.Role.PLATFORM_MANAGER,
            self.Role.PLATFORM_SALES,
            self.Role.PLATFORM_SUPPORT,
        ]
    
    def is_tenant_user(self):
        """Check if user is tenant-scoped"""
        return self.role in [
            self.Role.TENANT_OWNER,
            self.Role.TENANT_MANAGER,
            self.Role.TENANT_STAFF,
            self.Role.CUSTOMER,
        ]
    
    def can_manage_users(self):
        """Check if user can manage other users"""
        return self.role in [
            self.Role.SUPERUSER,
            self.Role.PLATFORM_MANAGER,
            self.Role.TENANT_OWNER,
            self.Role.TENANT_MANAGER,
        ]
    
    def can_access_billing(self):
        """Check if user can access billing information"""
        return self.role in [
            self.Role.SUPERUSER,
            self.Role.PLATFORM_MANAGER,
            self.Role.TENANT_OWNER,
        ]

    def can_invite_staff(self):
        """Check if user can invite new staff members"""
        # Owners can always invite
        if self.role == self.Role.TENANT_OWNER:
            return True
        # Managers can invite if they have the permission
        if self.role == self.Role.TENANT_MANAGER:
            return self.permissions.get('can_invite_staff', False)
        return False

    def can_access_tickets(self):
        """Check if user can access the ticket system"""
        # Platform users can always access
        if self.is_platform_user():
            return True
        # Owners and managers can always access
        if self.role in [self.Role.TENANT_OWNER, self.Role.TENANT_MANAGER]:
            return True
        # Staff can access if granted permission (default: False)
        if self.role == self.Role.TENANT_STAFF:
            return self.permissions.get('can_access_tickets', False)
        # Customers can create tickets
        if self.role == self.Role.CUSTOMER:
            return True
        return False

    def can_approve_plugins(self):
        """
        Check if user can approve/publish plugins to marketplace.
        Only platform users with explicit permission (granted by superuser).
        """
        # Superusers can always approve
        if self.role == self.Role.SUPERUSER:
            return True
        # Platform managers/support can approve if granted permission
        if self.role in [self.Role.PLATFORM_MANAGER, self.Role.PLATFORM_SUPPORT]:
            return self.permissions.get('can_approve_plugins', False)
        # All others cannot approve
        return False

    def can_whitelist_urls(self):
        """
        Check if user can whitelist URLs for plugin API calls.
        Only platform users with explicit permission (granted by superuser).
        Can whitelist both per-user and platform-wide URLs.
        """
        # Superusers can always whitelist
        if self.role == self.Role.SUPERUSER:
            return True
        # Platform managers/support can whitelist if granted permission
        if self.role in [self.Role.PLATFORM_MANAGER, self.Role.PLATFORM_SUPPORT]:
            return self.permissions.get('can_whitelist_urls', False)
        # All others cannot whitelist
        return False

    def can_self_approve_time_off(self):
        """
        Check if user can self-approve time off requests.
        Owners can always self-approve.
        Managers can self-approve by default but can be denied.
        Staff need explicit permission.
        """
        # Owners can always self-approve
        if self.role == self.Role.TENANT_OWNER:
            return True
        # Managers can self-approve by default, but can be denied
        if self.role == self.Role.TENANT_MANAGER:
            return self.permissions.get('can_self_approve_time_off', True)
        # Staff can self-approve if granted permission (default: False)
        if self.role == self.Role.TENANT_STAFF:
            return self.permissions.get('can_self_approve_time_off', False)
        return False

    def can_review_time_off_requests(self):
        """
        Check if user can review (approve/deny) time off requests from others.
        Only owners and managers can review.
        """
        return self.role in [self.Role.TENANT_OWNER, self.Role.TENANT_MANAGER]

    def can_send_messages(self):
        """
        Check if user can send broadcast messages to staff/customers.
        Owners can always send messages.
        Managers can by default but can be revoked.
        Staff cannot send messages.
        """
        # Owners can always send messages
        if self.role == self.Role.TENANT_OWNER:
            return True
        # Managers can send by default, but can be revoked
        if self.role == self.Role.TENANT_MANAGER:
            return self.permissions.get('can_send_messages', True)
        # Staff and others cannot send messages
        return False

    def has_staff_permission(self, permission_key):
        """
        Check if staff member has a specific permission.

        Permission Resolution Order:
        1. Owners and Managers always have all permissions (return True)
        2. For staff: User-level override takes priority
        3. Then check staff role permissions
        4. Default: False

        Args:
            permission_key: The permission key to check (e.g., 'can_access_scheduler')

        Returns:
            bool: Whether the user has the permission
        """
        # Owners and managers have all permissions
        if self.role in [self.Role.TENANT_OWNER, self.Role.TENANT_MANAGER]:
            return True

        # For staff, check permissions
        if self.role == self.Role.TENANT_STAFF:
            # User-level override takes priority
            if self.permissions and permission_key in self.permissions:
                return self.permissions[permission_key]

            # Then check staff role permissions
            if self.staff_role and self.staff_role.permissions:
                return self.staff_role.permissions.get(permission_key, False)

        return False

    def get_effective_permissions(self):
        """
        Get the merged permissions for this user (role + user overrides).

        Returns:
            dict: All effective permissions for this user
        """
        if self.role in [self.Role.TENANT_OWNER, self.Role.TENANT_MANAGER]:
            # Return all permissions as True for owner/manager
            from smoothschedule.identity.users.staff_permissions import ALL_PERMISSIONS
            return {k: True for k in ALL_PERMISSIONS.keys()}

        if self.role == self.Role.TENANT_STAFF:
            # Start with role permissions
            perms = {}
            if self.staff_role and self.staff_role.permissions:
                perms = self.staff_role.permissions.copy()

            # Override with user-level permissions
            if self.permissions:
                perms.update(self.permissions)

            return perms

        return {}

    def get_accessible_tenants(self):
        """
        Get list of tenants this user can access.
        Platform users can access all tenants.
        Tenant users can only access their own tenant.
        """
        from smoothschedule.identity.core.models import Tenant
        
        if self.is_platform_user():
            return Tenant.objects.all()
        elif self.tenant:
            return Tenant.objects.filter(pk=self.tenant.pk)
        else:
            return Tenant.objects.none()
    
    def save(self, *args, **kwargs):
        """
        Override save to enforce business rules
        """
        # If is_superuser flag is set, ensure role is SUPERUSER
        # This handles Django's create_superuser command
        if self.is_superuser:
            self.role = self.Role.SUPERUSER
            self.is_staff = True
        
        # Superusers must be staff and have is_superuser flag
        if self.role == self.Role.SUPERUSER:
            self.is_staff = True
            self.is_superuser = True
        
        # Platform users should not be tied to a tenant
        if self.is_platform_user():
            self.tenant = None
        
        # Tenant users must have a tenant
        if self.is_tenant_user() and not self.tenant:
            raise ValueError(f"Users with role {self.role} must be assigned to a tenant")

        super().save(*args, **kwargs)


class EmailVerificationToken(models.Model):
    """Token for email verification"""
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='email_tokens')
    token = models.CharField(max_length=64, unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField()
    used = models.BooleanField(default=False)

    class Meta:
        app_label = 'users'
        ordering = ['-created_at']

    def save(self, *args, **kwargs):
        if not self.token:
            self.token = secrets.token_urlsafe(32)
        if not self.expires_at:
            self.expires_at = timezone.now() + timedelta(hours=24)
        super().save(*args, **kwargs)

    def is_valid(self):
        return not self.used and timezone.now() < self.expires_at

    @classmethod
    def create_for_user(cls, user):
        # Invalidate old tokens
        cls.objects.filter(user=user, used=False).update(used=True)
        # Create new token
        return cls.objects.create(user=user)


class MFAVerificationCode(models.Model):
    """
    Temporary verification codes for MFA challenges.
    Used for SMS codes and login verification.
    """
    class Purpose(models.TextChoices):
        LOGIN = 'LOGIN', _('Login Verification')
        SETUP = 'SETUP', _('MFA Setup')
        PHONE_VERIFY = 'PHONE_VERIFY', _('Phone Verification')
        DISABLE = 'DISABLE', _('Disable MFA')

    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='mfa_codes')
    code = models.CharField(max_length=6, help_text="6-digit verification code")
    purpose = models.CharField(max_length=20, choices=Purpose.choices, default=Purpose.LOGIN)
    method = models.CharField(
        max_length=10,
        choices=[('SMS', 'SMS'), ('TOTP', 'TOTP')],
        default='SMS'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField()
    used = models.BooleanField(default=False)
    attempts = models.IntegerField(default=0, help_text="Number of failed verification attempts")

    class Meta:
        app_label = 'users'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['user', 'purpose', 'used']),
            models.Index(fields=['expires_at']),
        ]

    def save(self, *args, **kwargs):
        if not self.code:
            # Generate 6-digit code
            import random
            self.code = ''.join([str(random.randint(0, 9)) for _ in range(6)])
        if not self.expires_at:
            # Default expiration: 10 minutes
            self.expires_at = timezone.now() + timedelta(minutes=10)
        super().save(*args, **kwargs)

    def is_valid(self):
        """Check if code is still valid"""
        if self.used:
            return False
        if timezone.now() > self.expires_at:
            return False
        if self.attempts >= 5:  # Max 5 attempts
            return False
        return True

    def verify(self, code):
        """Verify the code and mark as used if correct"""
        if not self.is_valid():
            return False
        if self.code != code:
            self.attempts += 1
            self.save(update_fields=['attempts'])
            return False
        self.used = True
        self.save(update_fields=['used'])
        return True

    @classmethod
    def create_for_user(cls, user, purpose=Purpose.LOGIN, method='SMS'):
        """Create a new verification code, invalidating old ones"""
        # Invalidate old codes for same purpose
        cls.objects.filter(user=user, purpose=purpose, used=False).update(used=True)
        return cls.objects.create(user=user, purpose=purpose, method=method)


class TrustedDevice(models.Model):
    """
    Tracks trusted devices to skip MFA on subsequent logins.
    """
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='trusted_devices')
    device_hash = models.CharField(
        max_length=64,
        help_text="Hash of device fingerprint (IP + User-Agent + other factors)"
    )
    name = models.CharField(
        max_length=100,
        blank=True,
        help_text="User-friendly device name (e.g., 'Chrome on MacBook')"
    )
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    user_agent = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    last_used_at = models.DateTimeField(auto_now=True)
    expires_at = models.DateTimeField()

    class Meta:
        app_label = 'users'
        ordering = ['-last_used_at']
        unique_together = ['user', 'device_hash']
        indexes = [
            models.Index(fields=['user', 'device_hash']),
            models.Index(fields=['expires_at']),
        ]

    def save(self, *args, **kwargs):
        if not self.expires_at:
            # Default trust period: 30 days
            self.expires_at = timezone.now() + timedelta(days=30)
        super().save(*args, **kwargs)

    def is_valid(self):
        """Check if device trust is still valid"""
        return timezone.now() < self.expires_at

    @classmethod
    def create_or_update(cls, user, device_hash, name='', ip_address=None, user_agent='', trust_days=30):
        """Create or update a trusted device"""
        expires_at = timezone.now() + timedelta(days=trust_days)
        device, created = cls.objects.update_or_create(
            user=user,
            device_hash=device_hash,
            defaults={
                'name': name,
                'ip_address': ip_address,
                'user_agent': user_agent,
                'expires_at': expires_at,
            }
        )
        return device


class StaffRole(models.Model):
    """
    Tenant-scoped role definitions for staff members.

    This model is in the users app (SHARED_APP) with a tenant FK for scoping.
    It uses explicit tenant filtering instead of schema isolation because
    the User model (which references it) is also in a shared app.

    Each role contains a set of permissions that control access to
    menu items and dangerous operations at the API level.

    Permission Resolution Order:
    1. User-level override (user.permissions) - highest priority
    2. Staff role permissions (staff_role.permissions)
    3. Default: False
    """

    tenant = models.ForeignKey(
        'core.Tenant',
        on_delete=models.CASCADE,
        related_name='staff_roles',
        help_text="Tenant this role belongs to"
    )

    name = models.CharField(
        max_length=100,
        help_text="Display name of the role"
    )

    description = models.TextField(
        blank=True,
        default='',
        help_text="Description of what this role can do"
    )

    # Permissions stored as JSON for flexibility
    permissions = models.JSONField(
        default=dict,
        blank=True,
        help_text="Permission keys and their boolean values"
    )

    # System roles cannot be deleted
    is_default = models.BooleanField(
        default=False,
        help_text="True for system-created default roles"
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        app_label = 'users'
        ordering = ['-is_default', 'name']
        unique_together = [['tenant', 'name']]
        indexes = [
            models.Index(fields=['tenant', 'is_default']),
        ]

    def __str__(self):
        return f"{self.name} ({self.tenant.name})"

    def get_staff_count(self):
        """Returns the number of staff assigned to this role.

        Note: In list views, prefer using the annotated `staff_count` field
        from the queryset for better performance (single query vs N+1).
        """
        return self.staff_members.count()

    def can_delete(self):
        """Check if this role can be deleted"""
        if self.is_default:
            return False
        # Use annotated staff_count if available, otherwise query
        staff_count = getattr(self, 'staff_count', None)
        if staff_count is None:
            staff_count = self.get_staff_count()
        return staff_count == 0

    @classmethod
    def create_default_roles_for_tenant(cls, tenant):
        """
        Create default staff roles for a tenant.
        Called during tenant creation and in data migrations.
        """
        from smoothschedule.identity.users.staff_permissions import DEFAULT_ROLES

        created_roles = []
        for role_name, role_config in DEFAULT_ROLES.items():
            role, created = cls.objects.get_or_create(
                tenant=tenant,
                name=role_name,
                defaults={
                    'description': role_config['description'],
                    'permissions': role_config['permissions'],
                    'is_default': True,
                }
            )
            if created:
                created_roles.append(role)
        return created_roles


class StaffInvitation(models.Model):
    """
    Invitation for new staff members to join a business.

    Flow:
    1. Owner/Manager creates invitation with email and role
    2. System sends email with unique token link
    3. Invitee clicks link, creates account, and is added to tenant
    """

    class Status(models.TextChoices):
        PENDING = 'PENDING', _('Pending')
        ACCEPTED = 'ACCEPTED', _('Accepted')
        DECLINED = 'DECLINED', _('Declined')
        EXPIRED = 'EXPIRED', _('Expired')
        CANCELLED = 'CANCELLED', _('Cancelled')

    # Invitation target
    email = models.EmailField(help_text="Email address to send invitation to")
    role = models.CharField(
        max_length=20,
        choices=[
            (User.Role.TENANT_MANAGER, _('Manager')),
            (User.Role.TENANT_STAFF, _('Staff')),
        ],
        default=User.Role.TENANT_STAFF,
        help_text="Role the invited user will have"
    )

    # Tenant association
    tenant = models.ForeignKey(
        'core.Tenant',
        on_delete=models.CASCADE,
        related_name='staff_invitations',
        help_text="Business the user is being invited to"
    )

    # Invitation metadata
    invited_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        related_name='sent_invitations',
        help_text="User who sent the invitation"
    )

    # Token for secure acceptance
    token = models.CharField(max_length=64, unique=True)

    # Status tracking
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.PENDING
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    expires_at = models.DateTimeField()
    accepted_at = models.DateTimeField(null=True, blank=True)

    # Link to created user (after acceptance)
    accepted_user = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='accepted_invitation',
        help_text="User account created when invitation was accepted"
    )

    # Bookable resource configuration
    create_bookable_resource = models.BooleanField(
        default=False,
        help_text="Whether to create a bookable resource for this staff member"
    )
    resource_name = models.CharField(
        max_length=200,
        blank=True,
        help_text="Name for the bookable resource (defaults to user's name if empty)"
    )

    # Permissions configuration (stored as JSON for flexibility)
    permissions = models.JSONField(
        default=dict,
        blank=True,
        help_text="Permission settings for the invited user"
    )

    # Staff role to assign when invitation is accepted
    staff_role = models.ForeignKey(
        'StaffRole',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='invitations',
        help_text="Staff role to assign when invitation is accepted (for TENANT_STAFF only)"
    )

    class Meta:
        app_label = 'users'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['token']),
            models.Index(fields=['email', 'tenant', 'status']),
            models.Index(fields=['status', 'expires_at']),
        ]

    def __str__(self):
        return f"Invitation for {self.email} to {self.tenant.name} ({self.get_status_display()})"

    def save(self, *args, **kwargs):
        if not self.token:
            self.token = secrets.token_urlsafe(32)
        if not self.expires_at:
            # Default expiration: 7 days
            self.expires_at = timezone.now() + timedelta(days=7)
        super().save(*args, **kwargs)

    def is_valid(self):
        """Check if invitation can still be accepted"""
        if self.status != self.Status.PENDING:
            return False
        if timezone.now() > self.expires_at:
            return False
        return True

    def accept(self, user):
        """Mark invitation as accepted and link to user"""
        self.status = self.Status.ACCEPTED
        self.accepted_at = timezone.now()
        self.accepted_user = user
        self.save()

    def decline(self):
        """Mark invitation as declined"""
        self.status = self.Status.DECLINED
        self.save()

    def cancel(self):
        """Cancel a pending invitation"""
        if self.status == self.Status.PENDING:
            self.status = self.Status.CANCELLED
            self.save()

    @classmethod
    def create_invitation(cls, email, role, tenant, invited_by,
                          create_bookable_resource=False, resource_name='', permissions=None):
        """
        Create a new invitation, cancelling any existing pending invitations
        for the same email/tenant combination.

        Args:
            email: Email address to invite
            role: Role for the invited user (TENANT_MANAGER or TENANT_STAFF)
            tenant: Tenant/business the user is being invited to
            invited_by: User sending the invitation
            create_bookable_resource: Whether to create a bookable resource when accepted
            resource_name: Name for the bookable resource (optional)
            permissions: Dict of permission settings (optional)
        """
        # Cancel existing pending invitations for this email/tenant
        cls.objects.filter(
            email=email,
            tenant=tenant,
            status=cls.Status.PENDING
        ).update(status=cls.Status.CANCELLED)

        # Create new invitation
        return cls.objects.create(
            email=email,
            role=role,
            tenant=tenant,
            invited_by=invited_by,
            create_bookable_resource=create_bookable_resource,
            resource_name=resource_name,
            permissions=permissions or {}
        )
