"""Core Decorators

This module contains common decorators used throughout the Adtlas project.
Decorators provide a way to modify or enhance functions and classes.
"""

import functools
import time
import logging
from typing import Any, Callable, Optional, Union

from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, JsonResponse, HttpResponseForbidden
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.conf import settings
from django.core.cache import cache
from django.utils import timezone

from .utils import is_ajax, get_client_ip
from .models import ActivityLog


User = get_user_model()
logger = logging.getLogger(__name__)


# ============================================================================
# Authentication & Authorization Decorators
# ============================================================================

def admin_required(view_func: Callable) -> Callable:
    """Decorator that requires user to be an admin.
    
    Args:
        view_func: The view function to decorate
        
    Returns:
        Decorated function that checks for admin privileges
    """
    @functools.wraps(view_func)
    def wrapper(request, *args, **kwargs):
        if not request.user.is_authenticated:
            return redirect('accounts:login')
        
        if not request.user.is_staff:
            if is_ajax(request):
                return JsonResponse({
                    'success': False,
                    'error': 'Admin privileges required'
                }, status=403)
            raise PermissionDenied("Admin privileges required")
        
        return view_func(request, *args, **kwargs)
    
    return wrapper


def superuser_required(view_func: Callable) -> Callable:
    """Decorator that requires user to be a superuser.
    
    Args:
        view_func: The view function to decorate
        
    Returns:
        Decorated function that checks for superuser privileges
    """
    @functools.wraps(view_func)
    def wrapper(request, *args, **kwargs):
        if not request.user.is_authenticated:
            return redirect('accounts:login')
        
        if not request.user.is_superuser:
            if is_ajax(request):
                return JsonResponse({
                    'success': False,
                    'error': 'Superuser privileges required'
                }, status=403)
            raise PermissionDenied("Superuser privileges required")
        
        return view_func(request, *args, **kwargs)
    
    return wrapper


def permission_required(permission: str, raise_exception: bool = True):
    """Decorator that requires specific permission.
    
    Args:
        permission: Permission string (e.g., 'app.permission_name')
        raise_exception: Whether to raise exception or redirect
        
    Returns:
        Decorator function
    """
    def decorator(view_func: Callable) -> Callable:
        @functools.wraps(view_func)
        def wrapper(request, *args, **kwargs):
            if not request.user.is_authenticated:
                return redirect('accounts:login')
            
            if not request.user.has_perm(permission):
                if is_ajax(request):
                    return JsonResponse({
                        'success': False,
                        'error': f'Permission {permission} required'
                    }, status=403)
                
                if raise_exception:
                    raise PermissionDenied(f"Permission {permission} required")
                else:
                    return redirect('accounts:login')
            
            return view_func(request, *args, **kwargs)
        
        return wrapper
    
    return decorator


def role_required(role: str):
    """Decorator that requires specific role.
    
    Args:
        role: Role name required
        
    Returns:
        Decorator function
    """
    def decorator(view_func: Callable) -> Callable:
        @functools.wraps(view_func)
        def wrapper(request, *args, **kwargs):
            if not request.user.is_authenticated:
                return redirect('accounts:login')
            
            # Check if user has the required role
            user_roles = []
            if hasattr(request.user, 'roles'):
                user_roles = [r.name for r in request.user.roles.all()]
            elif hasattr(request.user, 'userroleassignment_set'):
                user_roles = [
                    assignment.role.name 
                    for assignment in request.user.userroleassignment_set.filter(
                        is_active=True
                    )
                ]
            
            if role not in user_roles:
                if is_ajax(request):
                    return JsonResponse({
                        'success': False,
                        'error': f'Role {role} required'
                    }, status=403)
                raise PermissionDenied(f"Role {role} required")
            
            return view_func(request, *args, **kwargs)
        
        return wrapper
    
    return decorator


def anonymous_required(view_func: Callable) -> Callable:
    """Decorator that requires user to be anonymous (not logged in).
    
    Args:
        view_func: The view function to decorate
        
    Returns:
        Decorated function that checks for anonymous user
    """
    @functools.wraps(view_func)
    def wrapper(request, *args, **kwargs):
        if request.user.is_authenticated:
            return redirect('core:dashboard')  # Redirect to dashboard or home
        
        return view_func(request, *args, **kwargs)
    
    return wrapper


