"""
Smooth Schedule Hijack Permissions
Implements the masquerading "Matrix" - strict rules for who can impersonate whom
"""
from django.core.exceptions import PermissionDenied


def can_hijack(hijacker, hijacked):
    """
    Determine if hijacker (the admin) can masquerade as hijacked (target user).
    
    The Matrix:
    ┌──────────────────────┬─────────────────────────────────────────────────┐
    │ Hijacker Role        │ Can Hijack                                      │
    ├──────────────────────┼─────────────────────────────────────────────────┤
    │ SUPERUSER            │ Anyone (full god mode)                          │
    │ PLATFORM_SUPPORT     │ TENANT_OWNER, TENANT_MANAGER, TENANT_STAFF      │
    │ PLATFORM_SALES       │ Only users with is_temporary=True               │
    │ TENANT_OWNER         │ TENANT_STAFF in same tenant only                │
    │ Others               │ Nobody                                          │
    └──────────────────────┴─────────────────────────────────────────────────┘
    
    Args:
        hijacker: User attempting to impersonate (the admin)
        hijacked: User being impersonated (the target)
    
    Returns:
        bool: True if hijack is allowed, False otherwise
    
    Security Notes:
        - Never allow self-hijacking
        - Never allow hijacking of superusers (except by other superusers)
        - Always validate tenant boundaries for tenant-scoped roles
        - Log all hijack attempts (success and failure) for audit
    """
    from users.models import User
    
    # Safety check: can't hijack yourself
    if hijacker.id == hijacked.id:
        return False
    
    # Safety check: only superusers can hijack other superusers
    if hijacked.role == User.Role.SUPERUSER and hijacker.role != User.Role.SUPERUSER:
        return False
    
    # Rule 1: SUPERUSER can hijack anyone
    if hijacker.role == User.Role.SUPERUSER:
        return True
    
    # Rule 2: PLATFORM_SUPPORT can hijack tenant users
    if hijacker.role == User.Role.PLATFORM_SUPPORT:
        return hijacked.role in [
            User.Role.TENANT_OWNER,
            User.Role.TENANT_MANAGER,
            User.Role.TENANT_STAFF,
            User.Role.CUSTOMER,
        ]
    
    # Rule 3: PLATFORM_SALES can only hijack temporary demo accounts
    if hijacker.role == User.Role.PLATFORM_SALES:
        return hijacked.is_temporary
    
    # Rule 4: TENANT_OWNER can hijack staff within their own tenant
    if hijacker.role == User.Role.TENANT_OWNER:
        # Must be in same tenant
        if not hijacker.tenant or not hijacked.tenant:
            return False
        if hijacker.tenant.id != hijacked.tenant.id:
            return False
        
        # Can only hijack staff and customers, not other owners/managers
        return hijacked.role in [
            User.Role.TENANT_STAFF,
            User.Role.CUSTOMER,
        ]
    
    # Default: deny
    return False


def can_hijack_or_403(hijacker, hijacked):
    """
    Same as can_hijack but raises PermissionDenied instead of returning False.
    Useful for views that want to use exception handling.
    
    Args:
        hijacker: User attempting to impersonate
        hijacked: User being impersonated
    
    Raises:
        PermissionDenied: If hijack is not allowed
    
    Returns:
        bool: True if allowed (never returns False, raises instead)
    """
    if not can_hijack(hijacker, hijacked):
        raise PermissionDenied(
            f"User {hijacker.email} ({hijacker.get_role_display()}) "
            f"is not authorized to masquerade as {hijacked.email} ({hijacked.get_role_display()})"
        )
    return True


def get_hijackable_users(hijacker):
    """
    Get queryset of all users that the hijacker can masquerade as.
    Useful for building "Masquerade as..." dropdowns in the UI.
    
    Args:
        hijacker: User who wants to hijack
    
    Returns:
        QuerySet: Users that can be hijacked by this user
    """
    from users.models import User
    
    # Start with all users except self
    qs = User.objects.exclude(id=hijacker.id)
    
    # Apply filters based on hijacker role
    if hijacker.role == User.Role.SUPERUSER:
        # Superuser can hijack anyone
        return qs
    
    elif hijacker.role == User.Role.PLATFORM_SUPPORT:
        # Can hijack all tenant-level users
        return qs.filter(role__in=[
            User.Role.TENANT_OWNER,
            User.Role.TENANT_MANAGER,
            User.Role.TENANT_STAFF,
            User.Role.CUSTOMER,
        ])
    
    elif hijacker.role == User.Role.PLATFORM_SALES:
        # Only temporary demo accounts
        return qs.filter(is_temporary=True)
    
    elif hijacker.role == User.Role.TENANT_OWNER:
        # Only staff in same tenant
        if not hijacker.tenant:
            return qs.none()
        
        return qs.filter(
            tenant=hijacker.tenant,
            role__in=[User.Role.TENANT_STAFF, User.Role.CUSTOMER]
        )
    
    else:
        # No one else can hijack
        return qs.none()


