"""Core Permissions

This module contains custom permission classes and utilities for the Adtlas project.
It provides role-based access control and permission checking functionality.
"""

from typing import Any, List, Optional, Union
from django.contrib.auth.models import Permission
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.http import HttpRequest
from rest_framework import permissions
from rest_framework.request import Request


User = get_user_model()


# ============================================================================
# Django REST Framework Permission Classes
# ============================================================================

class IsOwnerOrReadOnly(permissions.BasePermission):
    """Permission that allows owners to edit their own objects."""
    
    def has_object_permission(self, request: Request, view, obj) -> bool:
        # Read permissions for any request
        if request.method in permissions.SAFE_METHODS:
            return True
        
        # Write permissions only for owner
        return obj.owner == request.user or obj.created_by == request.user


class IsOwnerOrAdmin(permissions.BasePermission):
    """Permission that allows owners and admins to access objects."""
    
    def has_object_permission(self, request: Request, view, obj) -> bool:
        # Admin can access everything
        if request.user.is_staff or request.user.is_superuser:
            return True
        
        # Owner can access their own objects
        return obj.owner == request.user or obj.created_by == request.user


class IsAdminOrReadOnly(permissions.BasePermission):
    """Permission that allows admins to edit and others to read."""
    
    def has_permission(self, request: Request, view) -> bool:
        # Read permissions for authenticated users
        if request.method in permissions.SAFE_METHODS:
            return request.user.is_authenticated
        
        # Write permissions only for admin
        return request.user.is_staff or request.user.is_superuser


class IsSuperUserOnly(permissions.BasePermission):
    """Permission that allows only superusers."""
    
    def has_permission(self, request: Request, view) -> bool:
        return request.user.is_superuser


class IsAuthenticatedOrReadOnly(permissions.BasePermission):
    """Permission that allows authenticated users to edit and others to read."""
    
    def has_permission(self, request: Request, view) -> bool:
        # Read permissions for everyone
        if request.method in permissions.SAFE_METHODS:
            return True
        
        # Write permissions for authenticated users
        return request.user.is_authenticated


class HasRolePermission(permissions.BasePermission):
    """Permission that checks for specific roles."""
    
    required_roles = []  # Override in subclasses
    
    def has_permission(self, request: Request, view) -> bool:
        if not request.user.is_authenticated:
            return False
        
        # Superuser has all permissions
        if request.user.is_superuser:
            return True
        
        # Check if user has any of the required roles
        user_roles = self.get_user_roles(request.user)
        return any(role in user_roles for role in self.required_roles)
    
    def get_user_roles(self, user) -> List[str]:
        """Get list of user roles.
        
        Args:
            user: User instance
            
        Returns:
            List of role names
        """
        roles = []
        
        if hasattr(user, 'roles'):
            roles = [role.name for role in user.roles.all()]
        elif hasattr(user, 'userroleassignment_set'):
            roles = [
                assignment.role.name 
                for assignment in user.userroleassignment_set.filter(is_active=True)
            ]
        
        return roles


class HasSpecificPermission(permissions.BasePermission):
    """Permission that checks for specific Django permissions."""
    
    required_permissions = []  # Override in subclasses
    
    def has_permission(self, request: Request, view) -> bool:
        if not request.user.is_authenticated:
            return False
        
        # Check if user has all required permissions
        return all(
            request.user.has_perm(perm) 
            for perm in self.required_permissions
        )


class IsOwnerOrHasPermission(permissions.BasePermission):
    """Permission that allows owners or users with specific permission."""
    
    required_permission = None  # Override in subclasses
    
    def has_object_permission(self, request: Request, view, obj) -> bool:
        # Owner can access
        if hasattr(obj, 'owner') and obj.owner == request.user:
            return True
        
        if hasattr(obj, 'created_by') and obj.created_by == request.user:
            return True
        
        # Check permission
        if self.required_permission:
            return request.user.has_perm(self.required_permission)
        
        return False


# ============================================================================
# Role-based Permission Classes
# ============================================================================

class AdminPermission(HasRolePermission):
    """Permission for admin role."""
    required_roles = ['admin', 'administrator']


class ManagerPermission(HasRolePermission):
    """Permission for manager role."""
    required_roles = ['manager', 'admin', 'administrator']