# ============================================================================
# AJAX & API Decorators
# ============================================================================

def ajax_required(view_func: Callable) -> Callable:
    """Decorator that requires request to be AJAX.
    
    Args:
        view_func: The view function to decorate
        
    Returns:
        Decorated function that checks for AJAX request
    """
    @functools.wraps(view_func)
    def wrapper(request, *args, **kwargs):
        if not is_ajax(request):
            return JsonResponse({
                'success': False,
                'error': 'AJAX request required'
            }, status=400)
        
        return view_func(request, *args, **kwargs)
    
    return wrapper


def json_response(view_func: Callable) -> Callable:
    """Decorator that ensures view returns JSON response.
    
    Args:
        view_func: The view function to decorate
        
    Returns:
        Decorated function that wraps response in JSON
    """
    @functools.wraps(view_func)
    def wrapper(request, *args, **kwargs):
        try:
            result = view_func(request, *args, **kwargs)
            
            # If already a JsonResponse, return as is
            if isinstance(result, JsonResponse):
                return result
            
            # If it's a dict, convert to JsonResponse
            if isinstance(result, dict):
                return JsonResponse(result)
            
            # If it's other HttpResponse, try to extract data
            if isinstance(result, HttpResponse):
                return JsonResponse({
                    'success': True,
                    'data': result.content.decode('utf-8') if result.content else None
                })
            
            # For other types, wrap in success response
            return JsonResponse({
                'success': True,
                'data': result
            })
            
        except Exception as e:
            logger.error(f"Error in view {view_func.__name__}: {e}")
            return JsonResponse({
                'success': False,
                'error': str(e)
            }, status=500)
    
    return wrapper


# ============================================================================
# Performance & Caching Decorators
# ============================================================================

def timing_decorator(view_func: Callable) -> Callable:
    """Decorator that logs execution time of view.
    
    Args:
        view_func: The view function to decorate
        
    Returns:
        Decorated function that logs execution time
    """
    @functools.wraps(view_func)
    def wrapper(request, *args, **kwargs):
        start_time = time.time()
        
        try:
            result = view_func(request, *args, **kwargs)
            
            execution_time = time.time() - start_time
            
            # Log slow requests
            if execution_time > 1.0:  # Log requests taking more than 1 second
                logger.warning(
                    f"Slow request: {view_func.__name__} took {execution_time:.2f}s "
                    f"for {request.method} {request.path}"
                )
            else:
                logger.debug(
                    f"Request: {view_func.__name__} took {execution_time:.2f}s "
                    f"for {request.method} {request.path}"
                )
            
            return result
            
        except Exception as e:
            execution_time = time.time() - start_time
            logger.error(
                f"Error in {view_func.__name__} after {execution_time:.2f}s: {e}"
            )
            raise
    
    return wrapper


def cache_result(timeout: int = 300, key_prefix: str = ''):
    """Decorator that caches view result.
    
    Args:
        timeout: Cache timeout in seconds
        key_prefix: Prefix for cache key
        
    Returns:
        Decorator function
    """
    def decorator(view_func: Callable) -> Callable:
        @functools.wraps(view_func)
        def wrapper(request, *args, **kwargs):
            # Generate cache key
            cache_key = f"{key_prefix}{view_func.__name__}_{hash(str(args) + str(kwargs))}"
            
            # Try to get from cache
            result = cache.get(cache_key)
            if result is not None:
                logger.debug(f"Cache hit for {cache_key}")
                return result
            
            # Execute view and cache result
            result = view_func(request, *args, **kwargs)
            cache.set(cache_key, result, timeout)
            
            logger.debug(f"Cache set for {cache_key}")
            return result
        
        return wrapper
    
    return decorator


# ============================================================================
# Rate Limiting Decorators
# ============================================================================