def validate_hijack_chain(request):
    """
    Validate that hijack chains are not too deep.
    Prevents: Admin1 -> Admin2 -> Admin3 -> User scenarios.
    
    Smooth Schedule Security Policy: Maximum hijack depth is 1.
    You cannot hijack while already hijacked.
    
    Args:
        request: Django request object
    
    Raises:
        PermissionDenied: If already in a hijack session
    
    Returns:
        bool: True if allowed to start new hijack
    """
    hijack_history = request.session.get('hijack_history', [])
    
    if len(hijack_history) > 0:
        raise PermissionDenied(
            "Cannot start a new masquerade session while already masquerading. "
            "Please exit your current session first."
        )
    
    return True


# ==============================================================================
# SaaS Quota Enforcement ("Hard Block" Logic)
# ==============================================================================

def HasQuota(feature_code):
    """
    Permission factory for SaaS tier limit enforcement.
    
    Returns a DRF permission class that blocks write operations when
    tenant has exceeded their quota for a specific feature.
    
    The "Hard Block": Prevents resource creation when tenant hits limit.
    
    Usage:
        class ResourceViewSet(ModelViewSet):
            permission_classes = [IsAuthenticated, HasQuota('MAX_RESOURCES')]
    
    Args:
        feature_code: TierLimit feature code (e.g., 'MAX_RESOURCES', 'MAX_USERS')
    
    Returns:
        QuotaPermission class configured for the feature
    
    How it Works:
        1. Read operations (GET/HEAD/OPTIONS) always allowed
        2. Write operations check current usage vs tier limit
        3. If usage >= limit, raises PermissionDenied (403)
    """
    from rest_framework.permissions import BasePermission
    from django.apps import apps
    
    class QuotaPermission(BasePermission):
        """
        Dynamically generated permission class for quota checking.
        """
        
        # Map feature codes to model paths for usage counting
        # CRITICAL: This map must be populated for the permission to work
        USAGE_MAP = {
            'MAX_RESOURCES': 'schedule.Resource',
            'MAX_USERS': 'users.User',
            'MAX_EVENTS_PER_MONTH': 'schedule.Event',
            # Add more mappings as needed
        }
        
        def has_permission(self, request, view):
            """
            Check if tenant has quota for this operation.
            
            Returns True for read operations, checks quota for writes.
            """
            # Allow all read-only operations (GET, HEAD, OPTIONS)
            if request.method in ['GET', 'HEAD', 'OPTIONS']:
                return True
            
            # Get tenant from request
            tenant = getattr(request, 'tenant', None)
            if not tenant:
                # No tenant in request - allow for public schema operations
                return True
            
            # Get the model to check usage against
            model_path = self.USAGE_MAP.get(feature_code)
            if not model_path:
                # Feature not mapped - fail safe by allowing
                # (Production: you'd want to log this as a configuration error)
                return True
            
            # Get the model class
            try:
                app_label, model_name = model_path.split('.')
                Model = apps.get_model(app_label, model_name)
            except (ValueError, LookupError):
                # Invalid model path - fail safe
                return True
            
            # Get the tier limit for this tenant
            try:
                from core.models import TierLimit
                limit = TierLimit.objects.get(
                    tier=tenant.subscription_tier,
                    feature_code=feature_code
                ).limit
            except TierLimit.DoesNotExist:
                # No limit defined - allow (unlimited)
                return True
            
            # Count current usage
            # NOTE: django-tenants automatically scopes this query to tenant schema
            current_count = Model.objects.count()
            
            # The "Hard Block": Enforce the limit
            if current_count >= limit:
                # Quota exceeded - deny the operation
                from rest_framework.exceptions import PermissionDenied
                raise PermissionDenied(
                    f"Quota exceeded: You have reached your plan limit of {limit} "
                    f"{feature_code.replace('MAX_', '').lower().replace('_', ' ')}. "
                    f"Please upgrade your subscription to add more."
                )
            
            # Quota available - allow the operation
            return True
    
    return QuotaPermission
