"""
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 smoothschedule.identity.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 managers, staff, and customers 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 hijack managers, staff, and customers (not other owners)
        return hijacked.role in [
            User.Role.TENANT_MANAGER,
            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 smoothschedule.identity.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:
        # Managers, staff, and customers in same tenant
        if not hijacker.tenant:
            return qs.none()

        return qs.filter(
            tenant=hijacker.tenant,
            role__in=[User.Role.TENANT_MANAGER, User.Role.TENANT_STAFF, User.Role.CUSTOMER]
        )
    
    else:
        # No one else can hijack
        return qs.none()


def validate_hijack_chain(request, max_depth=5):
    """
    Validate that hijack chains are not too deep.
    Prevents unlimited masquerade chains for security.

    Smooth Schedule Security Policy: Maximum hijack depth is configurable (default 5).
    Multi-level masquerading is allowed up to the max depth.

    Args:
        request: Django request object
        max_depth: Maximum allowed masquerade depth (default 5)

    Raises:
        PermissionDenied: If max depth would be exceeded

    Returns:
        bool: True if allowed to start new hijack
    """
    hijack_history = request.session.get('hijack_history', [])

    if len(hijack_history) >= max_depth:
        raise PermissionDenied(
            f"Maximum masquerade depth ({max_depth}) reached. "
            "Please exit some sessions 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_ADDITIONAL_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
        # Note: MAX_ADDITIONAL_USERS requires special handling (shared schema + exclude owner)
        USAGE_MAP = {
            'MAX_RESOURCES': 'schedule.Resource',
            'MAX_ADDITIONAL_USERS': 'users.User',  # Renamed from MAX_USERS - excludes owner
            'MAX_EVENTS_PER_MONTH': 'schedule.Event',
            'MAX_SERVICES': 'schedule.Service',
            'MAX_APPOINTMENTS': 'schedule.Event',
            'MAX_EMAIL_TEMPLATES': 'schedule.EmailTemplate',
            'MAX_AUTOMATED_TASKS': 'schedule.ScheduledTask',
        }
        
        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 limit for this tenant from billing entitlements
            # Map quota feature codes to billing feature codes
            feature_code_map = {
                'MAX_RESOURCES': 'max_resources',
                'MAX_ADDITIONAL_USERS': 'max_users',
                'MAX_EVENTS_PER_MONTH': 'max_events',
                'MAX_SERVICES': 'max_services',
                'MAX_APPOINTMENTS': 'max_appointments',
                'MAX_EMAIL_TEMPLATES': 'max_email_templates',
                'MAX_AUTOMATED_TASKS': 'max_automated_tasks',
            }
            billing_feature = feature_code_map.get(feature_code, feature_code.lower())
            limit = tenant.get_limit(billing_feature)

            if limit is None or limit == 0:
                # No limit defined or unlimited (0 means unlimited)
                return True
            
            # Count current usage
            # NOTE: Most models use django-tenants automatic scoping
            # But User is in shared schema, so needs special handling

            # Special handling for additional users (shared schema, exclude owner)
            if feature_code == 'MAX_ADDITIONAL_USERS':
                from smoothschedule.identity.users.models import User
                # Count users in this tenant, excluding the owner and archived users
                current_count = User.objects.filter(
                    tenant=tenant,
                    is_archived_by_quota=False
                ).exclude(
                    role=User.Role.TENANT_OWNER
                ).count()

            # Special handling for monthly appointment limit
            elif feature_code == 'MAX_APPOINTMENTS':
                from django.utils import timezone
                from datetime import timedelta
                # Count appointments in current month
                now = timezone.now()
                start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
                next_month = start_of_month + timedelta(days=32)
                start_of_next_month = next_month.replace(day=1)
                current_count = Model.objects.filter(
                    start_time__gte=start_of_month,
                    start_time__lt=start_of_next_month
                ).count()

            # Standard counting for tenant-scoped models
            else:
                # Exclude archived resources from count
                if hasattr(Model, 'is_archived_by_quota'):
                    current_count = Model.objects.filter(is_archived_by_quota=False).count()
                else:
                    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


# ==============================================================================
# Feature Permission Checks (Plan-Based)
# ==============================================================================

def HasFeaturePermission(permission_key):
    """
    Permission factory for checking feature permissions from subscription plans.

    Returns a DRF permission class that blocks operations when the tenant
    does not have the required feature permission in their subscription plan.

    Usage:
        class ProxyNumberViewSet(ModelViewSet):
            permission_classes = [IsAuthenticated, HasFeaturePermission('can_use_masked_phone_numbers')]

    Args:
        permission_key: Feature permission key (e.g., 'can_use_sms_reminders',
                       'can_use_custom_domain', 'can_white_label')

    Returns:
        FeaturePermission class configured for the permission key

    How it Works:
        1. Gets the tenant from the request
        2. Checks if tenant.has_feature(permission_key) returns True
        3. If False, raises PermissionDenied (403) with upgrade message
    """
    from rest_framework.permissions import BasePermission
    from rest_framework.exceptions import PermissionDenied

    class FeaturePermission(BasePermission):
        """
        Dynamically generated permission class for feature checking.
        """

        # Human-readable feature names for error messages
        FEATURE_NAMES = {
            'can_use_sms_reminders': 'SMS Reminders',
            'can_use_masked_phone_numbers': 'Masked Calling',
            'can_use_custom_domain': 'Custom Domains',
            'can_white_label': 'White Labeling',
            'can_create_automations': 'Automation Creation',
            'can_use_webhooks': 'Webhooks',
            'can_accept_payments': 'Payment Processing',
            'can_api_access': 'API Access',
            'can_manage_oauth_credentials': 'Custom OAuth Credentials',
            'can_use_calendar_sync': 'Calendar Sync',
            'advanced_analytics': 'Advanced Analytics',
            'advanced_reporting': 'Advanced Reporting',
        }

        def has_permission(self, request, view):
            """
            Check if tenant has the required feature permission.

            Returns True if tenant has permission, raises PermissionDenied otherwise.
            """
            # Get tenant from request
            tenant = getattr(request, 'tenant', None)

            if not tenant:
                # No tenant in request - this is likely a public schema operation
                # or platform admin operation. Allow it to proceed.
                return True

            # Check if tenant has the feature
            if not tenant.has_feature(permission_key):
                feature_name = self.FEATURE_NAMES.get(
                    permission_key,
                    permission_key.replace('can_', '').replace('_', ' ').title()
                )
                raise PermissionDenied(
                    f"Your current plan does not include {feature_name}. "
                    f"Please upgrade your subscription to access this feature."
                )

            return True

        def has_object_permission(self, request, view, obj):
            """
            Object-level permission check. Uses the same logic as has_permission.
            """
            return self.has_permission(request, view)

    return FeaturePermission