class EditorPermission(HasRolePermission):
    """Permission for editor role."""
    required_roles = ['editor', 'manager', 'admin', 'administrator']


class ViewerPermission(HasRolePermission):
    """Permission for viewer role."""
    required_roles = ['viewer', 'editor', 'manager', 'admin', 'administrator']


# ============================================================================
# Permission Utility Functions
# ============================================================================

def has_permission(user, permission: str) -> bool:
    """Check if user has specific permission.
    
    Args:
        user: User instance
        permission: Permission string (e.g., 'app.permission_name')
        
    Returns:
        bool: True if user has permission, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    return user.has_perm(permission)


def has_any_permission(user, permissions: List[str]) -> bool:
    """Check if user has any of the specified permissions.
    
    Args:
        user: User instance
        permissions: List of permission strings
        
    Returns:
        bool: True if user has any permission, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    return any(user.has_perm(perm) for perm in permissions)


def has_all_permissions(user, permissions: List[str]) -> bool:
    """Check if user has all specified permissions.
    
    Args:
        user: User instance
        permissions: List of permission strings
        
    Returns:
        bool: True if user has all permissions, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    return all(user.has_perm(perm) for perm in permissions)


def has_role(user, role: str) -> bool:
    """Check if user has specific role.
    
    Args:
        user: User instance
        role: Role name
        
    Returns:
        bool: True if user has role, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    user_roles = get_user_roles(user)
    return role in user_roles


def has_any_role(user, roles: List[str]) -> bool:
    """Check if user has any of the specified roles.
    
    Args:
        user: User instance
        roles: List of role names
        
    Returns:
        bool: True if user has any role, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    user_roles = get_user_roles(user)
    return any(role in user_roles for role in roles)


def get_user_roles(user) -> List[str]:
    """Get list of user roles.
    
    Args:
        user: User instance
        
    Returns:
        List of role names
    """
    if not user or not user.is_authenticated:
        return []
    
    roles = []
    
    # Check for roles relationship
    if hasattr(user, 'roles'):
        roles = [role.name for role in user.roles.all()]
    elif hasattr(user, 'userroleassignment_set'):
        roles = [
            assignment.role.name 
            for assignment in user.userroleassignment_set.filter(is_active=True)
        ]
    
    return roles


def get_user_permissions(user) -> List[str]:
    """Get list of user permissions.
    
    Args:
        user: User instance
        
    Returns:
        List of permission strings
    """
    if not user or not user.is_authenticated:
        return []
    
    # Get all permissions (direct and through groups)
    permissions = user.get_all_permissions()
    return list(permissions)


def is_owner(user, obj) -> bool:
    """Check if user is owner of object.
    
    Args:
        user: User instance
        obj: Object to check ownership
        
    Returns:
        bool: True if user is owner, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    # Check various owner fields
    owner_fields = ['owner', 'created_by', 'user']
    
    for field in owner_fields:
        if hasattr(obj, field):
            owner = getattr(obj, field)
            if owner == user:
                return True
    
    return False


def can_edit_object(user, obj) -> bool:
    """Check if user can edit object.
    
    Args:
        user: User instance
        obj: Object to check edit permission
        
    Returns:
        bool: True if user can edit, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    # Superuser can edit everything
    if user.is_superuser:
        return True
    
    # Owner can edit
    if is_owner(user, obj):
        return True
    
    # Check for edit permission
    model_name = obj._meta.model_name
    app_label = obj._meta.app_label
    edit_permission = f'{app_label}.change_{model_name}'
    
    return user.has_perm(edit_permission)


def can_delete_object(user, obj) -> bool:
    """Check if user can delete object.
    
    Args:
        user: User instance
        obj: Object to check delete permission
        
    Returns:
        bool: True if user can delete, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    # Superuser can delete everything
    if user.is_superuser:
        return True
    
    # Check for delete permission
    model_name = obj._meta.model_name
    app_label = obj._meta.app_label
    delete_permission = f'{app_label}.delete_{model_name}'
    
    return user.has_perm(delete_permission)