def rate_limit(max_requests: int = 60, window: int = 60, key_func: Optional[Callable] = None):
    """Decorator that implements rate limiting.
    
    Args:
        max_requests: Maximum requests allowed
        window: Time window in seconds
        key_func: Function to generate rate limit key
        
    Returns:
        Decorator function
    """
    def decorator(view_func: Callable) -> Callable:
        @functools.wraps(view_func)
        def wrapper(request, *args, **kwargs):
            # Generate rate limit key
            if key_func:
                rate_key = key_func(request)
            else:
                rate_key = f"rate_limit_{get_client_ip(request)}_{view_func.__name__}"
            
            # Get current count
            current_count = cache.get(rate_key, 0)
            
            if current_count >= max_requests:
                if is_ajax(request):
                    return JsonResponse({
                        'success': False,
                        'error': 'Rate limit exceeded. Please try again later.'
                    }, status=429)
                
                return HttpResponseForbidden("Rate limit exceeded. Please try again later.")
            
            # Increment count
            cache.set(rate_key, current_count + 1, window)
            
            return view_func(request, *args, **kwargs)
        
        return wrapper
    
    return decorator


# ============================================================================
# Logging & Monitoring Decorators
# ============================================================================

def log_activity(activity_type: str = 'view', description: str = ''):
    """Decorator that logs user activity.
    
    Args:
        activity_type: Type of activity
        description: Activity description
        
    Returns:
        Decorator function
    """
    def decorator(view_func: Callable) -> Callable:
        @functools.wraps(view_func)
        def wrapper(request, *args, **kwargs):
            try:
                result = view_func(request, *args, **kwargs)
                
                # Log the activity
                if request.user.is_authenticated:
                    ActivityLog.log_activity(
                        user=request.user,
                        activity_type=activity_type,
                        description=description or f"Accessed {view_func.__name__}",
                        ip_address=get_client_ip(request),
                        extra_data={
                            'view_name': view_func.__name__,
                            'method': request.method,
                            'path': request.path
                        }
                    )
                
                return result
                
            except Exception as e:
                # Log the error
                if request.user.is_authenticated:
                    ActivityLog.log_activity(
                        user=request.user,
                        activity_type='error',
                        description=f"Error in {view_func.__name__}: {str(e)}",
                        ip_address=get_client_ip(request),
                        extra_data={
                            'view_name': view_func.__name__,
                            'method': request.method,
                            'path': request.path,
                            'error': str(e)
                        }
                    )
                
                raise
        
        return wrapper
    
    return decorator


def exception_handler(view_func: Callable) -> Callable:
    """Decorator that handles exceptions gracefully.
    
    Args:
        view_func: The view function to decorate
        
    Returns:
        Decorated function that handles exceptions
    """
    @functools.wraps(view_func)
    def wrapper(request, *args, **kwargs):
        try:
            return view_func(request, *args, **kwargs)
            
        except PermissionDenied as e:
            logger.warning(f"Permission denied in {view_func.__name__}: {e}")
            if is_ajax(request):
                return JsonResponse({
                    'success': False,
                    'error': 'Permission denied'
                }, status=403)
            raise
            
        except Exception as e:
            logger.error(f"Unexpected error in {view_func.__name__}: {e}")
            
            if settings.DEBUG:
                raise
            
            if is_ajax(request):
                return JsonResponse({
                    'success': False,
                    'error': 'An unexpected error occurred'
                }, status=500)
            
            # Return a generic error page
            return HttpResponse(
                "An unexpected error occurred. Please try again later.",
                status=500
            )
    
    return wrapper


# ============================================================================
# Class-based View Decorators
# ============================================================================

class LoginRequiredMixin:
    """Mixin that requires user to be logged in."""
    
    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)


class AdminRequiredMixin:
    """Mixin that requires user to be admin."""
    
    @method_decorator(admin_required)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)


class AjaxRequiredMixin:
    """Mixin that requires AJAX request."""
    
    @method_decorator(ajax_required)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)


class TimingMixin:
    """Mixin that logs execution time."""
    
    @method_decorator(timing_decorator)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)


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

def combine_decorators(*decorators):
    """Combine multiple decorators into one.
    
    Args:
        *decorators: Decorators to combine
        
    Returns:
        Combined decorator function
    """
    def decorator(view_func: Callable) -> Callable:
        for dec in reversed(decorators):
            view_func = dec(view_func)
        return view_func
    
    return decorator


# Common decorator combinations
api_view = combine_decorators(ajax_required, json_response, exception_handler)
admin_api_view = combine_decorators(admin_required, ajax_required, json_response, exception_handler)
secure_view = combine_decorators(login_required, exception_handler, timing_decorator)