def can_view_object(user, obj) -> bool:
    """Check if user can view object.
    
    Args:
        user: User instance
        obj: Object to check view permission
        
    Returns:
        bool: True if user can view, False otherwise
    """
    if not user or not user.is_authenticated:
        return False
    
    # Superuser can view everything
    if user.is_superuser:
        return True
    
    # Owner can view
    if is_owner(user, obj):
        return True
    
    # Check for view permission
    model_name = obj._meta.model_name
    app_label = obj._meta.app_label
    view_permission = f'{app_label}.view_{model_name}'
    
    return user.has_perm(view_permission)


# ============================================================================
# Permission Decorators
# ============================================================================

def require_permission(permission: str):
    """Decorator that requires specific permission.
    
    Args:
        permission: Permission string required
        
    Returns:
        Decorator function
    """
    def decorator(func):
        def wrapper(request, *args, **kwargs):
            if not has_permission(request.user, permission):
                raise PermissionDenied(f"Permission {permission} required")
            return func(request, *args, **kwargs)
        return wrapper
    return decorator


def require_role(role: str):
    """Decorator that requires specific role.
    
    Args:
        role: Role name required
        
    Returns:
        Decorator function
    """
    def decorator(func):
        def wrapper(request, *args, **kwargs):
            if not has_role(request.user, role):
                raise PermissionDenied(f"Role {role} required")
            return func(request, *args, **kwargs)
        return wrapper
    return decorator


def require_ownership(obj_param: str = 'obj'):
    """Decorator that requires object ownership.
    
    Args:
        obj_param: Parameter name containing the object
        
    Returns:
        Decorator function
    """
    def decorator(func):
        def wrapper(request, *args, **kwargs):
            obj = kwargs.get(obj_param)
            if obj and not is_owner(request.user, obj):
                raise PermissionDenied("Object ownership required")
            return func(request, *args, **kwargs)
        return wrapper
    return decorator


# ============================================================================
# Permission Management Functions
# ============================================================================

def create_permission(codename: str, name: str, content_type: ContentType) -> Permission:
    """Create a new permission.
    
    Args:
        codename: Permission codename
        name: Permission name
        content_type: Content type for permission
        
    Returns:
        Created Permission instance
    """
    permission, created = Permission.objects.get_or_create(
        codename=codename,
        content_type=content_type,
        defaults={'name': name}
    )
    return permission


def assign_permission(user, permission: Union[str, Permission]):
    """Assign permission to user.
    
    Args:
        user: User instance
        permission: Permission string or Permission instance
    """
    if isinstance(permission, str):
        app_label, codename = permission.split('.')
        try:
            permission_obj = Permission.objects.get(
                content_type__app_label=app_label,
                codename=codename
            )
        except Permission.DoesNotExist:
            return False
    else:
        permission_obj = permission
    
    user.user_permissions.add(permission_obj)
    return True


def remove_permission(user, permission: Union[str, Permission]):
    """Remove permission from user.
    
    Args:
        user: User instance
        permission: Permission string or Permission instance
    """
    if isinstance(permission, str):
        app_label, codename = permission.split('.')
        try:
            permission_obj = Permission.objects.get(
                content_type__app_label=app_label,
                codename=codename
            )
        except Permission.DoesNotExist:
            return False
    else:
        permission_obj = permission
    
    user.user_permissions.remove(permission_obj)
    return True


def get_model_permissions(model_class) -> List[str]:
    """Get all permissions for a model.
    
    Args:
        model_class: Model class
        
    Returns:
        List of permission strings
    """
    content_type = ContentType.objects.get_for_model(model_class)
    permissions = Permission.objects.filter(content_type=content_type)
    
    return [
        f"{content_type.app_label}.{perm.codename}"
        for perm in permissions
    ]


# ============================================================================
# Context Processors
# ============================================================================

def permissions_context(request):
    """Context processor for permissions.
    
    Args:
        request: HTTP request
        
    Returns:
        Dictionary with permission context
    """
    if not request.user.is_authenticated:
        return {
            'user_permissions': [],
            'user_roles': [],
            'is_admin': False,
            'is_manager': False
        }
    
    return {
        'user_permissions': get_user_permissions(request.user),
        'user_roles': get_user_roles(request.user),
        'is_admin': has_any_role(request.user, ['admin', 'administrator']),
        'is_manager': has_any_role(request.user, ['manager', 'admin', 'administrator'])
    }